Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

firewire: core: update fw_device outside of device_find_child()

When detecting updates of bus topology, the data of fw_device is newly
allocated and caches the content of configuration ROM from the
corresponding node. Then, the tree of device is sought to find the
previous data of fw_device corresponding to the node. If found, the
previous data is updated and reused and the data of fw_device newly
allocated is going to be released.

The above procedure is done in the call of device_find_child(), however it
is a bit abusing against the intention of the helper function, since it is
preferable to find only without updating.

This commit splits the update outside of the call.

Cc: Zijun Hu <zijun_hu@icloud.com>
Link: https://lore.kernel.org/r/20240820132132.28839-1-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>

+59 -57
+59 -57
drivers/firewire/core-device.c
··· 928 928 device_for_each_child(&device->device, NULL, update_unit); 929 929 } 930 930 931 - /* 932 - * If a device was pending for deletion because its node went away but its 933 - * bus info block and root directory header matches that of a newly discovered 934 - * device, revive the existing fw_device. 935 - * The newly allocated fw_device becomes obsolete instead. 936 - */ 937 - static int lookup_existing_device(struct device *dev, void *data) 938 - { 939 - struct fw_device *old = fw_device(dev); 940 - struct fw_device *new = data; 941 - struct fw_card *card = new->card; 942 - int match = 0; 943 - 944 - if (!is_fw_device(dev)) 945 - return 0; 946 - 947 - guard(rwsem_read)(&fw_device_rwsem); // serialize config_rom access 948 - guard(spinlock_irq)(&card->lock); // serialize node access 949 - 950 - if (memcmp(old->config_rom, new->config_rom, 6 * 4) == 0 && 951 - atomic_cmpxchg(&old->state, 952 - FW_DEVICE_GONE, 953 - FW_DEVICE_RUNNING) == FW_DEVICE_GONE) { 954 - struct fw_node *current_node = new->node; 955 - struct fw_node *obsolete_node = old->node; 956 - 957 - new->node = obsolete_node; 958 - new->node->data = new; 959 - old->node = current_node; 960 - old->node->data = old; 961 - 962 - old->max_speed = new->max_speed; 963 - old->node_id = current_node->node_id; 964 - smp_wmb(); /* update node_id before generation */ 965 - old->generation = card->generation; 966 - old->config_rom_retries = 0; 967 - fw_notice(card, "rediscovered device %s\n", dev_name(dev)); 968 - 969 - old->workfn = fw_device_update; 970 - fw_schedule_device_work(old, 0); 971 - 972 - if (current_node == card->root_node) 973 - fw_schedule_bm_work(card, 0); 974 - 975 - match = 1; 976 - } 977 - 978 - return match; 979 - } 980 - 981 931 enum { BC_UNKNOWN = 0, BC_UNIMPLEMENTED, BC_IMPLEMENTED, }; 982 932 983 933 static void set_broadcast_channel(struct fw_device *device, int generation) ··· 988 1038 return 0; 989 1039 } 990 1040 1041 + static int compare_configuration_rom(struct device *dev, void *data) 1042 + { 1043 + const struct fw_device *old = fw_device(dev); 1044 + const u32 *config_rom = data; 1045 + 1046 + if (!is_fw_device(dev)) 1047 + return 0; 1048 + 1049 + // Compare the bus information block and root_length/root_crc. 1050 + return !memcmp(old->config_rom, config_rom, 6 * 4); 1051 + } 1052 + 991 1053 static void fw_device_init(struct work_struct *work) 992 1054 { 993 1055 struct fw_device *device = 994 1056 container_of(work, struct fw_device, work.work); 995 1057 struct fw_card *card = device->card; 996 - struct device *revived_dev; 1058 + struct device *found; 997 1059 u32 minor; 998 1060 int ret; 999 1061 ··· 1033 1071 return; 1034 1072 } 1035 1073 1036 - revived_dev = device_find_child(card->device, 1037 - device, lookup_existing_device); 1038 - if (revived_dev) { 1039 - put_device(revived_dev); 1040 - fw_device_release(&device->device); 1074 + // If a device was pending for deletion because its node went away but its bus info block 1075 + // and root directory header matches that of a newly discovered device, revive the 1076 + // existing fw_device. The newly allocated fw_device becomes obsolete instead. 1077 + // 1078 + // serialize config_rom access. 1079 + scoped_guard(rwsem_read, &fw_device_rwsem) { 1080 + found = device_find_child(card->device, (void *)device->config_rom, 1081 + compare_configuration_rom); 1082 + } 1083 + if (found) { 1084 + struct fw_device *reused = fw_device(found); 1041 1085 1042 - return; 1086 + if (atomic_cmpxchg(&reused->state, 1087 + FW_DEVICE_GONE, 1088 + FW_DEVICE_RUNNING) == FW_DEVICE_GONE) { 1089 + // serialize node access 1090 + scoped_guard(spinlock_irq, &card->lock) { 1091 + struct fw_node *current_node = device->node; 1092 + struct fw_node *obsolete_node = reused->node; 1093 + 1094 + device->node = obsolete_node; 1095 + device->node->data = device; 1096 + reused->node = current_node; 1097 + reused->node->data = reused; 1098 + 1099 + reused->max_speed = device->max_speed; 1100 + reused->node_id = current_node->node_id; 1101 + smp_wmb(); /* update node_id before generation */ 1102 + reused->generation = card->generation; 1103 + reused->config_rom_retries = 0; 1104 + fw_notice(card, "rediscovered device %s\n", 1105 + dev_name(found)); 1106 + 1107 + reused->workfn = fw_device_update; 1108 + fw_schedule_device_work(reused, 0); 1109 + 1110 + if (current_node == card->root_node) 1111 + fw_schedule_bm_work(card, 0); 1112 + } 1113 + 1114 + put_device(found); 1115 + fw_device_release(&device->device); 1116 + 1117 + return; 1118 + } 1119 + 1120 + put_device(found); 1043 1121 } 1044 1122 1045 1123 device_initialize(&device->device);