summaryrefslogtreecommitdiff
path: root/drivers/firewire/core-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firewire/core-device.c')
-rw-r--r--drivers/firewire/core-device.c214
1 files changed, 99 insertions, 115 deletions
diff --git a/drivers/firewire/core-device.c b/drivers/firewire/core-device.c
index 7d3346b3a2bf..ec3e21ad2025 100644
--- a/drivers/firewire/core-device.c
+++ b/drivers/firewire/core-device.c
@@ -12,7 +12,6 @@
#include <linux/errno.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
-#include <linux/idr.h>
#include <linux/jiffies.h>
#include <linux/kobject.h>
#include <linux/list.h>
@@ -190,10 +189,10 @@ static bool match_ids(const struct ieee1394_device_id *id_table, int *id)
}
static const struct ieee1394_device_id *unit_match(struct device *dev,
- struct device_driver *drv)
+ const struct device_driver *drv)
{
const struct ieee1394_device_id *id_table =
- container_of(drv, struct fw_driver, driver)->id_table;
+ container_of_const(drv, struct fw_driver, driver)->id_table;
int id[] = {0, 0, 0, 0};
get_modalias_ids(fw_unit(dev), id);
@@ -207,7 +206,7 @@ static const struct ieee1394_device_id *unit_match(struct device *dev,
static bool is_fw_unit(const struct device *dev);
-static int fw_unit_match(struct device *dev, struct device_driver *drv)
+static int fw_unit_match(struct device *dev, const struct device_driver *drv)
{
/* We only allow binding to fw_units. */
return is_fw_unit(dev) && unit_match(dev, drv) != NULL;
@@ -288,7 +287,7 @@ static ssize_t show_immediate(struct device *dev,
const u32 *directories[] = {NULL, NULL};
int i, value = -1;
- down_read(&fw_device_rwsem);
+ guard(rwsem_read)(&fw_device_rwsem);
if (is_fw_unit(dev)) {
directories[0] = fw_unit(dev)->directory;
@@ -317,12 +316,11 @@ static ssize_t show_immediate(struct device *dev,
}
}
- up_read(&fw_device_rwsem);
-
if (value < 0)
return -ENOENT;
- return snprintf(buf, buf ? PAGE_SIZE : 0, "0x%06x\n", value);
+ // Note that this function is also called by init_fw_attribute_group() with NULL pointer.
+ return buf ? sysfs_emit(buf, "0x%06x\n", value) : 0;
}
#define IMMEDIATE_ATTR(name, key) \
@@ -338,7 +336,7 @@ static ssize_t show_text_leaf(struct device *dev,
char dummy_buf[2];
int i, ret = -ENOENT;
- down_read(&fw_device_rwsem);
+ guard(rwsem_read)(&fw_device_rwsem);
if (is_fw_unit(dev)) {
directories[0] = fw_unit(dev)->directory;
@@ -357,6 +355,7 @@ static ssize_t show_text_leaf(struct device *dev,
}
}
+ // Note that this function is also called by init_fw_attribute_group() with NULL pointer.
if (buf) {
bufsize = PAGE_SIZE - 1;
} else {
@@ -380,15 +379,14 @@ static ssize_t show_text_leaf(struct device *dev,
}
}
- if (ret >= 0) {
- /* Strip trailing whitespace and add newline. */
- while (ret > 0 && isspace(buf[ret - 1]))
- ret--;
- strcpy(buf + ret, "\n");
- ret++;
- }
+ if (ret < 0)
+ return ret;
- up_read(&fw_device_rwsem);
+ // Strip trailing whitespace and add newline.
+ while (ret > 0 && isspace(buf[ret - 1]))
+ ret--;
+ strcpy(buf + ret, "\n");
+ ret++;
return ret;
}
@@ -464,10 +462,10 @@ static ssize_t config_rom_show(struct device *dev,
struct fw_device *device = fw_device(dev);
size_t length;
- down_read(&fw_device_rwsem);
+ guard(rwsem_read)(&fw_device_rwsem);
+
length = device->config_rom_length * 4;
memcpy(buf, device->config_rom, length);
- up_read(&fw_device_rwsem);
return length;
}
@@ -476,13 +474,10 @@ static ssize_t guid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fw_device *device = fw_device(dev);
- int ret;
- down_read(&fw_device_rwsem);
- ret = sysfs_emit(buf, "0x%08x%08x\n", device->config_rom[3], device->config_rom[4]);
- up_read(&fw_device_rwsem);
+ guard(rwsem_read)(&fw_device_rwsem);
- return ret;
+ return sysfs_emit(buf, "0x%08x%08x\n", device->config_rom[3], device->config_rom[4]);
}
static ssize_t is_local_show(struct device *dev,
@@ -490,7 +485,7 @@ static ssize_t is_local_show(struct device *dev,
{
struct fw_device *device = fw_device(dev);
- return sprintf(buf, "%u\n", device->is_local);
+ return sysfs_emit(buf, "%u\n", device->is_local);
}
static int units_sprintf(char *buf, const u32 *directory)
@@ -522,7 +517,8 @@ static ssize_t units_show(struct device *dev,
struct fw_csr_iterator ci;
int key, value, i = 0;
- down_read(&fw_device_rwsem);
+ guard(rwsem_read)(&fw_device_rwsem);
+
fw_csr_iterator_init(&ci, &device->config_rom[ROOT_DIR_OFFSET]);
while (fw_csr_iterator_next(&ci, &key, &value)) {
if (key != (CSR_UNIT | CSR_DIRECTORY))
@@ -531,7 +527,6 @@ static ssize_t units_show(struct device *dev,
if (i >= PAGE_SIZE - (8 + 1 + 8 + 1))
break;
}
- up_read(&fw_device_rwsem);
if (i)
buf[i - 1] = '\n';
@@ -569,7 +564,8 @@ static int read_rom(struct fw_device *device,
return rcode;
}
-#define MAX_CONFIG_ROM_SIZE 256
+// By quadlet unit.
+#define MAX_CONFIG_ROM_SIZE ((CSR_CONFIG_ROM_END - CSR_CONFIG_ROM) / sizeof(u32))
/*
* Read the bus info block, perform a speed probe, and read all of the rest of
@@ -727,10 +723,10 @@ static int read_config_rom(struct fw_device *device, int generation)
goto out;
}
- down_write(&fw_device_rwsem);
- device->config_rom = new_rom;
- device->config_rom_length = length;
- up_write(&fw_device_rwsem);
+ scoped_guard(rwsem_write, &fw_device_rwsem) {
+ device->config_rom = new_rom;
+ device->config_rom_length = length;
+ }
kfree(old_rom);
ret = RCODE_COMPLETE;
@@ -811,24 +807,21 @@ static int shutdown_unit(struct device *device, void *data)
/*
* 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);
-DEFINE_IDR(fw_device_idr);
+DEFINE_XARRAY_ALLOC(fw_device_xa);
int fw_cdev_major;
struct fw_device *fw_device_get_by_devt(dev_t devt)
{
struct fw_device *device;
- down_read(&fw_device_rwsem);
- device = idr_find(&fw_device_idr, MINOR(devt));
+ device = xa_load(&fw_device_xa, MINOR(devt));
if (device)
fw_device_get(device);
- up_read(&fw_device_rwsem);
return device;
}
@@ -862,7 +855,6 @@ static void fw_device_shutdown(struct work_struct *work)
{
struct fw_device *device =
container_of(work, struct fw_device, work.work);
- int minor = MINOR(device->device.devt);
if (time_before64(get_jiffies_64(),
device->card->reset_jiffies + SHUTDOWN_DELAY)
@@ -880,9 +872,7 @@ static void fw_device_shutdown(struct work_struct *work)
device_for_each_child(&device->device, NULL, shutdown_unit);
device_unregister(&device->device);
- down_write(&fw_device_rwsem);
- idr_remove(&fw_device_idr, minor);
- up_write(&fw_device_rwsem);
+ xa_erase(&fw_device_xa, MINOR(device->device.devt));
fw_device_put(device);
}
@@ -891,16 +881,14 @@ static void fw_device_release(struct device *dev)
{
struct fw_device *device = fw_device(dev);
struct fw_card *card = device->card;
- unsigned long flags;
/*
* Take the card lock so we don't set this to NULL while a
* FW_NODE_UPDATED callback is being handled or while the
* bus manager work looks at this node.
*/
- spin_lock_irqsave(&card->lock, flags);
- device->node->data = NULL;
- spin_unlock_irqrestore(&card->lock, flags);
+ scoped_guard(spinlock_irqsave, &card->lock)
+ device->node->data = NULL;
fw_node_put(device->node);
kfree(device->config_rom);
@@ -940,59 +928,6 @@ static void fw_device_update(struct work_struct *work)
device_for_each_child(&device->device, NULL, update_unit);
}
-/*
- * If a device was pending for deletion because its node went away but its
- * bus info block and root directory header matches that of a newly discovered
- * device, revive the existing fw_device.
- * The newly allocated fw_device becomes obsolete instead.
- */
-static int lookup_existing_device(struct device *dev, void *data)
-{
- struct fw_device *old = fw_device(dev);
- struct fw_device *new = data;
- struct fw_card *card = new->card;
- int match = 0;
-
- if (!is_fw_device(dev))
- return 0;
-
- down_read(&fw_device_rwsem); /* serialize config_rom access */
- spin_lock_irq(&card->lock); /* serialize node access */
-
- if (memcmp(old->config_rom, new->config_rom, 6 * 4) == 0 &&
- atomic_cmpxchg(&old->state,
- FW_DEVICE_GONE,
- FW_DEVICE_RUNNING) == FW_DEVICE_GONE) {
- struct fw_node *current_node = new->node;
- struct fw_node *obsolete_node = old->node;
-
- new->node = obsolete_node;
- new->node->data = new;
- old->node = current_node;
- old->node->data = old;
-
- old->max_speed = new->max_speed;
- old->node_id = current_node->node_id;
- smp_wmb(); /* update node_id before generation */
- old->generation = card->generation;
- old->config_rom_retries = 0;
- fw_notice(card, "rediscovered device %s\n", dev_name(dev));
-
- old->workfn = fw_device_update;
- fw_schedule_device_work(old, 0);
-
- if (current_node == card->root_node)
- fw_schedule_bm_work(card, 0);
-
- match = 1;
- }
-
- spin_unlock_irq(&card->lock);
- up_read(&fw_device_rwsem);
-
- return match;
-}
-
enum { BC_UNKNOWN = 0, BC_UNIMPLEMENTED, BC_IMPLEMENTED, };
static void set_broadcast_channel(struct fw_device *device, int generation)
@@ -1053,13 +988,26 @@ int fw_device_set_broadcast_channel(struct device *dev, void *gen)
return 0;
}
+static int compare_configuration_rom(struct device *dev, const void *data)
+{
+ const struct fw_device *old = fw_device(dev);
+ const u32 *config_rom = data;
+
+ if (!is_fw_device(dev))
+ return 0;
+
+ // Compare the bus information block and root_length/root_crc.
+ return !memcmp(old->config_rom, config_rom, 6 * 4);
+}
+
static void fw_device_init(struct work_struct *work)
{
struct fw_device *device =
container_of(work, struct fw_device, work.work);
struct fw_card *card = device->card;
- struct device *revived_dev;
- int minor, ret;
+ struct device *found;
+ u32 minor;
+ int ret;
/*
* All failure paths here set node->data to NULL, so that we
@@ -1085,24 +1033,62 @@ static void fw_device_init(struct work_struct *work)
return;
}
- revived_dev = device_find_child(card->device,
- device, lookup_existing_device);
- if (revived_dev) {
- put_device(revived_dev);
- fw_device_release(&device->device);
+ // If a device was pending for deletion because its node went away but its bus info block
+ // and root directory header matches that of a newly discovered device, revive the
+ // existing fw_device. The newly allocated fw_device becomes obsolete instead.
+ //
+ // serialize config_rom access.
+ scoped_guard(rwsem_read, &fw_device_rwsem) {
+ found = device_find_child(card->device, device->config_rom,
+ compare_configuration_rom);
+ }
+ if (found) {
+ struct fw_device *reused = fw_device(found);
+
+ if (atomic_cmpxchg(&reused->state,
+ FW_DEVICE_GONE,
+ FW_DEVICE_RUNNING) == FW_DEVICE_GONE) {
+ // serialize node access
+ scoped_guard(spinlock_irq, &card->lock) {
+ struct fw_node *current_node = device->node;
+ struct fw_node *obsolete_node = reused->node;
+
+ device->node = obsolete_node;
+ device->node->data = device;
+ reused->node = current_node;
+ reused->node->data = reused;
+
+ reused->max_speed = device->max_speed;
+ reused->node_id = current_node->node_id;
+ smp_wmb(); /* update node_id before generation */
+ reused->generation = card->generation;
+ reused->config_rom_retries = 0;
+ fw_notice(card, "rediscovered device %s\n",
+ dev_name(found));
+
+ reused->workfn = fw_device_update;
+ fw_schedule_device_work(reused, 0);
+
+ if (current_node == card->root_node)
+ fw_schedule_bm_work(card, 0);
+ }
- return;
+ put_device(found);
+ fw_device_release(&device->device);
+
+ return;
+ }
+
+ put_device(found);
}
device_initialize(&device->device);
fw_device_get(device);
- down_write(&fw_device_rwsem);
- minor = idr_alloc(&fw_device_idr, device, 0, 1 << MINORBITS,
- GFP_KERNEL);
- up_write(&fw_device_rwsem);
- if (minor < 0)
+ // The index of allocated entry is used for minor identifier of device node.
+ ret = xa_alloc(&fw_device_xa, &minor, device, XA_LIMIT(0, MINORMASK), GFP_KERNEL);
+ if (ret < 0)
goto error;
device->device.bus = &fw_bus_type;
@@ -1163,11 +1149,9 @@ static void fw_device_init(struct work_struct *work)
return;
error_with_cdev:
- down_write(&fw_device_rwsem);
- idr_remove(&fw_device_idr, minor);
- up_write(&fw_device_rwsem);
+ xa_erase(&fw_device_xa, minor);
error:
- fw_device_put(device); /* fw_device_idr's reference */
+ fw_device_put(device); // fw_device_xa's reference.
put_device(&device->device); /* our reference */
}