]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/firewire/fw-device.c
firewire: core: add sysfs attribute for easier udev rules
[mirror_ubuntu-bionic-kernel.git] / drivers / firewire / fw-device.c
index 633e44de5d1a2cc4cdcabf1f31081879677aebf0..55158834289829204238ef185800c066d88f4258 100644 (file)
@@ -292,8 +292,7 @@ static void init_fw_attribute_group(struct device *dev,
                group->attrs[j++] = &attr->attr;
        }
 
-       BUG_ON(j >= ARRAY_SIZE(group->attrs));
-       group->attrs[j++] = NULL;
+       group->attrs[j] = NULL;
        group->groups[0] = &group->group;
        group->groups[1] = NULL;
        group->group.attrs = group->attrs;
@@ -356,9 +355,56 @@ static ssize_t guid_show(struct device *dev,
        return ret;
 }
 
+static int units_sprintf(char *buf, u32 *directory)
+{
+       struct fw_csr_iterator ci;
+       int key, value;
+       int specifier_id = 0;
+       int version = 0;
+
+       fw_csr_iterator_init(&ci, 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;
+               }
+       }
+
+       return sprintf(buf, "0x%06x:0x%06x ", specifier_id, version);
+}
+
+static ssize_t units_show(struct device *dev,
+                         struct device_attribute *attr, char *buf)
+{
+       struct fw_device *device = fw_device(dev);
+       struct fw_csr_iterator ci;
+       int key, value, i = 0;
+
+       down_read(&fw_device_rwsem);
+       fw_csr_iterator_init(&ci, &device->config_rom[5]);
+       while (fw_csr_iterator_next(&ci, &key, &value)) {
+               if (key != (CSR_UNIT | CSR_DIRECTORY))
+                       continue;
+               i += units_sprintf(&buf[i], ci.p + value - 1);
+               if (i >= PAGE_SIZE - (8 + 1 + 8 + 1))
+                       break;
+       }
+       up_read(&fw_device_rwsem);
+
+       if (i)
+               buf[i - 1] = '\n';
+
+       return i;
+}
+
 static struct device_attribute fw_device_attributes[] = {
        __ATTR_RO(config_rom),
        __ATTR_RO(guid),
+       __ATTR_RO(units),
        __ATTR_NULL,
 };
 
@@ -518,7 +564,7 @@ static int read_bus_info_block(struct fw_device *device, int generation)
 
        kfree(old_rom);
        ret = 0;
-       device->cmc = rom[2] & 1 << 30;
+       device->cmc = rom[2] >> 30 & 1;
  out:
        kfree(rom);
 
@@ -570,9 +616,13 @@ static void create_units(struct fw_device *device)
                unit->device.parent = &device->device;
                dev_set_name(&unit->device, "%s.%d", dev_name(&device->device), i++);
 
+               BUILD_BUG_ON(ARRAY_SIZE(unit->attribute_group.attrs) <
+                               ARRAY_SIZE(fw_unit_attributes) +
+                               ARRAY_SIZE(config_rom_attributes));
                init_fw_attribute_group(&unit->device,
                                        fw_unit_attributes,
                                        &unit->attribute_group);
+
                if (device_register(&unit->device) < 0)
                        goto skip_unit;
 
@@ -756,6 +806,44 @@ static int lookup_existing_device(struct device *dev, void *data)
        return match;
 }
 
+enum { BC_UNKNOWN = 0, BC_UNIMPLEMENTED, BC_IMPLEMENTED, };
+
+void fw_device_set_broadcast_channel(struct fw_device *device, int generation)
+{
+       struct fw_card *card = device->card;
+       __be32 data;
+       int rcode;
+
+       if (!card->broadcast_channel_allocated)
+               return;
+
+       if (device->bc_implemented == BC_UNKNOWN) {
+               rcode = fw_run_transaction(card, TCODE_READ_QUADLET_REQUEST,
+                               device->node_id, generation, device->max_speed,
+                               CSR_REGISTER_BASE + CSR_BROADCAST_CHANNEL,
+                               &data, 4);
+               switch (rcode) {
+               case RCODE_COMPLETE:
+                       if (data & cpu_to_be32(1 << 31)) {
+                               device->bc_implemented = BC_IMPLEMENTED;
+                               break;
+                       }
+                       /* else fall through to case address error */
+               case RCODE_ADDRESS_ERROR:
+                       device->bc_implemented = BC_UNIMPLEMENTED;
+               }
+       }
+
+       if (device->bc_implemented == BC_IMPLEMENTED) {
+               data = cpu_to_be32(BROADCAST_CHANNEL_INITIAL |
+                                  BROADCAST_CHANNEL_VALID);
+               fw_run_transaction(card, TCODE_WRITE_QUADLET_REQUEST,
+                               device->node_id, generation, device->max_speed,
+                               CSR_REGISTER_BASE + CSR_BROADCAST_CHANNEL,
+                               &data, 4);
+       }
+}
+
 static void fw_device_init(struct work_struct *work)
 {
        struct fw_device *device =
@@ -811,9 +899,13 @@ static void fw_device_init(struct work_struct *work)
        device->device.devt = MKDEV(fw_cdev_major, minor);
        dev_set_name(&device->device, "fw%d", minor);
 
+       BUILD_BUG_ON(ARRAY_SIZE(device->attribute_group.attrs) <
+                       ARRAY_SIZE(fw_device_attributes) +
+                       ARRAY_SIZE(config_rom_attributes));
        init_fw_attribute_group(&device->device,
                                fw_device_attributes,
                                &device->attribute_group);
+
        if (device_add(&device->device)) {
                fw_error("Failed to add device.\n");
                goto error_with_cdev;
@@ -849,6 +941,8 @@ static void fw_device_init(struct work_struct *work)
                                  device->config_rom[3], device->config_rom[4],
                                  1 << device->max_speed);
                device->config_rom_retries = 0;
+
+               fw_device_set_broadcast_channel(device, device->generation);
        }
 
        /*
@@ -953,6 +1047,9 @@ static void fw_device_refresh(struct work_struct *work)
 
        create_units(device);
 
+       /* Userspace may want to re-read attributes. */
+       kobject_uevent(&device->device.kobj, KOBJ_CHANGE);
+
        if (atomic_cmpxchg(&device->state,
                           FW_DEVICE_INITIALIZING,
                           FW_DEVICE_RUNNING) == FW_DEVICE_GONE)
@@ -1002,6 +1099,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
                device->node = fw_node_get(node);
                device->node_id = node->node_id;
                device->generation = card->generation;
+               device->is_local = node == card->local_node;
                mutex_init(&device->client_list_mutex);
                INIT_LIST_HEAD(&device->client_list);
 
@@ -1035,7 +1133,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
                            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);
+                               device->is_local ? 0 : INITIAL_DELAY);
                }
                break;