From c30c9b15187e977ab5928f7276e9dfcd8d6f9460 Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:38 -0700 Subject: W1: fix deadlocks and remove w1_control_thread w1_control_thread was removed which would wake up every second and process newly registered family codes and complete some final cleanup for a removed master. Those routines were moved to the threads that were previously requesting those operations. A new function w1_reconnect_slaves takes care of reconnecting existing slave devices when a new family code is registered or removed. The removal case was missing and would cause a deadlock waiting for the family code reference count to decrease, which will now happen. A problem with registering a family code was fixed. A slave device would be unattached if it wasn't yet claimed, then attached at the end of the list, two unclaimed slaves would cause an infinite loop. The struct w1_bus_master.search now takes a pointer to the struct w1_master device to avoid searching for it, which would have caused a lock ordering deadlock with the removal of w1_control_thread. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'drivers/w1/w1.h') diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index f1df5343f4ad..13e0ea880bf8 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -77,7 +77,7 @@ struct w1_slave struct completion released; }; -typedef void (* w1_slave_found_callback)(void *, u64); +typedef void (*w1_slave_found_callback)(struct w1_master *, u64); /** @@ -142,12 +142,14 @@ struct w1_bus_master */ u8 (*reset_bus)(void *); - /** Really nice hardware can handles the different types of ROM search */ - void (*search)(void *, u8, w1_slave_found_callback); + /** Really nice hardware can handles the different types of ROM search + * w1_master* is passed to the slave found callback. + */ + void (*search)(void *, struct w1_master *, + u8, w1_slave_found_callback); }; #define W1_MASTER_NEED_EXIT 0 -#define W1_MASTER_NEED_RECONNECT 1 struct w1_master { @@ -181,12 +183,21 @@ struct w1_master }; int w1_create_master_attributes(struct w1_master *); +void w1_destroy_master_attributes(struct w1_master *master); void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb); void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb); struct w1_slave *w1_search_slave(struct w1_reg_num *id); void w1_search_process(struct w1_master *dev, u8 search_type); struct w1_master *w1_search_master_id(u32 id); +/* Disconnect and reconnect devices in the given family. Used for finding + * unclaimed devices after a family has been registered or releasing devices + * after a family has been unregistered. Set attach to 1 when a new family + * has just been registered, to 0 when it has been unregistered. + */ +void w1_reconnect_slaves(struct w1_family *f, int attach); +void w1_slave_detach(struct w1_slave *sl); + u8 w1_triplet(struct w1_master *dev, int bdir); void w1_write_8(struct w1_master *, u8); int w1_reset_bus(struct w1_master *); -- cgit v1.2.3 From 3c52e4e627896b42152cc6ff98216c302932227e Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:41 -0700 Subject: W1: w1_process, block or sleep The w1_process thread's sleeping and termination has been modified. msleep_interruptible was replaced by schedule_timeout and schedule to allow for kthread_stop and wake_up_process to interrupt the sleep and the unbounded sleeping when a bus search is disabled. The W1_MASTER_NEED_EXIT and flags variable were removed as they were redundant with kthread_should_stop and kthread_stop. If w1_process is sleeping, requesting a search will immediately wake it up rather than waiting for the end of msleep_interruptible previously. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 20 +++++++++++++++++--- drivers/w1/w1.h | 4 ---- drivers/w1/w1_int.c | 2 -- 3 files changed, 17 insertions(+), 9 deletions(-) (limited to 'drivers/w1/w1.h') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 730faa49d8dc..9b5c11701c37 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -251,6 +251,7 @@ static ssize_t w1_master_attribute_store_search(struct device * dev, mutex_lock(&md->mutex); md->search_count = simple_strtol(buf, NULL, 0); mutex_unlock(&md->mutex); + wake_up_process(md->thread); return count; } @@ -773,7 +774,7 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb tmp64 = (triplet_ret >> 2); rn |= (tmp64 << i); - if (test_bit(W1_MASTER_NEED_EXIT, &dev->flags)) { + if (kthread_should_stop()) { printk(KERN_INFO "Abort w1_search (exiting)\n"); return; } @@ -811,8 +812,12 @@ void w1_search_process(struct w1_master *dev, u8 search_type) int w1_process(void *data) { struct w1_master *dev = (struct w1_master *) data; + /* As long as w1_timeout is only set by a module parameter the sleep + * time can be calculated in jiffies once. + */ + const unsigned long jtime = msecs_to_jiffies(w1_timeout * 1000); - while (!kthread_should_stop() && !test_bit(W1_MASTER_NEED_EXIT, &dev->flags)) { + while (!kthread_should_stop()) { if (dev->search_count) { mutex_lock(&dev->mutex); w1_search_process(dev, W1_SEARCH); @@ -820,7 +825,16 @@ int w1_process(void *data) } try_to_freeze(); - msleep_interruptible(w1_timeout * 1000); + __set_current_state(TASK_INTERRUPTIBLE); + + if (kthread_should_stop()) + break; + + /* Only sleep when the search is active. */ + if (dev->search_count) + schedule_timeout(jtime); + else + schedule(); } atomic_dec(&dev->refcnt); diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index 13e0ea880bf8..34ee01e008ad 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -149,8 +149,6 @@ struct w1_bus_master u8, w1_slave_found_callback); }; -#define W1_MASTER_NEED_EXIT 0 - struct w1_master { struct list_head w1_master_entry; @@ -169,8 +167,6 @@ struct w1_master void *priv; int priv_size; - long flags; - struct task_struct *thread; struct mutex mutex; diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 36ff40250b61..bd877b24ce42 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -142,7 +142,6 @@ int w1_add_master_device(struct w1_bus_master *master) #if 0 /* Thread cleanup code, not required currently. */ err_out_kill_thread: - set_bit(W1_MASTER_NEED_EXIT, &dev->flags); kthread_stop(dev->thread); #endif err_out_rm_attr: @@ -158,7 +157,6 @@ void __w1_remove_master_device(struct w1_master *dev) struct w1_netlink_msg msg; struct w1_slave *sl, *sln; - set_bit(W1_MASTER_NEED_EXIT, &dev->flags); kthread_stop(dev->thread); mutex_lock(&w1_mlock); -- cgit v1.2.3 From 6a158c0de791a81eb761ccf26ead1bd0834abac2 Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:42 -0700 Subject: W1: feature, enable hardware strong pullup Add a strong pullup option to the w1 system. This supplies extra power for parasite powered devices. There is a w1_master_pullup sysfs entry and enable_pullup module parameter to enable or disable the strong pullup. The one wire bus requires at a minimum one wire and ground. The common wire is used for sending and receiving data as well as supplying power to devices that are parasite powered of which temperature sensors can be one example. The bus must be idle and left high while a temperature conversion is in progress, in addition the normal pullup resister on larger networks or even higher temperatures might not supply enough power. The pullup resister can't provide too much pullup current, because devices need to pull the bus down to write a value. This enables the strong pullup for supported hardware, which can supply more current when requested. Unsupported hardware will just delay with the bus high. The hardware USB 2490 one wire bus master has a bit on some commands which will enable the strong pullup as soon as the command finishes executing. To use strong pullup, call the new w1_next_pullup function to register the duration. The next write command will call set_pullup before sending the data, and reset the duration to zero once it returns. Switched from simple_strtol to strict_strtol. Signed-off-by: David Fries Cc: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 40 ++++++++++++++++++++++++++++++- drivers/w1/w1.h | 12 ++++++++++ drivers/w1/w1_int.c | 16 +++++++++++++ drivers/w1/w1_io.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 131 insertions(+), 5 deletions(-) (limited to 'drivers/w1/w1.h') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 9b5c11701c37..32418d4e555a 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -246,10 +246,14 @@ static ssize_t w1_master_attribute_store_search(struct device * dev, struct device_attribute *attr, const char * buf, size_t count) { + long tmp; struct w1_master *md = dev_to_w1_master(dev); + if (strict_strtol(buf, 0, &tmp) == -EINVAL) + return -EINVAL; + mutex_lock(&md->mutex); - md->search_count = simple_strtol(buf, NULL, 0); + md->search_count = tmp; mutex_unlock(&md->mutex); wake_up_process(md->thread); @@ -270,6 +274,38 @@ static ssize_t w1_master_attribute_show_search(struct device *dev, return count; } +static ssize_t w1_master_attribute_store_pullup(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long tmp; + struct w1_master *md = dev_to_w1_master(dev); + + if (strict_strtol(buf, 0, &tmp) == -EINVAL) + return -EINVAL; + + mutex_lock(&md->mutex); + md->enable_pullup = tmp; + mutex_unlock(&md->mutex); + wake_up_process(md->thread); + + return count; +} + +static ssize_t w1_master_attribute_show_pullup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct w1_master *md = dev_to_w1_master(dev); + ssize_t count; + + mutex_lock(&md->mutex); + count = sprintf(buf, "%d\n", md->enable_pullup); + mutex_unlock(&md->mutex); + + return count; +} + static ssize_t w1_master_attribute_show_pointer(struct device *dev, struct device_attribute *attr, char *buf) { struct w1_master *md = dev_to_w1_master(dev); @@ -365,6 +401,7 @@ static W1_MASTER_ATTR_RO(attempts, S_IRUGO); static W1_MASTER_ATTR_RO(timeout, S_IRUGO); static W1_MASTER_ATTR_RO(pointer, S_IRUGO); static W1_MASTER_ATTR_RW(search, S_IRUGO | S_IWUGO); +static W1_MASTER_ATTR_RW(pullup, S_IRUGO | S_IWUGO); static struct attribute *w1_master_default_attrs[] = { &w1_master_attribute_name.attr, @@ -375,6 +412,7 @@ static struct attribute *w1_master_default_attrs[] = { &w1_master_attribute_timeout.attr, &w1_master_attribute_pointer.attr, &w1_master_attribute_search.attr, + &w1_master_attribute_pullup.attr, NULL }; diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index 34ee01e008ad..00b84ab22808 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -142,6 +142,12 @@ struct w1_bus_master */ u8 (*reset_bus)(void *); + /** + * Put out a strong pull-up pulse of the specified duration. + * @return -1=Error, 0=completed + */ + u8 (*set_pullup)(void *, int); + /** Really nice hardware can handles the different types of ROM search * w1_master* is passed to the slave found callback. */ @@ -167,6 +173,11 @@ struct w1_master void *priv; int priv_size; + /** 5V strong pullup enabled flag, 1 enabled, zero disabled. */ + int enable_pullup; + /** 5V strong pullup duration in milliseconds, zero disabled. */ + int pullup_duration; + struct task_struct *thread; struct mutex mutex; @@ -201,6 +212,7 @@ u8 w1_calc_crc8(u8 *, int); void w1_write_block(struct w1_master *, const u8 *, int); u8 w1_read_block(struct w1_master *, u8 *, int); int w1_reset_select_slave(struct w1_slave *sl); +void w1_next_pullup(struct w1_master *, int); static inline struct w1_slave* dev_to_w1_slave(struct device *dev) { diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index bd877b24ce42..9d723efdf915 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -31,6 +31,9 @@ static u32 w1_ids = 1; +static int w1_enable_pullup = 1; +module_param_named(enable_pullup, w1_enable_pullup, int, 0); + static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, struct device_driver *driver, struct device *device) @@ -59,6 +62,7 @@ static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, dev->initialized = 0; dev->id = id; dev->slave_ttl = slave_ttl; + dev->enable_pullup = w1_enable_pullup; dev->search_count = -1; /* continual scan */ /* 1 for w1_process to decrement @@ -107,6 +111,18 @@ int w1_add_master_device(struct w1_bus_master *master) printk(KERN_ERR "w1_add_master_device: invalid function set\n"); return(-EINVAL); } + /* While it would be electrically possible to make a device that + * generated a strong pullup in bit bang mode, only hardare that + * controls 1-wire time frames are even expected to support a strong + * pullup. w1_io.c would need to support calling set_pullup before + * the last write_bit operation of a w1_write_8 which it currently + * doesn't. + */ + if (!master->write_byte && !master->touch_bit && master->set_pullup) { + printk(KERN_ERR "w1_add_master_device: set_pullup requires " + "write_byte or touch_bit, disabling\n"); + master->set_pullup = NULL; + } dev = w1_alloc_dev(w1_ids++, w1_max_slave_count, w1_max_slave_ttl, &w1_master_driver, &w1_master_device); if (!dev) diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c index 0056ef69009b..97b338a16abc 100644 --- a/drivers/w1/w1_io.c +++ b/drivers/w1/w1_io.c @@ -92,6 +92,40 @@ static void w1_write_bit(struct w1_master *dev, int bit) } } +/** + * Pre-write operation, currently only supporting strong pullups. + * Program the hardware for a strong pullup, if one has been requested and + * the hardware supports it. + * + * @param dev the master device + */ +static void w1_pre_write(struct w1_master *dev) +{ + if (dev->pullup_duration && + dev->enable_pullup && dev->bus_master->set_pullup) { + dev->bus_master->set_pullup(dev->bus_master->data, + dev->pullup_duration); + } +} + +/** + * Post-write operation, currently only supporting strong pullups. + * If a strong pullup was requested, clear it if the hardware supports + * them, or execute the delay otherwise, in either case clear the request. + * + * @param dev the master device + */ +static void w1_post_write(struct w1_master *dev) +{ + if (dev->pullup_duration) { + if (dev->enable_pullup && dev->bus_master->set_pullup) + dev->bus_master->set_pullup(dev->bus_master->data, 0); + else + msleep(dev->pullup_duration); + dev->pullup_duration = 0; + } +} + /** * Writes 8 bits. * @@ -102,11 +136,17 @@ void w1_write_8(struct w1_master *dev, u8 byte) { int i; - if (dev->bus_master->write_byte) + if (dev->bus_master->write_byte) { + w1_pre_write(dev); dev->bus_master->write_byte(dev->bus_master->data, byte); + } else - for (i = 0; i < 8; ++i) + for (i = 0; i < 8; ++i) { + if (i == 7) + w1_pre_write(dev); w1_touch_bit(dev, (byte >> i) & 0x1); + } + w1_post_write(dev); } EXPORT_SYMBOL_GPL(w1_write_8); @@ -203,11 +243,14 @@ void w1_write_block(struct w1_master *dev, const u8 *buf, int len) { int i; - if (dev->bus_master->write_block) + if (dev->bus_master->write_block) { + w1_pre_write(dev); dev->bus_master->write_block(dev->bus_master->data, buf, len); + } else for (i = 0; i < len; ++i) - w1_write_8(dev, buf[i]); + w1_write_8(dev, buf[i]); /* calls w1_pre_write */ + w1_post_write(dev); } EXPORT_SYMBOL_GPL(w1_write_block); @@ -306,3 +349,20 @@ int w1_reset_select_slave(struct w1_slave *sl) return 0; } EXPORT_SYMBOL_GPL(w1_reset_select_slave); + +/** + * Put out a strong pull-up of the specified duration after the next write + * operation. Not all hardware supports strong pullups. Hardware that + * doesn't support strong pullups will sleep for the given time after the + * write operation without a strong pullup. This is a one shot request for + * the next write, specifying zero will clear a previous request. + * The w1 master lock must be held. + * + * @param delay time in milliseconds + * @return 0=success, anything else=error + */ +void w1_next_pullup(struct w1_master *dev, int delay) +{ + dev->pullup_duration = delay; +} +EXPORT_SYMBOL_GPL(w1_next_pullup); -- cgit v1.2.3 From 347ba8a588c3e49f357291e5a1ac38a11d7e052d Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:51 -0700 Subject: W1: w1_therm fix user buffer overflow and cat Fixed data reading bug by replacing binary attribute with device one. Switching the sysfs read from bin_attribute to device_attribute. The data is far under PAGE_SIZE so the binary interface isn't required. As the device_attribute interface will make one call to w1_therm_read per file open and buffer, the result is, the following problems go away. buffer overflow: Execute a short read on w1_slave and w1_therm_read_bin would still return the full string size worth of data clobbering the user space buffer when it returned. Switching to device_attribute avoids the buffer overflow problems. With the snprintf formatted output dealing with short reads without doing a conversion per read would have been difficult. bad behavior: `cat w1_slave` would cause two temperature conversions to take place. Previously the code assumed W1_SLAVE_DATA_SIZE would be returned with each read. It would not return 0 unless the offset was less than W1_SLAVE_DATA_SIZE. The result was the first read did a temperature conversion, filled the buffer and returned, the offset in the second read would be less than W1_SLAVE_DATA_SIZE and also fill the buffer and return, the third read would finnally have a big enough offset to return 0 and cause cat to stop. Now w1_therm_read will be called at most once per open. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/slaves/w1_therm.c | 55 ++++++++++++++++---------------------------- drivers/w1/w1.h | 1 - 2 files changed, 20 insertions(+), 36 deletions(-) (limited to 'drivers/w1/w1.h') diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c index e87f464a6fb0..7de99dfd11c8 100644 --- a/drivers/w1/slaves/w1_therm.c +++ b/drivers/w1/slaves/w1_therm.c @@ -50,26 +50,20 @@ static u8 bad_roms[][9] = { {} }; -static ssize_t w1_therm_read_bin(struct kobject *, struct bin_attribute *, - char *, loff_t, size_t); +static ssize_t w1_therm_read(struct device *device, + struct device_attribute *attr, char *buf); -static struct bin_attribute w1_therm_bin_attr = { - .attr = { - .name = "w1_slave", - .mode = S_IRUGO, - }, - .size = W1_SLAVE_DATA_SIZE, - .read = w1_therm_read_bin, -}; +static struct device_attribute w1_therm_attr = + __ATTR(w1_slave, S_IRUGO, w1_therm_read, NULL); static int w1_therm_add_slave(struct w1_slave *sl) { - return sysfs_create_bin_file(&sl->dev.kobj, &w1_therm_bin_attr); + return device_create_file(&sl->dev, &w1_therm_attr); } static void w1_therm_remove_slave(struct w1_slave *sl) { - sysfs_remove_bin_file(&sl->dev.kobj, &w1_therm_bin_attr); + device_remove_file(&sl->dev, &w1_therm_attr); } static struct w1_family_ops w1_therm_fops = { @@ -168,30 +162,19 @@ static int w1_therm_check_rom(u8 rom[9]) return 0; } -static ssize_t w1_therm_read_bin(struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +static ssize_t w1_therm_read(struct device *device, + struct device_attribute *attr, char *buf) { - struct w1_slave *sl = kobj_to_w1_slave(kobj); + struct w1_slave *sl = dev_to_w1_slave(device); struct w1_master *dev = sl->master; u8 rom[9], crc, verdict; int i, max_trying = 10; + ssize_t c = PAGE_SIZE; mutex_lock(&sl->master->mutex); - if (off > W1_SLAVE_DATA_SIZE) { - count = 0; - goto out; - } - if (off + count > W1_SLAVE_DATA_SIZE) { - count = 0; - goto out; - } - - memset(buf, 0, count); memset(rom, 0, sizeof(rom)); - count = 0; verdict = 0; crc = 0; @@ -211,7 +194,9 @@ static ssize_t w1_therm_read_bin(struct kobject *kobj, w1_write_8(dev, W1_READ_SCRATCHPAD); if ((count = w1_read_block(dev, rom, 9)) != 9) { - dev_warn(&dev->dev, "w1_read_block() returned %d instead of 9.\n", count); + dev_warn(device, "w1_read_block() " + "returned %u instead of 9.\n", + count); } crc = w1_calc_crc8(rom, 8); @@ -226,22 +211,22 @@ static ssize_t w1_therm_read_bin(struct kobject *kobj, } for (i = 0; i < 9; ++i) - count += sprintf(buf + count, "%02x ", rom[i]); - count += sprintf(buf + count, ": crc=%02x %s\n", + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", rom[i]); + c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", crc, (verdict) ? "YES" : "NO"); if (verdict) memcpy(sl->rom, rom, sizeof(sl->rom)); else - dev_warn(&dev->dev, "18S20 doesn't respond to CONVERT_TEMP.\n"); + dev_warn(device, "18S20 doesn't respond to CONVERT_TEMP.\n"); for (i = 0; i < 9; ++i) - count += sprintf(buf + count, "%02x ", sl->rom[i]); + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", sl->rom[i]); - count += sprintf(buf + count, "t=%d\n", w1_convert_temp(rom, sl->family->fid)); -out: + c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", + w1_convert_temp(rom, sl->family->fid)); mutex_unlock(&dev->mutex); - return count; + return PAGE_SIZE - c; } static int __init w1_therm_init(void) diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index 00b84ab22808..cdaa6fffbfc7 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -46,7 +46,6 @@ struct w1_reg_num #include "w1_family.h" #define W1_MAXNAMELEN 32 -#define W1_SLAVE_DATA_SIZE 128 #define W1_SEARCH 0xF0 #define W1_ALARM_SEARCH 0xEC -- cgit v1.2.3