|
@@ -25,7 +25,7 @@
|
|
#include <linux/device.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/idr.h>
|
|
-#include <linux/rwsem.h>
|
|
|
|
|
|
+#include <linux/string.h>
|
|
#include <asm/semaphore.h>
|
|
#include <asm/semaphore.h>
|
|
#include <asm/system.h>
|
|
#include <asm/system.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/ctype.h>
|
|
@@ -160,9 +160,9 @@ static void fw_device_release(struct device *dev)
|
|
* Take the card lock so we don't set this to NULL while a
|
|
* Take the card lock so we don't set this to NULL while a
|
|
* FW_NODE_UPDATED callback is being handled.
|
|
* FW_NODE_UPDATED callback is being handled.
|
|
*/
|
|
*/
|
|
- spin_lock_irqsave(&device->card->lock, flags);
|
|
|
|
|
|
+ spin_lock_irqsave(&card->lock, flags);
|
|
device->node->data = NULL;
|
|
device->node->data = NULL;
|
|
- spin_unlock_irqrestore(&device->card->lock, flags);
|
|
|
|
|
|
+ spin_unlock_irqrestore(&card->lock, flags);
|
|
|
|
|
|
fw_node_put(device->node);
|
|
fw_node_put(device->node);
|
|
kfree(device->config_rom);
|
|
kfree(device->config_rom);
|
|
@@ -195,7 +195,9 @@ show_immediate(struct device *dev, struct device_attribute *dattr, char *buf)
|
|
container_of(dattr, struct config_rom_attribute, attr);
|
|
container_of(dattr, struct config_rom_attribute, attr);
|
|
struct fw_csr_iterator ci;
|
|
struct fw_csr_iterator ci;
|
|
u32 *dir;
|
|
u32 *dir;
|
|
- int key, value;
|
|
|
|
|
|
+ int key, value, ret = -ENOENT;
|
|
|
|
+
|
|
|
|
+ down_read(&fw_device_rwsem);
|
|
|
|
|
|
if (is_fw_unit(dev))
|
|
if (is_fw_unit(dev))
|
|
dir = fw_unit(dev)->directory;
|
|
dir = fw_unit(dev)->directory;
|
|
@@ -204,11 +206,15 @@ show_immediate(struct device *dev, struct device_attribute *dattr, char *buf)
|
|
|
|
|
|
fw_csr_iterator_init(&ci, dir);
|
|
fw_csr_iterator_init(&ci, dir);
|
|
while (fw_csr_iterator_next(&ci, &key, &value))
|
|
while (fw_csr_iterator_next(&ci, &key, &value))
|
|
- if (attr->key == key)
|
|
|
|
- return snprintf(buf, buf ? PAGE_SIZE : 0,
|
|
|
|
- "0x%06x\n", value);
|
|
|
|
|
|
+ if (attr->key == key) {
|
|
|
|
+ ret = snprintf(buf, buf ? PAGE_SIZE : 0,
|
|
|
|
+ "0x%06x\n", value);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ up_read(&fw_device_rwsem);
|
|
|
|
|
|
- return -ENOENT;
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
#define IMMEDIATE_ATTR(name, key) \
|
|
#define IMMEDIATE_ATTR(name, key) \
|
|
@@ -221,9 +227,11 @@ show_text_leaf(struct device *dev, struct device_attribute *dattr, char *buf)
|
|
container_of(dattr, struct config_rom_attribute, attr);
|
|
container_of(dattr, struct config_rom_attribute, attr);
|
|
struct fw_csr_iterator ci;
|
|
struct fw_csr_iterator ci;
|
|
u32 *dir, *block = NULL, *p, *end;
|
|
u32 *dir, *block = NULL, *p, *end;
|
|
- int length, key, value, last_key = 0;
|
|
|
|
|
|
+ int length, key, value, last_key = 0, ret = -ENOENT;
|
|
char *b;
|
|
char *b;
|
|
|
|
|
|
|
|
+ down_read(&fw_device_rwsem);
|
|
|
|
+
|
|
if (is_fw_unit(dev))
|
|
if (is_fw_unit(dev))
|
|
dir = fw_unit(dev)->directory;
|
|
dir = fw_unit(dev)->directory;
|
|
else
|
|
else
|
|
@@ -238,18 +246,20 @@ show_text_leaf(struct device *dev, struct device_attribute *dattr, char *buf)
|
|
}
|
|
}
|
|
|
|
|
|
if (block == NULL)
|
|
if (block == NULL)
|
|
- return -ENOENT;
|
|
|
|
|
|
+ goto out;
|
|
|
|
|
|
length = min(block[0] >> 16, 256U);
|
|
length = min(block[0] >> 16, 256U);
|
|
if (length < 3)
|
|
if (length < 3)
|
|
- return -ENOENT;
|
|
|
|
|
|
+ goto out;
|
|
|
|
|
|
if (block[1] != 0 || block[2] != 0)
|
|
if (block[1] != 0 || block[2] != 0)
|
|
/* Unknown encoding. */
|
|
/* Unknown encoding. */
|
|
- return -ENOENT;
|
|
|
|
|
|
+ goto out;
|
|
|
|
|
|
- if (buf == NULL)
|
|
|
|
- return length * 4;
|
|
|
|
|
|
+ if (buf == NULL) {
|
|
|
|
+ ret = length * 4;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
|
|
b = buf;
|
|
b = buf;
|
|
end = &block[length + 1];
|
|
end = &block[length + 1];
|
|
@@ -259,8 +269,11 @@ show_text_leaf(struct device *dev, struct device_attribute *dattr, char *buf)
|
|
/* Strip trailing whitespace and add newline. */
|
|
/* Strip trailing whitespace and add newline. */
|
|
while (b--, (isspace(*b) || *b == '\0') && b > buf);
|
|
while (b--, (isspace(*b) || *b == '\0') && b > buf);
|
|
strcpy(b + 1, "\n");
|
|
strcpy(b + 1, "\n");
|
|
|
|
+ ret = b + 2 - buf;
|
|
|
|
+ out:
|
|
|
|
+ up_read(&fw_device_rwsem);
|
|
|
|
|
|
- return b + 2 - buf;
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
#define TEXT_LEAF_ATTR(name, key) \
|
|
#define TEXT_LEAF_ATTR(name, key) \
|
|
@@ -337,19 +350,28 @@ static ssize_t
|
|
config_rom_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
config_rom_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
{
|
|
struct fw_device *device = fw_device(dev);
|
|
struct fw_device *device = fw_device(dev);
|
|
|
|
+ size_t length;
|
|
|
|
|
|
- memcpy(buf, device->config_rom, device->config_rom_length * 4);
|
|
|
|
|
|
+ down_read(&fw_device_rwsem);
|
|
|
|
+ length = device->config_rom_length * 4;
|
|
|
|
+ memcpy(buf, device->config_rom, length);
|
|
|
|
+ up_read(&fw_device_rwsem);
|
|
|
|
|
|
- return device->config_rom_length * 4;
|
|
|
|
|
|
+ return length;
|
|
}
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
static ssize_t
|
|
guid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
guid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
{
|
|
struct fw_device *device = fw_device(dev);
|
|
struct fw_device *device = fw_device(dev);
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ down_read(&fw_device_rwsem);
|
|
|
|
+ ret = snprintf(buf, PAGE_SIZE, "0x%08x%08x\n",
|
|
|
|
+ device->config_rom[3], device->config_rom[4]);
|
|
|
|
+ up_read(&fw_device_rwsem);
|
|
|
|
|
|
- return snprintf(buf, PAGE_SIZE, "0x%08x%08x\n",
|
|
|
|
- device->config_rom[3], device->config_rom[4]);
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
static struct device_attribute fw_device_attributes[] = {
|
|
static struct device_attribute fw_device_attributes[] = {
|
|
@@ -412,7 +434,7 @@ read_rom(struct fw_device *device, int generation, int index, u32 *data)
|
|
*/
|
|
*/
|
|
static int read_bus_info_block(struct fw_device *device, int generation)
|
|
static int read_bus_info_block(struct fw_device *device, int generation)
|
|
{
|
|
{
|
|
- u32 *rom, *stack;
|
|
|
|
|
|
+ u32 *rom, *stack, *old_rom, *new_rom;
|
|
u32 sp, key;
|
|
u32 sp, key;
|
|
int i, end, length, ret = -1;
|
|
int i, end, length, ret = -1;
|
|
|
|
|
|
@@ -527,12 +549,19 @@ static int read_bus_info_block(struct fw_device *device, int generation)
|
|
length = i;
|
|
length = i;
|
|
}
|
|
}
|
|
|
|
|
|
- device->config_rom = kmalloc(length * 4, GFP_KERNEL);
|
|
|
|
- if (device->config_rom == NULL)
|
|
|
|
|
|
+ old_rom = device->config_rom;
|
|
|
|
+ new_rom = kmemdup(rom, length * 4, GFP_KERNEL);
|
|
|
|
+ if (new_rom == NULL)
|
|
goto out;
|
|
goto out;
|
|
- memcpy(device->config_rom, rom, length * 4);
|
|
|
|
|
|
+
|
|
|
|
+ down_write(&fw_device_rwsem);
|
|
|
|
+ device->config_rom = new_rom;
|
|
device->config_rom_length = length;
|
|
device->config_rom_length = length;
|
|
|
|
+ up_write(&fw_device_rwsem);
|
|
|
|
+
|
|
|
|
+ kfree(old_rom);
|
|
ret = 0;
|
|
ret = 0;
|
|
|
|
+ device->cmc = rom[2] & 1 << 30;
|
|
out:
|
|
out:
|
|
kfree(rom);
|
|
kfree(rom);
|
|
|
|
|
|
@@ -605,7 +634,14 @@ static int shutdown_unit(struct device *device, void *data)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-static DECLARE_RWSEM(idr_rwsem);
|
|
|
|
|
|
+/*
|
|
|
|
+ * fw_device_rwsem acts as dual purpose mutex:
|
|
|
|
+ * - serializes accesses to fw_device_idr,
|
|
|
|
+ * - serializes accesses to fw_device.config_rom/.config_rom_length and
|
|
|
|
+ * fw_unit.directory, unless those accesses happen at safe occasions
|
|
|
|
+ */
|
|
|
|
+DECLARE_RWSEM(fw_device_rwsem);
|
|
|
|
+
|
|
static DEFINE_IDR(fw_device_idr);
|
|
static DEFINE_IDR(fw_device_idr);
|
|
int fw_cdev_major;
|
|
int fw_cdev_major;
|
|
|
|
|
|
@@ -613,11 +649,11 @@ struct fw_device *fw_device_get_by_devt(dev_t devt)
|
|
{
|
|
{
|
|
struct fw_device *device;
|
|
struct fw_device *device;
|
|
|
|
|
|
- down_read(&idr_rwsem);
|
|
|
|
|
|
+ down_read(&fw_device_rwsem);
|
|
device = idr_find(&fw_device_idr, MINOR(devt));
|
|
device = idr_find(&fw_device_idr, MINOR(devt));
|
|
if (device)
|
|
if (device)
|
|
fw_device_get(device);
|
|
fw_device_get(device);
|
|
- up_read(&idr_rwsem);
|
|
|
|
|
|
+ up_read(&fw_device_rwsem);
|
|
|
|
|
|
return device;
|
|
return device;
|
|
}
|
|
}
|
|
@@ -632,9 +668,9 @@ static void fw_device_shutdown(struct work_struct *work)
|
|
device_for_each_child(&device->device, NULL, shutdown_unit);
|
|
device_for_each_child(&device->device, NULL, shutdown_unit);
|
|
device_unregister(&device->device);
|
|
device_unregister(&device->device);
|
|
|
|
|
|
- down_write(&idr_rwsem);
|
|
|
|
|
|
+ down_write(&fw_device_rwsem);
|
|
idr_remove(&fw_device_idr, minor);
|
|
idr_remove(&fw_device_idr, minor);
|
|
- up_write(&idr_rwsem);
|
|
|
|
|
|
+ up_write(&fw_device_rwsem);
|
|
fw_device_put(device);
|
|
fw_device_put(device);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -687,10 +723,10 @@ static void fw_device_init(struct work_struct *work)
|
|
err = -ENOMEM;
|
|
err = -ENOMEM;
|
|
|
|
|
|
fw_device_get(device);
|
|
fw_device_get(device);
|
|
- down_write(&idr_rwsem);
|
|
|
|
|
|
+ down_write(&fw_device_rwsem);
|
|
if (idr_pre_get(&fw_device_idr, GFP_KERNEL))
|
|
if (idr_pre_get(&fw_device_idr, GFP_KERNEL))
|
|
err = idr_get_new(&fw_device_idr, device, &minor);
|
|
err = idr_get_new(&fw_device_idr, device, &minor);
|
|
- up_write(&idr_rwsem);
|
|
|
|
|
|
+ up_write(&fw_device_rwsem);
|
|
|
|
|
|
if (err < 0)
|
|
if (err < 0)
|
|
goto error;
|
|
goto error;
|
|
@@ -724,7 +760,7 @@ static void fw_device_init(struct work_struct *work)
|
|
if (atomic_cmpxchg(&device->state,
|
|
if (atomic_cmpxchg(&device->state,
|
|
FW_DEVICE_INITIALIZING,
|
|
FW_DEVICE_INITIALIZING,
|
|
FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) {
|
|
FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) {
|
|
- fw_device_shutdown(&device->work.work);
|
|
|
|
|
|
+ fw_device_shutdown(work);
|
|
} else {
|
|
} else {
|
|
if (device->config_rom_retries)
|
|
if (device->config_rom_retries)
|
|
fw_notify("created device %s: GUID %08x%08x, S%d00, "
|
|
fw_notify("created device %s: GUID %08x%08x, S%d00, "
|
|
@@ -738,6 +774,7 @@ static void fw_device_init(struct work_struct *work)
|
|
device->device.bus_id,
|
|
device->device.bus_id,
|
|
device->config_rom[3], device->config_rom[4],
|
|
device->config_rom[3], device->config_rom[4],
|
|
1 << device->max_speed);
|
|
1 << device->max_speed);
|
|
|
|
+ device->config_rom_retries = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -752,9 +789,9 @@ static void fw_device_init(struct work_struct *work)
|
|
return;
|
|
return;
|
|
|
|
|
|
error_with_cdev:
|
|
error_with_cdev:
|
|
- down_write(&idr_rwsem);
|
|
|
|
|
|
+ down_write(&fw_device_rwsem);
|
|
idr_remove(&fw_device_idr, minor);
|
|
idr_remove(&fw_device_idr, minor);
|
|
- up_write(&idr_rwsem);
|
|
|
|
|
|
+ up_write(&fw_device_rwsem);
|
|
error:
|
|
error:
|
|
fw_device_put(device); /* fw_device_idr's reference */
|
|
fw_device_put(device); /* fw_device_idr's reference */
|
|
|
|
|
|
@@ -784,6 +821,106 @@ static void fw_device_update(struct work_struct *work)
|
|
device_for_each_child(&device->device, NULL, update_unit);
|
|
device_for_each_child(&device->device, NULL, update_unit);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+enum {
|
|
|
|
+ REREAD_BIB_ERROR,
|
|
|
|
+ REREAD_BIB_GONE,
|
|
|
|
+ REREAD_BIB_UNCHANGED,
|
|
|
|
+ REREAD_BIB_CHANGED,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Reread and compare bus info block and header of root directory */
|
|
|
|
+static int reread_bus_info_block(struct fw_device *device, int generation)
|
|
|
|
+{
|
|
|
|
+ u32 q;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < 6; i++) {
|
|
|
|
+ if (read_rom(device, generation, i, &q) != RCODE_COMPLETE)
|
|
|
|
+ return REREAD_BIB_ERROR;
|
|
|
|
+
|
|
|
|
+ if (i == 0 && q == 0)
|
|
|
|
+ return REREAD_BIB_GONE;
|
|
|
|
+
|
|
|
|
+ if (i > device->config_rom_length || q != device->config_rom[i])
|
|
|
|
+ return REREAD_BIB_CHANGED;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return REREAD_BIB_UNCHANGED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void fw_device_refresh(struct work_struct *work)
|
|
|
|
+{
|
|
|
|
+ struct fw_device *device =
|
|
|
|
+ container_of(work, struct fw_device, work.work);
|
|
|
|
+ struct fw_card *card = device->card;
|
|
|
|
+ int node_id = device->node_id;
|
|
|
|
+
|
|
|
|
+ switch (reread_bus_info_block(device, device->generation)) {
|
|
|
|
+ case REREAD_BIB_ERROR:
|
|
|
|
+ if (device->config_rom_retries < MAX_RETRIES / 2 &&
|
|
|
|
+ atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
|
|
|
|
+ device->config_rom_retries++;
|
|
|
|
+ schedule_delayed_work(&device->work, RETRY_DELAY / 2);
|
|
|
|
+
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ goto give_up;
|
|
|
|
+
|
|
|
|
+ case REREAD_BIB_GONE:
|
|
|
|
+ goto gone;
|
|
|
|
+
|
|
|
|
+ case REREAD_BIB_UNCHANGED:
|
|
|
|
+ if (atomic_cmpxchg(&device->state,
|
|
|
|
+ FW_DEVICE_INITIALIZING,
|
|
|
|
+ FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN)
|
|
|
|
+ goto gone;
|
|
|
|
+
|
|
|
|
+ fw_device_update(work);
|
|
|
|
+ device->config_rom_retries = 0;
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ case REREAD_BIB_CHANGED:
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Something changed. We keep things simple and don't investigate
|
|
|
|
+ * further. We just destroy all previous units and create new ones.
|
|
|
|
+ */
|
|
|
|
+ device_for_each_child(&device->device, NULL, shutdown_unit);
|
|
|
|
+
|
|
|
|
+ if (read_bus_info_block(device, device->generation) < 0) {
|
|
|
|
+ if (device->config_rom_retries < MAX_RETRIES &&
|
|
|
|
+ atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
|
|
|
|
+ device->config_rom_retries++;
|
|
|
|
+ schedule_delayed_work(&device->work, RETRY_DELAY);
|
|
|
|
+
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ goto give_up;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ create_units(device);
|
|
|
|
+
|
|
|
|
+ if (atomic_cmpxchg(&device->state,
|
|
|
|
+ FW_DEVICE_INITIALIZING,
|
|
|
|
+ FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN)
|
|
|
|
+ goto gone;
|
|
|
|
+
|
|
|
|
+ fw_notify("refreshed device %s\n", device->device.bus_id);
|
|
|
|
+ device->config_rom_retries = 0;
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ give_up:
|
|
|
|
+ fw_notify("giving up on refresh of device %s\n", device->device.bus_id);
|
|
|
|
+ gone:
|
|
|
|
+ atomic_set(&device->state, FW_DEVICE_SHUTDOWN);
|
|
|
|
+ fw_device_shutdown(work);
|
|
|
|
+ out:
|
|
|
|
+ if (node_id == card->root_node->node_id)
|
|
|
|
+ schedule_delayed_work(&card->work, 0);
|
|
|
|
+}
|
|
|
|
+
|
|
void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
|
|
void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
|
|
{
|
|
{
|
|
struct fw_device *device;
|
|
struct fw_device *device;
|
|
@@ -793,7 +930,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
|
|
case FW_NODE_LINK_ON:
|
|
case FW_NODE_LINK_ON:
|
|
if (!node->link_on)
|
|
if (!node->link_on)
|
|
break;
|
|
break;
|
|
-
|
|
|
|
|
|
+ create:
|
|
device = kzalloc(sizeof(*device), GFP_ATOMIC);
|
|
device = kzalloc(sizeof(*device), GFP_ATOMIC);
|
|
if (device == NULL)
|
|
if (device == NULL)
|
|
break;
|
|
break;
|
|
@@ -832,6 +969,23 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
|
|
schedule_delayed_work(&device->work, INITIAL_DELAY);
|
|
schedule_delayed_work(&device->work, INITIAL_DELAY);
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
+ case FW_NODE_INITIATED_RESET:
|
|
|
|
+ device = node->data;
|
|
|
|
+ if (device == NULL)
|
|
|
|
+ goto create;
|
|
|
|
+
|
|
|
|
+ device->node_id = node->node_id;
|
|
|
|
+ smp_wmb(); /* update node_id before generation */
|
|
|
|
+ device->generation = card->generation;
|
|
|
|
+ if (atomic_cmpxchg(&device->state,
|
|
|
|
+ FW_DEVICE_RUNNING,
|
|
|
|
+ FW_DEVICE_INITIALIZING) == FW_DEVICE_RUNNING) {
|
|
|
|
+ PREPARE_DELAYED_WORK(&device->work, fw_device_refresh);
|
|
|
|
+ schedule_delayed_work(&device->work,
|
|
|
|
+ node == card->local_node ? 0 : INITIAL_DELAY);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
case FW_NODE_UPDATED:
|
|
case FW_NODE_UPDATED:
|
|
if (!node->link_on || node->data == NULL)
|
|
if (!node->link_on || node->data == NULL)
|
|
break;
|
|
break;
|