I attached the wrong patch in the last email. This one should work better. On Tuesday 14 June 2005 12:39 pm, bgardner at wabtec.com wrote: > Hi Evgeniy, > > I've been toying with a dev interface for the w1 bus to allow a user-space > program to talk directly to a w1 slave. > The w1 slave that I am using it with is the DS2433 (family 23 4kb EEPROM). > The examples in the documentation refer to that chip. > > The dev interface accepts a string as a series of w1 commands. > You then read back the results. > Documentation is included in the patch. > > Although it works, I don't think it is quite ready to be considered 'done'. > There are a few areas that I am not comfortable with: locking, reference > counts, etc. > > If you (or anyone else) would like to play with it, feel free to do so. > > The patch is against the latest set of w1 changes that we did. > > Thanks, > Ben -------------- next part -------------- --- linux-2.6.12-rc5-mm2-w1/drivers/w1/Kconfig 2005-06-07 10:53:49.000000000 -0500 +++ linux-2.6.12-rc5-mm2/drivers/w1/Kconfig 2005-06-06 17:07:09.000000000 -0500 @@ -11,6 +11,15 @@ This W1 support can also be built as a module. If so, the module will be called wire.ko. +config W1_DEV + tristate "w1 master character device" + depends on W1 + help + Enables a char device interface to the w1 bus. + + This can also be built as a module. If so, the module + will be called w1_dev. + config W1_MATROX tristate "Matrox G400 transport layer for 1-wire" depends on W1 && PCI --- linux-2.6.12-rc5-mm2-w1/drivers/w1/Makefile 2005-06-07 10:53:49.000000000 -0500 +++ linux-2.6.12-rc5-mm2/drivers/w1/Makefile 2005-06-07 11:03:45.000000000 -0500 @@ -18,3 +18,5 @@ obj-$(CONFIG_W1_DS9490_BRIDGE) += ds_w1_bridge.o +obj-$(CONFIG_W1_DEV) += w1_dev.o + --- linux-2.6.12-rc5-mm2-w1/drivers/w1/w1_int.h 2005-06-07 10:53:49.000000000 -0500 +++ linux-2.6.12-rc5-mm2/drivers/w1/w1_int.h 2005-06-14 11:16:59.000000000 -0500 @@ -27,6 +27,14 @@ #include "w1.h" +struct w1_notify_ops +{ + void (*notify_add)(struct w1_master *master); + void (*notify_remove)(struct w1_master *master); +}; +int w1_add_notify(struct w1_notify_ops *ops); +int w1_remove_notify(struct w1_notify_ops *ops); + int w1_add_master_device(struct w1_bus_master *); void w1_remove_master_device(struct w1_bus_master *); void __w1_remove_master_device(struct w1_master *); --- linux-2.6.12-rc5-mm2-w1/drivers/w1/w1_int.c 2005-06-14 11:09:23.000000000 -0500 +++ linux-2.6.12-rc5-mm2/drivers/w1/w1_int.c 2005-06-14 12:43:07.005994958 -0500 @@ -24,6 +24,7 @@ #include <linux/delay.h> #include "w1.h" +#include "w1_int.h" #include "w1_log.h" #include "w1_netlink.h" @@ -39,6 +40,10 @@ extern int w1_process(void *); +#define MAX_NOTIFY_OPS 1 +static struct w1_notify_ops *notify_ops[MAX_NOTIFY_OPS]; +static int notify_count = 0; + static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, struct device_driver *driver, struct device *device) @@ -119,6 +124,7 @@ int w1_add_master_device(struct w1_bus_master *master) { struct w1_master *dev; + int i; int retval = 0; struct w1_netlink_msg msg; @@ -159,6 +165,11 @@ msg.type = W1_MASTER_ADD; w1_netlink_send(dev, &msg); + for (i=0; i < notify_count; i++) { + if (notify_ops[i] && notify_ops[i]->notify_add) + notify_ops[i]->notify_add(dev); + } + return 0; err_out_kill_thread: @@ -178,6 +189,7 @@ void __w1_remove_master_device(struct w1_master *dev) { int err; + int i; struct w1_netlink_msg msg; set_bit(W1_MASTER_NEED_EXIT, &dev->flags); @@ -195,6 +207,11 @@ flush_signals(current); } + for (i=0; i < notify_count; i++) { + if (notify_ops[i] && notify_ops[i]->notify_remove) + notify_ops[i]->notify_remove(dev); + } + msg.id.mst.id = dev->id; msg.id.mst.pid = dev->kpid; msg.type = W1_MASTER_REMOVE; @@ -223,5 +240,39 @@ __w1_remove_master_device(dev); } +int w1_add_notify(struct w1_notify_ops *ops) +{ + struct w1_master *master; + + if (notify_count >= MAX_NOTIFY_OPS) + return -1; + + notify_ops[notify_count++] = ops; + + /* loop through all masters */ + if (ops->notify_add) { + list_for_each_entry(master, &w1_masters, w1_master_entry) { + ops->notify_add(master); + } + } + + return 0; +} + +int w1_remove_notify(struct w1_notify_ops *ops) +{ + int i; + for (i=0; i < notify_count; i++) { + if (notify_ops[i] == ops) { + notify_count--; + notify_ops[i] = notify_ops[notify_count]; + return 0; + } + } + return -1; +} + +EXPORT_SYMBOL(w1_add_notify); +EXPORT_SYMBOL(w1_remove_notify); EXPORT_SYMBOL(w1_add_master_device); EXPORT_SYMBOL(w1_remove_master_device); --- linux-2.6.12-rc5-mm2-w1/drivers/w1/w1_dev.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-mm2/drivers/w1/w1_dev.c 2005-06-14 12:41:10.000000000 -0500 @@ -0,0 +1,536 @@ +/* + * w1_dev.c - a script-like character interface + * + * Copyright (c) 2005 Ben Gardner <bgardner at wabtec.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include <asm/atomic.h> + +#include <linux/types.h> +#include <linux/ctype.h> +#include <linux/fs.h> +#include <linux/cdev.h> + +#include "w1_int.h" +#include "w1_io.h" + + +#define W1_CHAR_DEV_COUNT 16 +#define W1_RAW_DATA_SIZE 2048 + + +static int major = 0; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Set the chrdev major number"); + +struct w1_raw_dev { + struct w1_master *master; + + struct semaphore raw_lock; + + dev_t dev_id; + struct cdev cdev; + + char *data; + int data_len; +}; + +/* Protects w1_devs, below */ +static DECLARE_MUTEX(w1_devs_lock); +static struct w1_raw_dev *w1_devs[W1_CHAR_DEV_COUNT]; + +static dev_t w1_dev; + + +/** Adds a char to the script buffer */ +static void w1_script_add_char(struct w1_raw_dev *rd, char ch) +{ + if (rd->data_len < (W1_RAW_DATA_SIZE - 1)) { + rd->data[rd->data_len++] = ch; + } +} + +/** Adds two hex digits to the script buffer */ +static void w1_script_add_hexbyte(struct w1_raw_dev *rd, u8 byte) +{ + if (rd->data_len < (W1_RAW_DATA_SIZE - 2)) { + rd->data_len += sprintf(&rd->data[rd->data_len], "%02X", byte); + } +} + +/** Adds a decimal number (0-255) to the script buffer */ +static void w1_script_add_decbyte(struct w1_raw_dev *rd, u8 byte) +{ + if (rd->data_len < (W1_RAW_DATA_SIZE - 3)) { + rd->data_len += sprintf(&rd->data[rd->data_len], "%u", byte); + } +} + +/** Converts a hex character into a number (0-16) */ +static u8 hexchar_to_byte(char ch) +{ + u8 val = 0; + if (isxdigit(ch)) { + val = (ch <= '9') ? ch - '0' : + (ch > 'F') ? ch - ('a' - 10) : ch - ('A' - 10); + } + return val; +} + +/** Converts two hex characters into a number */ +static inline u8 make_byte(char hi, char lo) +{ + return (hexchar_to_byte(hi) << 4) | hexchar_to_byte(lo); +} + +/** + * Executes a simple w1 script. + * The ouput is put in the master structure. + * The master semaphore should be held. + * + * Commands: + * s = reSet "S" + * r = Read bytes "R3" + * w = Write bytes "W2,1234" + * t = Touch bit "T0110" + * p = Pause (ms) "P5" + * v = Verify read "V2,1234" + * + * The output is a more-or-less copy of the input. + * Here's an example for the DS2433 - writing two bytes (1234) at offset 0026. + * "S W6,CC0F26001234 S W2,CCAA V5,2600071234 S W5,CC55260007 P5 S" + * + * Successful output would match the input: + * "S W6,CC0F26001234 S W2,CCAA V5,2600071234 S W5,CC55260007 P5 S" + * + * On failure, a * is placed after the failed command and the rest is omitted. + * Output if the verify failed: + * "S W6,CC0F26001234 S W2,CCAA V5,26000F1234*" + * + * Output if no devices were present: + * "S*" + * + * Read example: + * In: "S W4,CCF00000 R10 S" + * Out: "S W4,CCF00000 R10,00112233445566778899 S" + * + * @param rd The raw dev + * @param buf The buffer to process + * @param len The number of characters in the buffer + * @return -1=error encountered, 0=all successful + */ +static int w1_exec_script(struct w1_raw_dev *rd, const char *buf, size_t len) +{ + int ret = 0; + unsigned int uret; + int ch; + const char *endptr = &buf[len]; + char *newptr; + u8 val, valread; + + rd->data_len = 0; + while ((buf < endptr) && (*buf != 0)) { + ch = toupper(*buf); + buf++; + w1_script_add_char(rd, ch); + if (isspace(ch)) + continue; + + switch (ch) { + case 'S': /* reSet : no additional data */ + if (w1_reset_bus(rd->master)) + goto error_out; + break; + + case 'T': /* Touch bit : one bit per char */ + while ((*buf == '0') || (*buf == '1')) { + ret = w1_touch_bit(rd->master, *buf == '1'); + w1_script_add_char(rd, ret ? '1' : '0'); + buf++; + } + break; + + case 'P': /* Pause : one decimal uint */ + uret = simple_strtoul(buf, &newptr, 0); + buf = newptr; + w1_script_add_decbyte(rd, uret); + msleep_interruptible(uret); + break; + + case 'R': /* Read : one decimal uint */ + uret = simple_strtoul(buf, &newptr, 0); + buf = newptr; + w1_script_add_decbyte(rd, uret); + w1_script_add_char(rd, ','); + while (uret-- > 0) + w1_script_add_hexbyte(rd, + w1_read_8(rd->master)); + break; + + case 'W': /* Write : one decimal uint, hex bytes */ + case 'V': /* Validate : one decimal uint, hex bytes */ + uret = simple_strtoul(buf, &newptr, 0); + buf = newptr; + w1_script_add_decbyte(rd, uret); + w1_script_add_char(rd, *buf); + if (*buf != ',') + goto error_out; + buf++; + while ((uret-- > 0) && (buf < (endptr - 1))) { + val = make_byte(buf[0], buf[1]); + buf += 2; + if (ch == 'W') { + w1_script_add_hexbyte(rd, val); + w1_write_8(rd->master, val); + } else { + valread = w1_read_8(rd->master); + w1_script_add_hexbyte(rd, valread); + if (valread != val) + goto error_out; + } + } + break; + + default: + goto error_out; + } + } + return 0; + +error_out: + w1_script_add_char(rd, '*'); + return -1; +} + +static ssize_t w1_dev_fop_write(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct w1_raw_dev *rd; + u8 mycopy[W1_RAW_DATA_SIZE]; + int idx; + + if (len > (W1_RAW_DATA_SIZE - 1)) + return -EINVAL; + + idx = (int)file->private_data; + + printk(KERN_WARNING "w1_raw: write(%d)\n", idx); + + if ((idx < 0) || (idx >= W1_CHAR_DEV_COUNT) || + ((rd = w1_devs[idx]) == NULL)) + return -ENODEV; + + if (copy_from_user(mycopy, buf, len)) + return -EFAULT; + + /* Ensure termination */ + mycopy[len] = 0; + + if (rd->master == NULL) { + printk(KERN_WARNING "w1_raw: write(%d) rd->master is NULL\n", + idx); + return -EFAULT; + } + + /* Lock the raw entry so it doesn't go away */ + if (down_interruptible(&rd->raw_lock)) + return -EAGAIN; + + /* Lock master to prevent a search in the middle of the script */ + if (down_interruptible(&rd->master->mutex)) { + len = -EAGAIN; + goto exit_up1; + } + + /* Reset the read size */ + if (rd->data == NULL) { + if (!(rd->data = kmalloc(W1_RAW_DATA_SIZE, GFP_KERNEL))) { + len = -ENOMEM; + goto exit_up2; + } + memset(rd->data, 0, W1_RAW_DATA_SIZE); + } + w1_exec_script(rd, mycopy, len); + + /* write always resets the position */ + *ppos = 0; + +exit_up2: + up(&rd->master->mutex); +exit_up1: + up(&rd->raw_lock); + + return len; +} + +/** + * Reads the results from the last script executed. + */ +static ssize_t w1_dev_fop_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned i = *ppos; + struct w1_raw_dev *rd; + + int idx; + ssize_t len = 0; + + idx = (int)file->private_data; + + printk(KERN_WARNING "w1_raw: read(%d)\n", idx); + + if ((idx < 0) || (idx >= W1_CHAR_DEV_COUNT) || + ((rd = w1_devs[idx]) == NULL)) + return -ENODEV; + + /* Lock the interface */ + if (down_interruptible(&rd->raw_lock)) + return -EAGAIN; + + if (rd->data != NULL) { + if (i > rd->data_len) { + len = 0; + } else { + len = rd->data_len - i; + if (len > count) { + len = count; + } + } + + if (copy_to_user(buf, &rd->data[i], len)) { + len = -EFAULT; + } else { + i += len; + } + } + up(&rd->raw_lock); + + *ppos = i; + + return len; +} + +/** + * Sets private_data to the w1_master structure index + * TODO: support exclusive opens? + */ +static int w1_dev_fop_open(struct inode *inode, struct file *file) +{ + int dev_idx; + + if (inode == NULL) { + printk(KERN_ERR "w1_dev_fop_open() inode is NULL\n"); + return -ENODEV; + } + + if (file == NULL) { + printk(KERN_ERR "w1_dev_fop_open() file is NULL\n"); + return -ENODEV; + } + + if (inode->i_cdev == NULL) { + printk(KERN_ERR "w1_dev_fop_open() inode->i_cdev is NULL\n"); + return -ENODEV; + } + + dev_idx = iminor(inode); + if ((dev_idx >= W1_CHAR_DEV_COUNT) || (w1_devs[dev_idx] == NULL)) { + printk(KERN_ERR "w1_dev_fop_open() bad dev_idx %d\n", dev_idx); + return -ENODEV; + } + + printk(KERN_WARNING "w1_raw: open(%d)\n", dev_idx); + + file->private_data = (void *)dev_idx; + return 0; +} + +static struct file_operations w1_dev_fileops = { + .open = w1_dev_fop_open, + .read = w1_dev_fop_read, + .write = w1_dev_fop_write, +}; + + +ssize_t w1_master_attribute_show_dev(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w1_master *md = container_of(dev, struct w1_master, dev); + int i; + + for (i = 0; i < W1_CHAR_DEV_COUNT; i++) { + if ((w1_devs[i] != NULL) && (w1_devs[i]->master == md)) + return print_dev_t(buf, w1_devs[i]->dev_id); + } + return 0; +} + +static DEVICE_ATTR(dev, S_IRUGO, w1_master_attribute_show_dev, NULL); + + +/** + * Registers a master with the w1_raw module. + * + * @param master The w1_master structure + */ +void w1_raw_register(struct w1_master *master) +{ + int i; + int free_idx = -1; + struct w1_raw_dev *raw_dev = NULL; + + down(&w1_devs_lock); + + /* See if this master is already registered */ + for (i = 0; i < W1_CHAR_DEV_COUNT; i++) { + if ((w1_devs[i] != NULL) && (w1_devs[i]->master == master)) { + printk(KERN_INFO "w1_raw_register(%d): duplicate\n", + master->id); + goto exit_up; + } + } + for (free_idx = 0; free_idx < W1_CHAR_DEV_COUNT; free_idx++) { + if (!w1_devs[free_idx]) + break; + } + if (free_idx >= W1_CHAR_DEV_COUNT) + goto exit_up; + + printk(KERN_WARNING "w1_raw_register() free_idx=%d\n", free_idx); + + if (!(raw_dev = kmalloc(sizeof(struct w1_raw_dev), GFP_KERNEL))) + goto exit_up; + + memset(raw_dev, 0, sizeof(struct w1_raw_dev)); + + raw_dev->master = master; + raw_dev->dev_id = MKDEV(MAJOR(w1_dev), free_idx); + init_MUTEX(&raw_dev->raw_lock); + + device_create_file(&master->dev, &dev_attr_dev); + cdev_init(&raw_dev->cdev, &w1_dev_fileops); + w1_devs[free_idx] = raw_dev; + + cdev_add(&raw_dev->cdev, raw_dev->dev_id, 1); + + printk(KERN_NOTICE "w1_raw_register(%d) dev=%d:%d\n", + master->id, MAJOR(raw_dev->dev_id), MINOR(raw_dev->dev_id)); + +exit_up: + up(&w1_devs_lock); +} + + +static void w1_raw_free(struct w1_raw_dev *rd) +{ + down(&rd->raw_lock); + + device_remove_file(&rd->master->dev, &dev_attr_dev); + cdev_del(&rd->cdev); + + if (rd->data != NULL) { + kfree(rd->data); + } + kfree(rd->data); +} + +/** + * Unregisters a master with the w1_raw module. + * + * @param master The w1_master structure + */ +void w1_raw_unregister(struct w1_master *master) +{ + int i; + + down(&w1_devs_lock); + + /* Find the master & remove it */ + for (i = 0; i < W1_CHAR_DEV_COUNT; i++) { + if ((w1_devs[i] != NULL) && (w1_devs[i]->master == master)) { + struct w1_raw_dev *rd = w1_devs[i]; + w1_devs[i] = NULL; + + printk(KERN_NOTICE "w1_raw_unregister(%d) dev=%d:%d\n", + master->id, + MAJOR(rd->dev_id), MINOR(rd->dev_id)); + + w1_raw_free(rd); + break; + } + } + + up(&w1_devs_lock); +} + +static struct w1_notify_ops raw_notify_ops = { + .notify_add = w1_raw_register, + .notify_remove = w1_raw_unregister, +}; + + +static int w1_raw_init(void) +{ + int retval; + + printk(KERN_INFO "w1 raw char driver\n"); + + if (major) { + w1_dev = MKDEV(major, 0); + retval = register_chrdev_region(w1_dev, W1_CHAR_DEV_COUNT, + "w1_dev"); + } else { + retval = alloc_chrdev_region(&w1_dev, 0, W1_CHAR_DEV_COUNT, + "w1_dev"); + major = MAJOR(w1_dev); + } + + if (retval >= 0) { + w1_add_notify(&raw_notify_ops); + } + + return retval; +} + +static void w1_raw_fini(void) +{ + int i; + + w1_remove_notify(&raw_notify_ops); + + /* Unregister all devices */ + down(&w1_devs_lock); + for (i = 0; i < W1_CHAR_DEV_COUNT; i++) { + if (w1_devs[i] != NULL) { + struct w1_raw_dev *rd = w1_devs[i]; + w1_devs[i] = NULL; + w1_raw_free(rd); + } + } + up(&w1_devs_lock); + + unregister_chrdev_region(w1_dev, W1_CHAR_DEV_COUNT); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ben Gardner <bgardner at wabtec.com>"); +MODULE_DESCRIPTION("dev interface for w1 bus master"); + +module_init(w1_raw_init); +module_exit(w1_raw_fini); --- linux-2.6.12-rc5-mm2-w1/Documentation/w1/w1.dev 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-mm2/Documentation/w1/w1.dev 2005-06-09 13:56:16.000000000 -0500 @@ -0,0 +1,103 @@ +dev interface for 1-wire (w1) masters +------------------------------------------------------------------ +The w1_dev module adds a simple script-like character interface for each +w1 master present in the system (up to 16). +This is useful to tinker with a w1 slave device without touching kernel code. + +It adds a "dev" entry to each w1_bus_masterN entry in sysfs. +Example: +/sys/devices/w1_bus_master1/dev + +Commands are executed by writing to the character device. +The result of a command is retained until a new command is executed. + +There are 6 basic commands, which are case-insensitive: + s = reSet + r = Read bytes + w = Write bytes + t = Touch bit + p = Pause (ms) + v = Verify read + +The output is a copy of the input, except for the T and R commands. +If a command fails, a '*' is placed in the output buffer at the point of +failure and execution terminates. + + +Reset "s" +-------------------------------- +Resets the bus. +This fails if there is not a device present on the w1 bus. + + +Read bytes "rN" +-------------------------------- +Reads N (decimal) bytes from the bus. +Output format "rN,XXXX", where XX is N*2 hexidecimal characters. + + +Write bytes "wN,XX" +-------------------------------- +Writes N (decimal) bytes to the bus. +XX is the N*2 hexidecimal characters to write to the bus. + + +Touch bit "tBBB" +-------------------------------- +Performs a series of touch bit commands. +Each B is a 1 or 0. +In the output, a 1 may change to a 0 if that is the value read. + + +Pause "pN" +-------------------------------- +Pauses for N milliseconds. + + +Verify read "vN,XX" +-------------------------------- +Reads N (decimal) bytes from the bus and compares against the given bytes. +XX is the N*2 hexidecimal characters that are expected to be read from the bus. + + +Real-world examples - MAXIM DS2433 (family 23) 512 byte EEPROM +---------------------------------------------------------------- +Assume $dev is set to the char device node you created for the w1 master. +Commands are written using echo, and results are read with cat. +Scan the result string for a '*' to see if the command failed. + +This example assumes that the DS2433 is the only device on the w1 bus. +If there are multiple devices on the w1 bus, you can't use the SkipROM (0xCC) +command to address the device. Rather, you'd have to address it using the +MatchROM (0x55) command plus the 8 byte ROM address. + +Other commands for the DS2433, used in the examples below: + 0x0F - write scratch + 0xAA - read scratch + 0x55 - copy scratch to EEPROM + 0xF0 - read EEPROM + + +Example 1 - write to EEPROM +-------------------------------- +To write to the DS2433, you must write to a scratch buffer, verify the +content of the scratch buffer and then send a copy scratch command. +See the datasheet for details. + +This string will write two bytes (1234) at offset 0026. +Write: "S W6,CC0F26001234 S W2,CCAA V5,2600071234 S W5,CC55260007 P5 S" +Read: "S W6,CC0F26001234 S W2,CCAA V5,2600071234 S W5,CC55260007 P5 S" + +If no devices were present on the bus, you'd get this output: +Read: "S*" + +If the verify failed on the second byte, you'd get: +Read: "S W6,CC0F26001234 S W2,CCAA V5,2600*" + + +Example 2 - read from EEPROM +-------------------------------- +This string will read 10 bytes from offset 0x123. +Write: "S W4,CCF02301 R10 S" +Read: "S W4,CCF02301 R10,00112233445566778899 S" +