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.c | 146 +++++++++++++------------------------------------------- 1 file changed, 34 insertions(+), 112 deletions(-) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 7293c9b11f91..25640f681729 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -46,20 +46,16 @@ MODULE_AUTHOR("Evgeniy Polyakov "); MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol."); static int w1_timeout = 10; -static int w1_control_timeout = 1; int w1_max_slave_count = 10; int w1_max_slave_ttl = 10; module_param_named(timeout, w1_timeout, int, 0); -module_param_named(control_timeout, w1_control_timeout, int, 0); module_param_named(max_slave_count, w1_max_slave_count, int, 0); module_param_named(slave_ttl, w1_max_slave_ttl, int, 0); DEFINE_MUTEX(w1_mlock); LIST_HEAD(w1_masters); -static struct task_struct *w1_control_thread; - static int w1_master_match(struct device *dev, struct device_driver *drv) { return 1; @@ -390,7 +386,7 @@ int w1_create_master_attributes(struct w1_master *master) return sysfs_create_group(&master->dev.kobj, &w1_master_defattr_group); } -static void w1_destroy_master_attributes(struct w1_master *master) +void w1_destroy_master_attributes(struct w1_master *master) { sysfs_remove_group(&master->dev.kobj, &w1_master_defattr_group); } @@ -567,7 +563,7 @@ static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn) return 0; } -static void w1_slave_detach(struct w1_slave *sl) +void w1_slave_detach(struct w1_slave *sl) { struct w1_netlink_msg msg; @@ -591,24 +587,6 @@ static void w1_slave_detach(struct w1_slave *sl) kfree(sl); } -static struct w1_master *w1_search_master(void *data) -{ - struct w1_master *dev; - int found = 0; - - mutex_lock(&w1_mlock); - list_for_each_entry(dev, &w1_masters, w1_master_entry) { - if (dev->bus_master->data == data) { - found = 1; - atomic_inc(&dev->refcnt); - break; - } - } - mutex_unlock(&w1_mlock); - - return (found)?dev:NULL; -} - struct w1_master *w1_search_master_id(u32 id) { struct w1_master *dev; @@ -656,34 +634,49 @@ struct w1_slave *w1_search_slave(struct w1_reg_num *id) return (found)?sl:NULL; } -void w1_reconnect_slaves(struct w1_family *f) +void w1_reconnect_slaves(struct w1_family *f, int attach) { + struct w1_slave *sl, *sln; struct w1_master *dev; mutex_lock(&w1_mlock); list_for_each_entry(dev, &w1_masters, w1_master_entry) { - dev_dbg(&dev->dev, "Reconnecting slaves in %s into new family %02x.\n", - dev->name, f->fid); - set_bit(W1_MASTER_NEED_RECONNECT, &dev->flags); + dev_dbg(&dev->dev, "Reconnecting slaves in device %s " + "for family %02x.\n", dev->name, f->fid); + mutex_lock(&dev->mutex); + list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { + /* If it is a new family, slaves with the default + * family driver and are that family will be + * connected. If the family is going away, devices + * matching that family are reconneced. + */ + if ((attach && sl->family->fid == W1_FAMILY_DEFAULT + && sl->reg_num.family == f->fid) || + (!attach && sl->family->fid == f->fid)) { + struct w1_reg_num rn; + + memcpy(&rn, &sl->reg_num, sizeof(rn)); + w1_slave_detach(sl); + + w1_attach_slave_device(dev, &rn); + } + } + dev_dbg(&dev->dev, "Reconnecting slaves in device %s " + "has been finished.\n", dev->name); + mutex_unlock(&dev->mutex); } mutex_unlock(&w1_mlock); } -static void w1_slave_found(void *data, u64 rn) +static void w1_slave_found(struct w1_master *dev, u64 rn) { int slave_count; struct w1_slave *sl; struct list_head *ent; struct w1_reg_num *tmp; - struct w1_master *dev; u64 rn_le = cpu_to_le64(rn); - dev = w1_search_master(data); - if (!dev) { - printk(KERN_ERR "Failed to find w1 master device for data %p, " - "it is impossible.\n", data); - return; - } + atomic_inc(&dev->refcnt); tmp = (struct w1_reg_num *) &rn; @@ -785,76 +778,11 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb if ( (desc_bit == last_zero) || (last_zero < 0)) last_device = 1; desc_bit = last_zero; - cb(dev->bus_master->data, rn); + cb(dev, rn); } } } -static int w1_control(void *data) -{ - struct w1_slave *sl, *sln; - struct w1_master *dev, *n; - int have_to_wait = 0; - - set_freezable(); - while (!kthread_should_stop() || have_to_wait) { - have_to_wait = 0; - - try_to_freeze(); - msleep_interruptible(w1_control_timeout * 1000); - - list_for_each_entry_safe(dev, n, &w1_masters, w1_master_entry) { - if (!kthread_should_stop() && !dev->flags) - continue; - /* - * Little race: we can create thread but not set the flag. - * Get a chance for external process to set flag up. - */ - if (!dev->initialized) { - have_to_wait = 1; - continue; - } - - if (kthread_should_stop() || test_bit(W1_MASTER_NEED_EXIT, &dev->flags)) { - set_bit(W1_MASTER_NEED_EXIT, &dev->flags); - - mutex_lock(&w1_mlock); - list_del(&dev->w1_master_entry); - mutex_unlock(&w1_mlock); - - mutex_lock(&dev->mutex); - list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { - w1_slave_detach(sl); - } - w1_destroy_master_attributes(dev); - mutex_unlock(&dev->mutex); - atomic_dec(&dev->refcnt); - continue; - } - - if (test_bit(W1_MASTER_NEED_RECONNECT, &dev->flags)) { - dev_dbg(&dev->dev, "Reconnecting slaves in device %s.\n", dev->name); - mutex_lock(&dev->mutex); - list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { - if (sl->family->fid == W1_FAMILY_DEFAULT) { - struct w1_reg_num rn; - - memcpy(&rn, &sl->reg_num, sizeof(rn)); - w1_slave_detach(sl); - - w1_attach_slave_device(dev, &rn); - } - } - dev_dbg(&dev->dev, "Reconnecting slaves in device %s has been finished.\n", dev->name); - clear_bit(W1_MASTER_NEED_RECONNECT, &dev->flags); - mutex_unlock(&dev->mutex); - } - } - } - - return 0; -} - void w1_search_process(struct w1_master *dev, u8 search_type) { struct w1_slave *sl, *sln; @@ -932,18 +860,13 @@ static int w1_init(void) goto err_out_master_unregister; } - w1_control_thread = kthread_run(w1_control, NULL, "w1_control"); - if (IS_ERR(w1_control_thread)) { - retval = PTR_ERR(w1_control_thread); - printk(KERN_ERR "Failed to create control thread. err=%d\n", - retval); - goto err_out_slave_unregister; - } - return 0; +#if 0 +/* For undoing the slave register if there was a step after it. */ err_out_slave_unregister: driver_unregister(&w1_slave_driver); +#endif err_out_master_unregister: driver_unregister(&w1_master_driver); @@ -959,13 +882,12 @@ static void w1_fini(void) { struct w1_master *dev; + /* Set netlink removal messages and some cleanup */ list_for_each_entry(dev, &w1_masters, w1_master_entry) __w1_remove_master_device(dev); w1_fini_netlink(); - kthread_stop(w1_control_thread); - driver_unregister(&w1_slave_driver); driver_unregister(&w1_master_driver); bus_unregister(&w1_bus_type); -- cgit v1.2.3 From 0d671b272af9eb06260ab3fd210d454e98dd4216 Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:39 -0700 Subject: W1: abort search early on on exit Early abort if the master driver or the hardware goes away in the middle of a bus search operation. The alternative is to spam the print buffer up to 64*64 times with read errors in the case of USB. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 25640f681729..aac03f151fe0 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -772,6 +772,11 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb /* extract the direction taken & update the device number */ tmp64 = (triplet_ret >> 2); rn |= (tmp64 << i); + + if (test_bit(W1_MASTER_NEED_EXIT, &dev->flags)) { + printk(KERN_INFO "Abort w1_search (exiting)\n"); + return; + } } if ( (triplet_ret & 0x03) != 0x03 ) { -- cgit v1.2.3 From 01e14d6db9654be005a0a5384090aea2cde39976 Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:40 -0700 Subject: W1: don't delay search start Move the creation of the w1_process thread to after the device has been initialized. This way w1_process doesn't have to check to see if it has been initialized and the bus search can proceed without sleeping. That also eliminates two checks in the w1_process loop. The sleep now happens at the end of the loop not the beginning. Also added a comment for why the atomic_set was 2. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 19 ++++++------------- drivers/w1/w1_int.c | 26 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 22 deletions(-) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index aac03f151fe0..730faa49d8dc 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -813,21 +813,14 @@ int w1_process(void *data) struct w1_master *dev = (struct w1_master *) data; while (!kthread_should_stop() && !test_bit(W1_MASTER_NEED_EXIT, &dev->flags)) { + if (dev->search_count) { + mutex_lock(&dev->mutex); + w1_search_process(dev, W1_SEARCH); + mutex_unlock(&dev->mutex); + } + try_to_freeze(); msleep_interruptible(w1_timeout * 1000); - - if (kthread_should_stop() || test_bit(W1_MASTER_NEED_EXIT, &dev->flags)) - break; - - if (!dev->initialized) - continue; - - if (dev->search_count == 0) - continue; - - mutex_lock(&dev->mutex); - w1_search_process(dev, W1_SEARCH); - mutex_unlock(&dev->mutex); } atomic_dec(&dev->refcnt); diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index ed3228017dad..36ff40250b61 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -61,6 +61,9 @@ static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, dev->slave_ttl = slave_ttl; dev->search_count = -1; /* continual scan */ + /* 1 for w1_process to decrement + * 1 for __w1_remove_master_device to decrement + */ atomic_set(&dev->refcnt, 2); INIT_LIST_HEAD(&dev->slist); @@ -109,23 +112,23 @@ int w1_add_master_device(struct w1_bus_master *master) if (!dev) return -ENOMEM; + retval = w1_create_master_attributes(dev); + if (retval) + goto err_out_free_dev; + + memcpy(dev->bus_master, master, sizeof(struct w1_bus_master)); + + dev->initialized = 1; + dev->thread = kthread_run(&w1_process, dev, "%s", dev->name); if (IS_ERR(dev->thread)) { retval = PTR_ERR(dev->thread); dev_err(&dev->dev, "Failed to create new kernel thread. err=%d\n", retval); - goto err_out_free_dev; + goto err_out_rm_attr; } - retval = w1_create_master_attributes(dev); - if (retval) - goto err_out_kill_thread; - - memcpy(dev->bus_master, master, sizeof(struct w1_bus_master)); - - dev->initialized = 1; - mutex_lock(&w1_mlock); list_add(&dev->w1_master_entry, &w1_masters); mutex_unlock(&w1_mlock); @@ -137,8 +140,13 @@ int w1_add_master_device(struct w1_bus_master *master) return 0; +#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: + w1_destroy_master_attributes(dev); err_out_free_dev: w1_free_dev(dev); -- 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.c') 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.c') 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 9b46741119590bf23c5c519b49024eb2001cfafa Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:43 -0700 Subject: W1: be able to manually add and remove slaves sysfs entries were added to manually add and remove slave devices. This is useful if the automatic bus searching is disabled, and the device ids are already known. [akpm@linux-foundation.org: fix printk types] Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 32418d4e555a..f3be2991e6e8 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -56,6 +56,8 @@ module_param_named(slave_ttl, w1_max_slave_ttl, int, 0); DEFINE_MUTEX(w1_mlock); LIST_HEAD(w1_masters); +static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn); + static int w1_master_match(struct device *dev, struct device_driver *drv) { return 1; @@ -357,7 +359,8 @@ static ssize_t w1_master_attribute_show_slave_count(struct device *dev, struct d return count; } -static ssize_t w1_master_attribute_show_slaves(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t w1_master_attribute_show_slaves(struct device *dev, + struct device_attribute *attr, char *buf) { struct w1_master *md = dev_to_w1_master(dev); int c = PAGE_SIZE; @@ -382,6 +385,135 @@ static ssize_t w1_master_attribute_show_slaves(struct device *dev, struct device return PAGE_SIZE - c; } +static ssize_t w1_master_attribute_show_add(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int c = PAGE_SIZE; + c -= snprintf(buf+PAGE_SIZE - c, c, + "write device id xx-xxxxxxxxxxxx to add slave\n"); + return PAGE_SIZE - c; +} + +static int w1_atoreg_num(struct device *dev, const char *buf, size_t count, + struct w1_reg_num *rn) +{ + unsigned int family; + unsigned long long id; + int i; + u64 rn64_le; + + /* The CRC value isn't read from the user because the sysfs directory + * doesn't include it and most messages from the bus search don't + * print it either. It would be unreasonable for the user to then + * provide it. + */ + const char *error_msg = "bad slave string format, expecting " + "ff-dddddddddddd\n"; + + if (buf[2] != '-') { + dev_err(dev, "%s", error_msg); + return -EINVAL; + } + i = sscanf(buf, "%02x-%012llx", &family, &id); + if (i != 2) { + dev_err(dev, "%s", error_msg); + return -EINVAL; + } + rn->family = family; + rn->id = id; + + rn64_le = cpu_to_le64(*(u64 *)rn); + rn->crc = w1_calc_crc8((u8 *)&rn64_le, 7); + +#if 0 + dev_info(dev, "With CRC device is %02x.%012llx.%02x.\n", + rn->family, (unsigned long long)rn->id, rn->crc); +#endif + + return 0; +} + +/* Searches the slaves in the w1_master and returns a pointer or NULL. + * Note: must hold the mutex + */ +static struct w1_slave *w1_slave_search_device(struct w1_master *dev, + struct w1_reg_num *rn) +{ + struct w1_slave *sl; + list_for_each_entry(sl, &dev->slist, w1_slave_entry) { + if (sl->reg_num.family == rn->family && + sl->reg_num.id == rn->id && + sl->reg_num.crc == rn->crc) { + return sl; + } + } + return NULL; +} + +static ssize_t w1_master_attribute_store_add(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_master *md = dev_to_w1_master(dev); + struct w1_reg_num rn; + struct w1_slave *sl; + ssize_t result = count; + + if (w1_atoreg_num(dev, buf, count, &rn)) + return -EINVAL; + + mutex_lock(&md->mutex); + sl = w1_slave_search_device(md, &rn); + /* It would be nice to do a targeted search one the one-wire bus + * for the new device to see if it is out there or not. But the + * current search doesn't support that. + */ + if (sl) { + dev_info(dev, "Device %s already exists\n", sl->name); + result = -EINVAL; + } else { + w1_attach_slave_device(md, &rn); + } + mutex_unlock(&md->mutex); + + return result; +} + +static ssize_t w1_master_attribute_show_remove(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int c = PAGE_SIZE; + c -= snprintf(buf+PAGE_SIZE - c, c, + "write device id xx-xxxxxxxxxxxx to remove slave\n"); + return PAGE_SIZE - c; +} + +static ssize_t w1_master_attribute_store_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_master *md = dev_to_w1_master(dev); + struct w1_reg_num rn; + struct w1_slave *sl; + ssize_t result = count; + + if (w1_atoreg_num(dev, buf, count, &rn)) + return -EINVAL; + + mutex_lock(&md->mutex); + sl = w1_slave_search_device(md, &rn); + if (sl) { + w1_slave_detach(sl); + } else { + dev_info(dev, "Device %02x-%012llx doesn't exists\n", rn.family, + (unsigned long long)rn.id); + result = -EINVAL; + } + mutex_unlock(&md->mutex); + + return result; +} + #define W1_MASTER_ATTR_RO(_name, _mode) \ struct device_attribute w1_master_attribute_##_name = \ __ATTR(w1_master_##_name, _mode, \ @@ -402,6 +534,8 @@ 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 W1_MASTER_ATTR_RW(add, S_IRUGO | S_IWUGO); +static W1_MASTER_ATTR_RW(remove, S_IRUGO | S_IWUGO); static struct attribute *w1_master_default_attrs[] = { &w1_master_attribute_name.attr, @@ -413,6 +547,8 @@ static struct attribute *w1_master_default_attrs[] = { &w1_master_attribute_pointer.attr, &w1_master_attribute_search.attr, &w1_master_attribute_pullup.attr, + &w1_master_attribute_add.attr, + &w1_master_attribute_remove.attr, NULL }; -- cgit v1.2.3 From cd7b28d33d0cabdc86fa7d546da07b9385274bbb Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:44 -0700 Subject: W1: recode w1_slave_found logic Simplified the logic in w1_slave_found by using the new w1_attach_slave_device function to find a slave and mark it as active or add the device if the crc checks. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index f3be2991e6e8..b1b21df835f5 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -845,9 +845,7 @@ void w1_reconnect_slaves(struct w1_family *f, int attach) static void w1_slave_found(struct w1_master *dev, u64 rn) { - int slave_count; struct w1_slave *sl; - struct list_head *ent; struct w1_reg_num *tmp; u64 rn_le = cpu_to_le64(rn); @@ -855,24 +853,12 @@ static void w1_slave_found(struct w1_master *dev, u64 rn) tmp = (struct w1_reg_num *) &rn; - slave_count = 0; - list_for_each(ent, &dev->slist) { - - sl = list_entry(ent, struct w1_slave, w1_slave_entry); - - if (sl->reg_num.family == tmp->family && - sl->reg_num.id == tmp->id && - sl->reg_num.crc == tmp->crc) { - set_bit(W1_SLAVE_ACTIVE, (long *)&sl->flags); - break; - } - - slave_count++; - } - - if (slave_count == dev->slave_count && - rn && ((rn >> 56) & 0xff) == w1_calc_crc8((u8 *)&rn_le, 7)) { - w1_attach_slave_device(dev, tmp); + sl = w1_slave_search_device(dev, tmp); + if (sl) { + set_bit(W1_SLAVE_ACTIVE, (long *)&sl->flags); + } else { + if (rn && tmp->crc == w1_calc_crc8((u8 *)&rn_le, 7)) + w1_attach_slave_device(dev, tmp); } atomic_dec(&dev->refcnt); -- cgit v1.2.3 From 07e003417b88deac4b887c98f499fc3b01bc8df0 Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:50 -0700 Subject: W1: w1_slave_read_id read bug, use device_attribute Fix bug reading the id sysfs file. If less than the full 8 bytes were read, the next read would start at the first byte instead of continuing. It needed the offset added to memcpy, or the better solution was to replace it with the device attribute instead of bin attribute. Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index b1b21df835f5..04e7de4b266a 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -103,35 +103,20 @@ static ssize_t w1_slave_read_name(struct device *dev, struct device_attribute *a return sprintf(buf, "%s\n", sl->name); } -static ssize_t w1_slave_read_id(struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +static ssize_t w1_slave_read_id(struct device *dev, + struct device_attribute *attr, char *buf) { - struct w1_slave *sl = kobj_to_w1_slave(kobj); - - if (off > 8) { - count = 0; - } else { - if (off + count > 8) - count = 8 - off; - - memcpy(buf, (u8 *)&sl->reg_num, count); - } + struct w1_slave *sl = dev_to_w1_slave(dev); + ssize_t count = sizeof(sl->reg_num); + memcpy(buf, (u8 *)&sl->reg_num, count); return count; } static struct device_attribute w1_slave_attr_name = __ATTR(name, S_IRUGO, w1_slave_read_name, NULL); - -static struct bin_attribute w1_slave_attr_bin_id = { - .attr = { - .name = "id", - .mode = S_IRUGO, - }, - .size = 8, - .read = w1_slave_read_id, -}; +static struct device_attribute w1_slave_attr_id = + __ATTR(id, S_IRUGO, w1_slave_read_id, NULL); /* Default family */ @@ -650,7 +635,7 @@ static int __w1_attach_slave_device(struct w1_slave *sl) } /* Create "id" entry */ - err = sysfs_create_bin_file(&sl->dev.kobj, &w1_slave_attr_bin_id); + err = device_create_file(&sl->dev, &w1_slave_attr_id); if (err < 0) { dev_err(&sl->dev, "sysfs file creation for [%s] failed. err=%d\n", @@ -672,7 +657,7 @@ static int __w1_attach_slave_device(struct w1_slave *sl) return 0; out_rem2: - sysfs_remove_bin_file(&sl->dev.kobj, &w1_slave_attr_bin_id); + device_remove_file(&sl->dev, &w1_slave_attr_id); out_rem1: device_remove_file(&sl->dev, &w1_slave_attr_name); out_unreg: @@ -754,7 +739,7 @@ void w1_slave_detach(struct w1_slave *sl) msg.type = W1_SLAVE_REMOVE; w1_netlink_send(sl->master, &msg); - sysfs_remove_bin_file(&sl->dev.kobj, &w1_slave_attr_bin_id); + device_remove_file(&sl->dev, &w1_slave_attr_id); device_remove_file(&sl->dev, &w1_slave_attr_name); device_unregister(&sl->dev); -- cgit v1.2.3 From 7dc8f527ef20bf95143dfbe2ecc01dc70b1e6ab7 Mon Sep 17 00:00:00 2001 From: David Fries Date: Wed, 15 Oct 2008 22:04:58 -0700 Subject: W1: w1.c s/printk/dev_dbg/ s/printk/dev_dbg/ Signed-off-by: David Fries Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/w1/w1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/w1/w1.c') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 04e7de4b266a..3b615d4022ee 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -81,10 +81,10 @@ static void w1_slave_release(struct device *dev) { struct w1_slave *sl = dev_to_w1_slave(dev); - printk("%s: Releasing %s.\n", __func__, sl->name); + dev_dbg(dev, "%s: Releasing %s.\n", __func__, sl->name); while (atomic_read(&sl->refcnt)) { - printk("Waiting for %s to become free: refcnt=%d.\n", + dev_dbg(dev, "Waiting for %s to become free: refcnt=%d.\n", sl->name, atomic_read(&sl->refcnt)); if (msleep_interruptible(1000)) flush_signals(current); @@ -920,7 +920,7 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb rn |= (tmp64 << i); if (kthread_should_stop()) { - printk(KERN_INFO "Abort w1_search (exiting)\n"); + dev_dbg(&dev->dev, "Abort w1_search\n"); return; } } -- cgit v1.2.3