diff options
Diffstat (limited to 'drivers/firewire/core-device.c')
-rw-r--r-- | drivers/firewire/core-device.c | 302 |
1 files changed, 172 insertions, 130 deletions
diff --git a/drivers/firewire/core-device.c b/drivers/firewire/core-device.c index 9d0dfcbe2c1..882472d1e14 100644 --- a/drivers/firewire/core-device.c +++ b/drivers/firewire/core-device.c @@ -18,6 +18,7 @@ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include <linux/bug.h> #include <linux/ctype.h> #include <linux/delay.h> #include <linux/device.h> @@ -32,7 +33,6 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/rwsem.h> -#include <linux/semaphore.h> #include <linux/spinlock.h> #include <linux/string.h> #include <linux/workqueue.h> @@ -43,7 +43,7 @@ #include "core.h" -void fw_csr_iterator_init(struct fw_csr_iterator *ci, u32 * p) +void fw_csr_iterator_init(struct fw_csr_iterator *ci, const u32 *p) { ci->p = p + 1; ci->end = ci->p + (p[0] >> 16); @@ -59,97 +59,141 @@ int fw_csr_iterator_next(struct fw_csr_iterator *ci, int *key, int *value) } EXPORT_SYMBOL(fw_csr_iterator_next); -static bool is_fw_unit(struct device *dev); - -static int match_unit_directory(u32 *directory, u32 match_flags, - const struct ieee1394_device_id *id) +static const u32 *search_leaf(const u32 *directory, int search_key) { struct fw_csr_iterator ci; - int key, value, match; + int last_key = 0, key, value; - match = 0; fw_csr_iterator_init(&ci, directory); while (fw_csr_iterator_next(&ci, &key, &value)) { - if (key == CSR_VENDOR && value == id->vendor_id) - match |= IEEE1394_MATCH_VENDOR_ID; - if (key == CSR_MODEL && value == id->model_id) - match |= IEEE1394_MATCH_MODEL_ID; - if (key == CSR_SPECIFIER_ID && value == id->specifier_id) - match |= IEEE1394_MATCH_SPECIFIER_ID; - if (key == CSR_VERSION && value == id->version) - match |= IEEE1394_MATCH_VERSION; + if (last_key == search_key && + key == (CSR_DESCRIPTOR | CSR_LEAF)) + return ci.p - 1 + value; + + last_key = key; } - return (match & match_flags) == match_flags; + return NULL; } -static int fw_unit_match(struct device *dev, struct device_driver *drv) +static int textual_leaf_to_string(const u32 *block, char *buf, size_t size) { - struct fw_unit *unit = fw_unit(dev); - struct fw_device *device; - const struct ieee1394_device_id *id; - - /* We only allow binding to fw_units. */ - if (!is_fw_unit(dev)) - return 0; + unsigned int quadlets, i; + char c; - device = fw_parent_device(unit); - id = container_of(drv, struct fw_driver, driver)->id_table; + if (!size || !buf) + return -EINVAL; - for (; id->match_flags != 0; id++) { - if (match_unit_directory(unit->directory, id->match_flags, id)) - return 1; + quadlets = min(block[0] >> 16, 256U); + if (quadlets < 2) + return -ENODATA; - /* Also check vendor ID in the root directory. */ - if ((id->match_flags & IEEE1394_MATCH_VENDOR_ID) && - match_unit_directory(&device->config_rom[5], - IEEE1394_MATCH_VENDOR_ID, id) && - match_unit_directory(unit->directory, id->match_flags - & ~IEEE1394_MATCH_VENDOR_ID, id)) - return 1; + if (block[1] != 0 || block[2] != 0) + /* unknown language/character set */ + return -ENODATA; + + block += 3; + quadlets -= 2; + for (i = 0; i < quadlets * 4 && i < size - 1; i++) { + c = block[i / 4] >> (24 - 8 * (i % 4)); + if (c == '\0') + break; + buf[i] = c; } + buf[i] = '\0'; - return 0; + return i; } -static int get_modalias(struct fw_unit *unit, char *buffer, size_t buffer_size) +/** + * fw_csr_string - reads a string from the configuration ROM + * @directory: e.g. root directory or unit directory + * @key: the key of the preceding directory entry + * @buf: where to put the string + * @size: size of @buf, in bytes + * + * The string is taken from a minimal ASCII text descriptor leaf after + * the immediate entry with @key. The string is zero-terminated. + * Returns strlen(buf) or a negative error code. + */ +int fw_csr_string(const u32 *directory, int key, char *buf, size_t size) { - struct fw_device *device = fw_parent_device(unit); - struct fw_csr_iterator ci; + const u32 *leaf = search_leaf(directory, key); + if (!leaf) + return -ENOENT; + + return textual_leaf_to_string(leaf, buf, size); +} +EXPORT_SYMBOL(fw_csr_string); +static void get_ids(const u32 *directory, int *id) +{ + struct fw_csr_iterator ci; int key, value; - int vendor = 0; - int model = 0; - int specifier_id = 0; - int version = 0; - fw_csr_iterator_init(&ci, &device->config_rom[5]); + fw_csr_iterator_init(&ci, directory); while (fw_csr_iterator_next(&ci, &key, &value)) { switch (key) { - case CSR_VENDOR: - vendor = value; - break; - case CSR_MODEL: - model = value; - break; + case CSR_VENDOR: id[0] = value; break; + case CSR_MODEL: id[1] = value; break; + case CSR_SPECIFIER_ID: id[2] = value; break; + case CSR_VERSION: id[3] = value; break; } } +} - fw_csr_iterator_init(&ci, unit->directory); - while (fw_csr_iterator_next(&ci, &key, &value)) { - switch (key) { - case CSR_SPECIFIER_ID: - specifier_id = value; - break; - case CSR_VERSION: - version = value; - break; - } - } +static void get_modalias_ids(struct fw_unit *unit, int *id) +{ + get_ids(&fw_parent_device(unit)->config_rom[5], id); + get_ids(unit->directory, id); +} + +static bool match_ids(const struct ieee1394_device_id *id_table, int *id) +{ + int match = 0; + + if (id[0] == id_table->vendor_id) + match |= IEEE1394_MATCH_VENDOR_ID; + if (id[1] == id_table->model_id) + match |= IEEE1394_MATCH_MODEL_ID; + if (id[2] == id_table->specifier_id) + match |= IEEE1394_MATCH_SPECIFIER_ID; + if (id[3] == id_table->version) + match |= IEEE1394_MATCH_VERSION; + + return (match & id_table->match_flags) == id_table->match_flags; +} + +static bool is_fw_unit(struct device *dev); + +static int fw_unit_match(struct device *dev, struct device_driver *drv) +{ + const struct ieee1394_device_id *id_table = + container_of(drv, struct fw_driver, driver)->id_table; + int id[] = {0, 0, 0, 0}; + + /* We only allow binding to fw_units. */ + if (!is_fw_unit(dev)) + return 0; + + get_modalias_ids(fw_unit(dev), id); + + for (; id_table->match_flags != 0; id_table++) + if (match_ids(id_table, id)) + return 1; + + return 0; +} + +static int get_modalias(struct fw_unit *unit, char *buffer, size_t buffer_size) +{ + int id[] = {0, 0, 0, 0}; + + get_modalias_ids(unit, id); return snprintf(buffer, buffer_size, "ieee1394:ven%08Xmo%08Xsp%08Xver%08X", - vendor, model, specifier_id, version); + id[0], id[1], id[2], id[3]); } static int fw_unit_uevent(struct device *dev, struct kobj_uevent_env *env) @@ -195,7 +239,7 @@ static ssize_t show_immediate(struct device *dev, struct config_rom_attribute *attr = container_of(dattr, struct config_rom_attribute, attr); struct fw_csr_iterator ci; - u32 *dir; + const u32 *dir; int key, value, ret = -ENOENT; down_read(&fw_device_rwsem); @@ -226,10 +270,10 @@ static ssize_t show_text_leaf(struct device *dev, { struct config_rom_attribute *attr = container_of(dattr, struct config_rom_attribute, attr); - struct fw_csr_iterator ci; - u32 *dir, *block = NULL, *p, *end; - int length, key, value, last_key = 0, ret = -ENOENT; - char *b; + const u32 *dir; + size_t bufsize; + char dummy_buf[2]; + int ret; down_read(&fw_device_rwsem); @@ -238,40 +282,23 @@ static ssize_t show_text_leaf(struct device *dev, else dir = fw_device(dev)->config_rom + 5; - fw_csr_iterator_init(&ci, dir); - while (fw_csr_iterator_next(&ci, &key, &value)) { - if (attr->key == last_key && - key == (CSR_DESCRIPTOR | CSR_LEAF)) - block = ci.p - 1 + value; - last_key = key; + if (buf) { + bufsize = PAGE_SIZE - 1; + } else { + buf = dummy_buf; + bufsize = 1; } - if (block == NULL) - goto out; - - length = min(block[0] >> 16, 256U); - if (length < 3) - goto out; - - if (block[1] != 0 || block[2] != 0) - /* Unknown encoding. */ - goto out; + ret = fw_csr_string(dir, attr->key, buf, bufsize); - if (buf == NULL) { - ret = length * 4; - goto out; + if (ret >= 0) { + /* Strip trailing whitespace and add newline. */ + while (ret > 0 && isspace(buf[ret - 1])) + ret--; + strcpy(buf + ret, "\n"); + ret++; } - b = buf; - end = &block[length + 1]; - for (p = &block[3]; p < end; p++, b += 4) - * (u32 *) b = (__force u32) __cpu_to_be32(*p); - - /* Strip trailing whitespace and add newline. */ - while (b--, (isspace(*b) || *b == '\0') && b > buf); - strcpy(b + 1, "\n"); - ret = b + 2 - buf; - out: up_read(&fw_device_rwsem); return ret; @@ -371,7 +398,7 @@ static ssize_t guid_show(struct device *dev, return ret; } -static int units_sprintf(char *buf, u32 *directory) +static int units_sprintf(char *buf, const u32 *directory) { struct fw_csr_iterator ci; int key, value; @@ -441,28 +468,29 @@ static int read_rom(struct fw_device *device, return rcode; } -#define READ_BIB_ROM_SIZE 256 -#define READ_BIB_STACK_SIZE 16 +#define MAX_CONFIG_ROM_SIZE 256 /* * Read the bus info block, perform a speed probe, and read all of the rest of * the config ROM. We do all this with a cached bus generation. If the bus - * generation changes under us, read_bus_info_block will fail and get retried. + * generation changes under us, read_config_rom will fail and get retried. * It's better to start all over in this case because the node from which we * are reading the ROM may have changed the ROM during the reset. */ -static int read_bus_info_block(struct fw_device *device, int generation) +static int read_config_rom(struct fw_device *device, int generation) { - u32 *rom, *stack, *old_rom, *new_rom; + const u32 *old_rom, *new_rom; + u32 *rom, *stack; u32 sp, key; int i, end, length, ret = -1; - rom = kmalloc(sizeof(*rom) * READ_BIB_ROM_SIZE + - sizeof(*stack) * READ_BIB_STACK_SIZE, GFP_KERNEL); + rom = kmalloc(sizeof(*rom) * MAX_CONFIG_ROM_SIZE + + sizeof(*stack) * MAX_CONFIG_ROM_SIZE, GFP_KERNEL); if (rom == NULL) return -ENOMEM; - stack = &rom[READ_BIB_ROM_SIZE]; + stack = &rom[MAX_CONFIG_ROM_SIZE]; + memset(rom, 0, sizeof(*rom) * MAX_CONFIG_ROM_SIZE); device->max_speed = SCODE_100; @@ -529,40 +557,54 @@ static int read_bus_info_block(struct fw_device *device, int generation) */ key = stack[--sp]; i = key & 0xffffff; - if (i >= READ_BIB_ROM_SIZE) - /* - * The reference points outside the standard - * config rom area, something's fishy. - */ + if (WARN_ON(i >= MAX_CONFIG_ROM_SIZE)) goto out; /* Read header quadlet for the block to get the length. */ if (read_rom(device, generation, i, &rom[i]) != RCODE_COMPLETE) goto out; end = i + (rom[i] >> 16) + 1; - i++; - if (end > READ_BIB_ROM_SIZE) + if (end > MAX_CONFIG_ROM_SIZE) { /* - * This block extends outside standard config - * area (and the array we're reading it - * into). That's broken, so ignore this - * device. + * This block extends outside the config ROM which is + * a firmware bug. Ignore this whole block, i.e. + * simply set a fake block length of 0. */ - goto out; + fw_error("skipped invalid ROM block %x at %llx\n", + rom[i], + i * 4 | CSR_REGISTER_BASE | CSR_CONFIG_ROM); + rom[i] = 0; + end = i; + } + i++; /* * Now read in the block. If this is a directory * block, check the entries as we read them to see if * it references another block, and push it in that case. */ - while (i < end) { + for (; i < end; i++) { if (read_rom(device, generation, i, &rom[i]) != RCODE_COMPLETE) goto out; - if ((key >> 30) == 3 && (rom[i] >> 30) > 1 && - sp < READ_BIB_STACK_SIZE) - stack[sp++] = i + rom[i]; - i++; + + if ((key >> 30) != 3 || (rom[i] >> 30) < 2) + continue; + /* + * Offset points outside the ROM. May be a firmware + * bug or an Extended ROM entry (IEEE 1212-2001 clause + * 7.7.18). Simply overwrite this pointer here by a + * fake immediate entry so that later iterators over + * the ROM don't have to check offsets all the time. + */ + if (i + (rom[i] & 0xffffff) >= MAX_CONFIG_ROM_SIZE) { + fw_error("skipped unsupported ROM entry %x at %llx\n", + rom[i], + i * 4 | CSR_REGISTER_BASE | CSR_CONFIG_ROM); + rom[i] = 0; + continue; + } + stack[sp++] = i + rom[i]; } if (length < i) length = i; @@ -762,9 +804,9 @@ static int update_unit(struct device *dev, void *data) struct fw_driver *driver = (struct fw_driver *)dev->driver; if (is_fw_unit(dev) && driver != NULL && driver->update != NULL) { - down(&dev->sem); + device_lock(dev); driver->update(unit); - up(&dev->sem); + device_unlock(dev); } return 0; @@ -905,7 +947,7 @@ static void fw_device_init(struct work_struct *work) * device. */ - if (read_bus_info_block(device, device->generation) < 0) { + if (read_config_rom(device, device->generation) < 0) { if (device->config_rom_retries < MAX_RETRIES && atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { device->config_rom_retries++; @@ -1022,7 +1064,7 @@ enum { }; /* Reread and compare bus info block and header of root directory */ -static int reread_bus_info_block(struct fw_device *device, int generation) +static int reread_config_rom(struct fw_device *device, int generation) { u32 q; int i; @@ -1048,7 +1090,7 @@ static void fw_device_refresh(struct work_struct *work) struct fw_card *card = device->card; int node_id = device->node_id; - switch (reread_bus_info_block(device, device->generation)) { + switch (reread_config_rom(device, device->generation)) { case REREAD_BIB_ERROR: if (device->config_rom_retries < MAX_RETRIES / 2 && atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { @@ -1082,7 +1124,7 @@ static void fw_device_refresh(struct work_struct *work) */ device_for_each_child(&device->device, NULL, shutdown_unit); - if (read_bus_info_block(device, device->generation) < 0) { + if (read_config_rom(device, device->generation) < 0) { if (device->config_rom_retries < MAX_RETRIES && atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { device->config_rom_retries++; |