diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-21 13:39:25 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-21 13:39:25 +0100 |
| commit | de87643cd2865fed133fd594cc10f162b1648d62 (patch) | |
| tree | 99ce27d9b31c0f95c460f834013019fda76d8b98 | |
| parent | 6efe1fb4b34f71e414462659f3a1c57bc55ce402 (diff) | |
| parent | 32906613748b3a42bb29472ff406f8ba2538cb31 (diff) | |
| download | linux-next-de87643cd2865fed133fd594cc10f162b1648d62.tar.gz linux-next-de87643cd2865fed133fd594cc10f162b1648d62.zip | |
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394.git
| -rw-r--r-- | Documentation/driver-api/firewire.rst | 5 | ||||
| -rw-r--r-- | drivers/firewire/.kunitconfig | 1 | ||||
| -rw-r--r-- | drivers/firewire/Kconfig | 16 | ||||
| -rw-r--r-- | drivers/firewire/Makefile | 2 | ||||
| -rw-r--r-- | drivers/firewire/config-rom-generator-test.c | 407 | ||||
| -rw-r--r-- | drivers/firewire/config-rom-parser-test.c | 355 | ||||
| -rw-r--r-- | drivers/firewire/core-card.c | 124 | ||||
| -rw-r--r-- | drivers/firewire/core-cdev.c | 301 | ||||
| -rw-r--r-- | drivers/firewire/core-device.c | 12 | ||||
| -rw-r--r-- | drivers/firewire/core-iso.c | 32 | ||||
| -rw-r--r-- | drivers/firewire/core-topology.c | 3 | ||||
| -rw-r--r-- | drivers/firewire/core-transaction.c | 414 | ||||
| -rw-r--r-- | drivers/firewire/device-attribute-test.c | 4 | ||||
| -rw-r--r-- | drivers/firewire/ohci-serdes-test.c | 4 | ||||
| -rw-r--r-- | drivers/firewire/ohci.c | 312 | ||||
| -rw-r--r-- | drivers/firewire/ohci.h | 2 | ||||
| -rw-r--r-- | drivers/firewire/uapi-test.c | 20 | ||||
| -rw-r--r-- | include/linux/firewire.h | 153 | ||||
| -rw-r--r-- | include/uapi/linux/firewire-cdev.h | 4 | ||||
| -rw-r--r-- | tools/firewire/nosy-dump.c | 10 |
20 files changed, 1655 insertions, 526 deletions
diff --git a/Documentation/driver-api/firewire.rst b/Documentation/driver-api/firewire.rst index 28a32410f7d2..695c5f6db045 100644 --- a/Documentation/driver-api/firewire.rst +++ b/Documentation/driver-api/firewire.rst @@ -37,6 +37,11 @@ Firewire device probing and sysfs interfaces Firewire core transaction interfaces ==================================== +.. kernel-doc:: include/linux/firewire.h + :identifiers: fw_transaction fw_transaction_callback_t fw_transaction_callback_with_tstamp_t + fw_transaction_callback fw_send_request fw_send_request_with_tstamp + fw_address_callback_t fw_address_handler fw_address_region + .. kernel-doc:: drivers/firewire/core-transaction.c :export: diff --git a/drivers/firewire/.kunitconfig b/drivers/firewire/.kunitconfig index 7406acb00478..ee9129081cef 100644 --- a/drivers/firewire/.kunitconfig +++ b/drivers/firewire/.kunitconfig @@ -7,3 +7,4 @@ CONFIG_FIREWIRE_KUNIT_PACKET_SERDES_TEST=y CONFIG_FIREWIRE_KUNIT_SELF_ID_SEQUENCE_HELPER_TEST=y CONFIG_FIREWIRE_KUNIT_OHCI_SERDES_TEST=y CONFIG_FIREWIRE_KUNIT_NODE_TREE_TEST=y +CONFIG_FIREWIRE_KUNIT_CONFIG_ROM_PARSER_AND_GENERATOR_TEST=y diff --git a/drivers/firewire/Kconfig b/drivers/firewire/Kconfig index b5abe00accc9..7e1d4b2c440b 100644 --- a/drivers/firewire/Kconfig +++ b/drivers/firewire/Kconfig @@ -96,6 +96,22 @@ config FIREWIRE_KUNIT_NODE_TREE_TEST For more information on KUnit and unit tests in general, refer to the KUnit documentation in Documentation/dev-tools/kunit/. +config FIREWIRE_KUNIT_CONFIG_ROM_PARSER_AND_GENERATOR_TEST + tristate "KUnit tests for config ROM parser and generator" if !KUNIT_ALL_TESTS + depends on FIREWIRE && KUNIT + default KUNIT_ALL_TESTS + help + This builds the KUnit tests to check parser and generator for + configuration ROM content defined in IEEE 1212. + + KUnit tests run during boot and output the results to the debug + log in TAP format (https://testanything.org/). Only useful for + kernel devs running KUnit test harness and are not for inclusion + into a production build. + + For more information on KUnit and unit tests in general, refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + config FIREWIRE_OHCI tristate "OHCI-1394 controllers" depends on PCI && FIREWIRE diff --git a/drivers/firewire/Makefile b/drivers/firewire/Makefile index 1ff550e93a8c..e27fca37b4b2 100644 --- a/drivers/firewire/Makefile +++ b/drivers/firewire/Makefile @@ -3,6 +3,8 @@ # Makefile for the Linux IEEE 1394 implementation # +CONTEXT_ANALYSIS_core-transaction.o := y + firewire-core-y += core-trace.o core-card.o core-cdev.o core-device.o \ core-iso.o core-topology.o core-transaction.o firewire-ohci-y += ohci.o diff --git a/drivers/firewire/config-rom-generator-test.c b/drivers/firewire/config-rom-generator-test.c new file mode 100644 index 000000000000..dd2e71213209 --- /dev/null +++ b/drivers/firewire/config-rom-generator-test.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// config-rom-generator-test.c - An application of Kunit to test configuration ROM generator. +// +// Copyright (c) 2026 Takashi Sakamoto +// +// This file can not be built independently since it is intentionally included in core-card.c. + +#include <kunit/test.h> +#include <kunit/device.h> + +static const __be32 config_rom_bare[] = { + cpu_to_be32(0x0404921b), // bus info + cpu_to_be32(0x31333934), // | + cpu_to_be32(0xf000b223), // | + cpu_to_be32(0x01234567), // | + cpu_to_be32(0x89abcdef), // v + cpu_to_be32(0x00051b70), // root directory + cpu_to_be32(0x0c0083c0), // | + cpu_to_be32(0x03001f11), // | + cpu_to_be32(0x81000003), // | + cpu_to_be32(0x17023901), // | + cpu_to_be32(0x81000008), // v + cpu_to_be32(0x00064cb7), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4c696e75), // | + cpu_to_be32(0x78204669), // | + cpu_to_be32(0x72657769), // | + cpu_to_be32(0x72650000), // v + cpu_to_be32(0x0003ff1c), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4a756a75), // v Juju is a code name at the development time of this stack. +}; + +// Following to Configuration ROM for AV/C Devices 1.0 (Dec. 2000. 1394 Trading Association, +// Document 1999027). +#define UNIT_SPEC_ID_1394TA 0x0000a02d +#define UNIT_SW_VERSION_AVC 0x00010001 +#define UNIT_SW_VERSION_IIDC_0104 0x00000100 + +static const u32 avc_unit_directory_and_leaf[] = { + 0x00040000, // Unit directory consists of below 4 quads. + (CSR_SPECIFIER_ID << 24) | UNIT_SPEC_ID_1394TA, + (CSR_VERSION << 24) | UNIT_SW_VERSION_AVC, + (CSR_MODEL << 24) | 0x00260827, // Today. + ((CSR_LEAF | CSR_DESCRIPTOR) << 24) | 0x00000001, // Point to next quadlet. + 0x00030000, // Text leaf consists of below 3 quads. + 0x00000000, + 0x00000000, + 0x50756900, // Pui is the name of a cat that the author takes care of. +}; + +static const __be32 config_rom_with_avc_unit[] = { + cpu_to_be32(0x0404c1e5), // bus info + cpu_to_be32(0x31333934), // | + cpu_to_be32(0xf000b233), // | + cpu_to_be32(0x01234567), // | + cpu_to_be32(0x89abcdef), // v + cpu_to_be32(0x0006a2d2), // root directory + cpu_to_be32(0x0c0083c0), // | + cpu_to_be32(0x03001f11), // | + cpu_to_be32(0x81000004), // | + cpu_to_be32(0x17023901), // | + cpu_to_be32(0x81000009), // | + cpu_to_be32(0xd100000c), // v + cpu_to_be32(0x00064cb7), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4c696e75), // | + cpu_to_be32(0x78204669), // | + cpu_to_be32(0x72657769), // | + cpu_to_be32(0x72650000), // v + cpu_to_be32(0x0003ff1c), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4a756a75), // v + cpu_to_be32(0x0004227b), // unit directory (from root) + cpu_to_be32(0x1200a02d), // | + cpu_to_be32(0x13010001), // | + cpu_to_be32(0x17260827), // | + cpu_to_be32(0x81000001), // v + cpu_to_be32(0x0003f771), // text descriptor leaf (from unit) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x50756900), // v +}; + +// Following to 1394-based Digital Camera Specification Version 1.04 (Aug. 1996. 1394 Trading +// Association) +#define IIDC_COMMAND_REGS_BASE 0x00 +#define IIDC_VENDOR_NAME_LEAF 0x01 +#define IIDC_MODEL_NAME_LEAF 0x02 + +static const u32 iidc_unit_directories_and_leafs[] = { + 0x00030000, + (CSR_SPECIFIER_ID << 24) | UNIT_SPEC_ID_1394TA, + (CSR_VERSION << 24) | UNIT_SW_VERSION_IIDC_0104, + ((CSR_DIRECTORY | CSR_DEPENDENT_INFO) << 24) | 0x00000001, + 0x00030000, + ((CSR_OFFSET | IIDC_COMMAND_REGS_BASE) << 24) | 0x00012345, + ((CSR_LEAF | IIDC_VENDOR_NAME_LEAF) << 24) | 0x00000002, + ((CSR_LEAF | IIDC_MODEL_NAME_LEAF) << 24) | 0x00000008, + 0x00060000, // Text leaf consists of below 6 quads. + 0x00000000, + 0x00000000, + 0x4c696e75, // Use the same name in root directory. + 0x78204669, + 0x72657769, + 0x72650000, + 0x00040000, // Text leaf consists of below 4 quads. + 0x00000000, + 0x00000000, + 0x43686566, // Chef Cat is a nick name when inventing IEEE 1394 itself. + 0x20436174, +}; + +static const __be32 config_rom_with_iidc_unit[] = { + cpu_to_be32(0x04046a3e), // bus info + cpu_to_be32(0x31333934), // | + cpu_to_be32(0xf000b243), // | + cpu_to_be32(0x01234567), // | + cpu_to_be32(0x89abcdef), // v + cpu_to_be32(0x0006a2d2), // root directory + cpu_to_be32(0x0c0083c0), // | + cpu_to_be32(0x03001f11), // | + cpu_to_be32(0x81000004), // | + cpu_to_be32(0x17023901), // | + cpu_to_be32(0x81000009), // | + cpu_to_be32(0xd100000c), // v + cpu_to_be32(0x00064cb7), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4c696e75), // | + cpu_to_be32(0x78204669), // | + cpu_to_be32(0x72657769), // | + cpu_to_be32(0x72650000), // v + cpu_to_be32(0x0003ff1c), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4a756a75), // v + cpu_to_be32(0x0003d7fe), // unit directory (from root) + cpu_to_be32(0x1200a02d), // | + cpu_to_be32(0x13000100), // | + cpu_to_be32(0xd4000001), // v + cpu_to_be32(0x0003ea57), // dependent directory (from unit directory) + cpu_to_be32(0x40012345), // | + cpu_to_be32(0x81000002), // | + cpu_to_be32(0x82000008), // v + cpu_to_be32(0x00064cb7), // text descriptor leaf (from dependent directory) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4c696e75), // | + cpu_to_be32(0x78204669), // | + cpu_to_be32(0x72657769), // | + cpu_to_be32(0x72650000), // v + cpu_to_be32(0x0004a3e9), // text descriptor leaf (from dependent directory) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x43686566), // | + cpu_to_be32(0x20436174), // v +}; + +static const __be32 config_rom_with_avc_and_iidc_units[] = { + cpu_to_be32(0x040439c0), // bus info + cpu_to_be32(0x31333934), // | + cpu_to_be32(0xf000b253), // | + cpu_to_be32(0x01234567), // | + cpu_to_be32(0x89abcdef), // v + cpu_to_be32(0x0007073e), // root directory + cpu_to_be32(0x0c0083c0), // | + cpu_to_be32(0x03001f11), // | + cpu_to_be32(0x81000005), // | + cpu_to_be32(0x17023901), // | + cpu_to_be32(0x8100000a), // | + cpu_to_be32(0xd100000d), // | + cpu_to_be32(0xd1000015), // v + cpu_to_be32(0x00064cb7), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4c696e75), // | + cpu_to_be32(0x78204669), // | + cpu_to_be32(0x72657769), // | + cpu_to_be32(0x72650000), // v + cpu_to_be32(0x0003ff1c), // text descriptor leaf (from root) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4a756a75), // v + cpu_to_be32(0x0004227b), // unit directory (from root) + cpu_to_be32(0x1200a02d), // | + cpu_to_be32(0x13010001), // | + cpu_to_be32(0x17260827), // | + cpu_to_be32(0x81000001), // v + cpu_to_be32(0x0003f771), // text descriptor leaf (from unit) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x50756900), // v + cpu_to_be32(0x0003d7fe), // unit directory (from root) + cpu_to_be32(0x1200a02d), // | + cpu_to_be32(0x13000100), // | + cpu_to_be32(0xd4000001), // v + cpu_to_be32(0x0003ea57), // dependent directory (from unit directory) + cpu_to_be32(0x40012345), // | + cpu_to_be32(0x81000002), // | + cpu_to_be32(0x82000008), // v + cpu_to_be32(0x00064cb7), // text descriptor leaf (from dependent directory) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x4c696e75), // | + cpu_to_be32(0x78204669), // | + cpu_to_be32(0x72657769), // | + cpu_to_be32(0x72650000), // v + cpu_to_be32(0x0004a3e9), // text descriptor leaf (from dependent directory) + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x00000000), // | + cpu_to_be32(0x43686566), // | + cpu_to_be32(0x20436174), // v +}; + +static const struct generator_test_case { + const char *const name; + int config_rom_generation; + const __be32 *const expected; + unsigned int quadlet_length; +} generator_test_cases[] = { + { + .name = "bare", + .config_rom_generation = 0, + .expected = config_rom_bare, + .quadlet_length = ARRAY_SIZE(config_rom_bare), + }, + { + .name = "with_avc_unit", + .config_rom_generation = 1, + .expected = config_rom_with_avc_unit, + .quadlet_length = ARRAY_SIZE(config_rom_with_avc_unit), + }, + { + .name = "with_iidc_unit", + .config_rom_generation = 2, + .expected = config_rom_with_iidc_unit, + .quadlet_length = ARRAY_SIZE(config_rom_with_iidc_unit), + }, + { + .name = "with_avc_and_iidc_unit", + .config_rom_generation = 3, + .expected = config_rom_with_avc_and_iidc_units, + .quadlet_length = ARRAY_SIZE(config_rom_with_avc_and_iidc_units), + }, +}; + +// Define generator_test_gen_params. +KUNIT_ARRAY_PARAM_DESC(generator_test, generator_test_cases, name); + +struct state_data { + struct fw_card card; + __be32 config_rom[(CSR_CONFIG_ROM_END - CSR_CONFIG_ROM) / sizeof(__be32)]; +}; + +static void test_config_rom_generator(struct kunit *test) +{ + // Use kernel stack since they should be mutable for doubly linked-list. + struct fw_descriptor avc_unit_entry = { + .length = ARRAY_SIZE(avc_unit_directory_and_leaf), + .key = (CSR_DIRECTORY | CSR_UNIT) << 24, + .data = avc_unit_directory_and_leaf, + }; + struct fw_descriptor iidc_unit_entry = { + .length = ARRAY_SIZE(iidc_unit_directories_and_leafs), + .key = (CSR_DIRECTORY | CSR_UNIT) << 24, + .data = iidc_unit_directories_and_leafs, + }; + const struct generator_test_case *test_case = test->param_value; + struct state_data *state = test->priv; + struct fw_card *card = &state->card; + __be32 *config_rom = state->config_rom; + + card->config_rom_generation = test_case->config_rom_generation; + card->link_speed = SCODE_800; + card->max_receive = 11; + card->guid = 0x0123456789abcdefULL; + + if (test_case->expected == config_rom_with_avc_unit || + test_case->expected == config_rom_with_avc_and_iidc_units) + KUNIT_EXPECT_EQ(test, fw_core_add_descriptor(&avc_unit_entry), 0); + + if (test_case->expected == config_rom_with_iidc_unit || + test_case->expected == config_rom_with_avc_and_iidc_units) + KUNIT_EXPECT_EQ(test, fw_core_add_descriptor(&iidc_unit_entry), 0); + + scoped_guard(mutex, &card_mutex) { + generate_config_rom(card, config_rom); + KUNIT_EXPECT_EQ(test, config_rom_length, test_case->quadlet_length); + } + + KUNIT_EXPECT_MEMEQ(test, config_rom, test_case->expected, + sizeof(*test_case->expected) * test_case->quadlet_length); + + if (test_case->expected == config_rom_with_iidc_unit || + test_case->expected == config_rom_with_avc_and_iidc_units) + fw_core_remove_descriptor(&iidc_unit_entry); + + if (test_case->expected == config_rom_with_avc_unit || + test_case->expected == config_rom_with_avc_and_iidc_units) + fw_core_remove_descriptor(&avc_unit_entry); +} + +static void add_descriptor_with_invalid_length(struct kunit *test) +{ + // Use kernel stack since they should be mutable for doubly linked-list. + struct fw_descriptor entry_with_invalid_length = { + .length = 257, + }; + + KUNIT_EXPECT_EQ(test, fw_core_add_descriptor(&entry_with_invalid_length), -EINVAL); +} + +static void add_descriptor_with_invalid_data(struct kunit *test) +{ + // Use vendor directory defined in Annex A of Configuration ROM for AV/C Devices 1.0 (Dec. + // 2000, 1394 Trading Association, TA Document 1999027). + static const u32 invalid_vendor_directory[] = { + 0x00020000, + (CSR_MODEL << 24) | 0x00009402, + ((CSR_LEAF | CSR_DESCRIPTOR) << 24) | 0x00000001, + 0xffff0000, // The length should be 6, invalid. + 0x00000000, + 0x00000000, + 0x436f6e63, + 0x6174204e, + 0x6f746174, + 0x696f6e00, + }; + // Use kernel stack since they should be mutable for doubly linked-list. + struct fw_descriptor entry_with_invalid_data = { + .length = ARRAY_SIZE(invalid_vendor_directory), + .key = (CSR_DIRECTORY | CSR_VENDOR) << 24, + .data = invalid_vendor_directory, + }; + + KUNIT_EXPECT_EQ(test, fw_core_add_descriptor(&entry_with_invalid_data), -EINVAL); +} + +static void add_descriptor_beyond_upper_limit(struct kunit *test) +{ + // Use kernel stack since they should be mutable for doubly linked-list. + struct fw_descriptor entry_beyond_upper_limit = { + .length = 255, + .key = (CSR_DIRECTORY | CSR_UNIT) << 24, + .data = NULL, + }; + u32 *data; + + data = kunit_kzalloc(test, entry_beyond_upper_limit.length, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, data); + + data[0] = (entry_beyond_upper_limit.length - 1) << 16; + entry_beyond_upper_limit.data = data; + KUNIT_EXPECT_EQ(test, fw_core_add_descriptor(&entry_beyond_upper_limit), -EBUSY); + + kunit_kfree(test, data); +} + +static const struct fw_card_driver dummy_card_driver; + +static int config_rom_generator_test_init(struct kunit *test) +{ + struct state_data *state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, state); + + struct device *dev = kunit_device_register(test, "dummy-device"); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + fw_card_initialize(&state->card, &dummy_card_driver, dev); + + test->priv = state; + + return 0; +} + +static void config_rom_generator_test_exit(struct kunit *test) +{ + struct state_data *state = test->priv; + + kunit_device_unregister(test, state->card.device); + kunit_kfree(test, state); +} + + +static struct kunit_case config_rom_generator_test_cases[] = { + KUNIT_CASE_PARAM(test_config_rom_generator, generator_test_gen_params), + KUNIT_CASE(add_descriptor_with_invalid_length), + KUNIT_CASE(add_descriptor_with_invalid_data), + KUNIT_CASE(add_descriptor_beyond_upper_limit), + {} +}; + +static struct kunit_suite config_rom_generator_test_suite = { + .name = "firewire-config-rom-generator", + .init = config_rom_generator_test_init, + .exit = config_rom_generator_test_exit, + .test_cases = config_rom_generator_test_cases, +}; +kunit_test_suite(config_rom_generator_test_suite); diff --git a/drivers/firewire/config-rom-parser-test.c b/drivers/firewire/config-rom-parser-test.c new file mode 100644 index 000000000000..45f1ead326c5 --- /dev/null +++ b/drivers/firewire/config-rom-parser-test.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// config-rom-parser-test.c - An application of Kunit to test configuration ROM parser. +// +// Copyright (c) 2026 Takashi Sakamoto +// +// This file can not be built independently since it is intentionally included in core-device.c. + +#include <kunit/test.h> +#include <kunit/static_stub.h> +#include <kunit/device.h> + +static const u32 sony_dcr_trv310k_config_rom[] = { + 0x0404e552, + 0x31333934, + 0xe0648100, + 0x00008500, + 0x005eb597, + 0x0007cdd0, + 0x03000085, + 0x8100000d, + 0x17000002, + 0x81000010, + 0x0c0083c0, + 0xd8000002, + 0xd1000003, + 0x0001ce96, + 0xd1000001, + 0x0004bbee, + 0x1200a02d, + 0x13010001, + 0x17000002, + 0x81000006, + 0x00046dc8, + 0x00000000, + 0x00000000, + 0x43616e6f, + 0x6e000000, + 0x000621ee, + 0x00000000, + 0x00000000, + 0x4d563569, + 0x204d4300, + 0x00000000, + 0x00000000, +}; + +static const u32 canon_mv5i_mc_config_rom[] = { + 0x0404e552, + 0x31333934, + 0xe0648100, + 0x00008500, + 0x005eb597, + 0x0007cdd0, + 0x03000085, + 0x8100000d, + 0x17000002, + 0x81000010, + 0x0c0083c0, + 0xd8000002, + 0xd1000003, + 0x0001ce96, + 0xd1000001, + 0x0004bbee, + 0x1200a02d, + 0x13010001, + 0x17000002, + 0x81000006, + 0x00046dc8, + 0x00000000, + 0x00000000, + 0x43616e6f, + 0x6e000000, + 0x000621ee, + 0x00000000, + 0x00000000, + 0x4d563569, + 0x204d4300, + 0x00000000, + 0x00000000, +}; + +static const u32 motu_audioexpress_config_rom[] = { + 0x0410a756, + 0x31333934, + 0x20ff7000, + 0x0001f200, + 0x000a8a7b, + 0x0004ef04, + 0x030001f2, + 0x0c0083c0, + 0xd1000002, + 0x8d000005, + 0x00031680, + 0x120001f2, + 0x13000033, + 0x17104800, + 0x00025ef3, + 0x0001f200, + 0x000a8a7b, +}; + +static const u32 tascam_fw1884_config_rom[] = { + 0x040f23c0, + 0x31333934, + 0x20ff7002, + 0x00022eff, + 0xfe800000, + 0x0004bccc, + 0x0300022e, + 0x0c0083c0, + 0x8d000006, + 0xd1000001, + 0x000347f5, + 0x1200022e, + 0x13800000, + 0xd4000004, + 0x000289aa, + 0x00022eff, + 0xfe800000, + 0x0002ae47, + 0x81000002, + 0x82000006, + 0x0004a79e, + 0x00000000, + 0x00000000, + 0x54415343, + 0x414d0000, + 0x00045443, + 0x00000000, + 0x00000000, + 0x46572d31, + 0x38383400, +}; + +static const struct parser_test_case { + const char *name; + const u32 *quadlets; + size_t quadlet_length; + int phy_speed_in_self_id; + int expected_speed; + int expected_quirk; + unsigned int expected_max_rec; + bool expected_cmc; + bool expected_irmc; +} parser_test_cases[] = { + { + .name = "detect_irm_is_1394_1995_only_quirk", + .quadlets = sony_dcr_trv310k_config_rom, + .quadlet_length = ARRAY_SIZE(sony_dcr_trv310k_config_rom), + .phy_speed_in_self_id = SCODE_100, + .expected_speed = SCODE_100, + .expected_quirk = FW_DEVICE_QUIRK_IRM_IS_1394_1995_ONLY, + .expected_max_rec = 8, + .expected_cmc = true, + .expected_irmc = true, + }, + { + .name = "detect_irm_ignores_bus_manager_quirk", + .quadlets = canon_mv5i_mc_config_rom, + .quadlet_length = ARRAY_SIZE(canon_mv5i_mc_config_rom), + .phy_speed_in_self_id = SCODE_100, + .expected_speed = SCODE_100, + .expected_quirk = FW_DEVICE_QUIRK_IRM_IGNORES_BUS_MANAGER, + .expected_max_rec = 8, + .expected_cmc = true, + .expected_irmc = true, + }, + { + .name = "detect_ack_packet_with_invalid_pending_code_quirk", + .quadlets = motu_audioexpress_config_rom, + .quadlet_length = ARRAY_SIZE(motu_audioexpress_config_rom), + .phy_speed_in_self_id = SCODE_400, + .expected_speed = SCODE_400, + .expected_quirk = FW_DEVICE_QUIRK_ACK_PACKET_WITH_INVALID_PENDING_CODE, + .expected_max_rec = 7, + .expected_cmc = false, + .expected_irmc = false, + }, + { + .name = "detect_unstable_at_s400_quirk", + .quadlets = tascam_fw1884_config_rom, + .quadlet_length = ARRAY_SIZE(tascam_fw1884_config_rom), + .phy_speed_in_self_id = SCODE_400, + .expected_speed = SCODE_200, + .expected_quirk = FW_DEVICE_QUIRK_UNSTABLE_AT_S400, + .expected_max_rec = 7, + .expected_cmc = false, + .expected_irmc = false, + }, +}; + +// Define parser_test_gen_params. +KUNIT_ARRAY_PARAM_DESC(parser_test, parser_test_cases, name); + +static int stub_run_transaction_regular(struct fw_card *card, int tcode, int destination_id, + int generation, int speed, unsigned long long offset, + void *payload, size_t length) +{ + struct kunit *test = kunit_get_current_test(); + const struct parser_test_case *param = test->param_value; + + KUNIT_ASSERT_GE(test, offset, CSR_REGISTER_BASE | CSR_CONFIG_ROM); + KUNIT_ASSERT_LT(test, offset, CSR_REGISTER_BASE | CSR_CONFIG_ROM_END); + KUNIT_ASSERT_NOT_NULL(test, payload); + KUNIT_ASSERT_EQ(test, length, 4); + + unsigned int index = (offset - (CSR_REGISTER_BASE | CSR_CONFIG_ROM)) / sizeof(u32); + __be32 *quadlet = payload; + + KUNIT_EXPECT_LE(test, speed, param->expected_speed); + KUNIT_EXPECT_LT(test, index, param->quadlet_length); + + *quadlet = cpu_to_be32(param->quadlets[index]); + + return RCODE_COMPLETE; +} + +static void test_parser_with_regular_cases(struct kunit *test) +{ + struct fw_device *device = test->priv; + const struct parser_test_case *param = test->param_value; + + kunit_activate_static_stub(test, fw_run_transaction, stub_run_transaction_regular); + + device->card->link_speed = SCODE_BETA; + device->node->max_speed = param->phy_speed_in_self_id; + + KUNIT_EXPECT_EQ(test, read_config_rom(device, 0), RCODE_COMPLETE); + + KUNIT_EXPECT_EQ(test, device->config_rom_length, param->quadlet_length); + KUNIT_EXPECT_MEMEQ(test, device->config_rom, param->quadlets, param->quadlet_length); + + KUNIT_EXPECT_TRUE(test, device->quirks & param->expected_quirk); + KUNIT_EXPECT_EQ(test, device->max_speed, param->expected_speed); + KUNIT_EXPECT_EQ(test, (unsigned int)device->max_rec, param->expected_max_rec); + KUNIT_EXPECT_EQ(test, (bool)device->cmc, param->expected_cmc); + KUNIT_EXPECT_EQ(test, (bool)device->irmc, param->expected_irmc); + + kunit_deactivate_static_stub(test, fw_run_transaction); +} + +static int stub_run_transaction_malformed(struct fw_card *card, int tcode, int destination_id, + int generation, int speed, unsigned long long offset, + void *payload, size_t length) +{ + static const u32 config_rom_first_part[] = { + 0x04000000, + 0x31333934, + 0x00008002, + 0x00000000, + 0x00000000, + 0x00030000, + (CSR_VENDOR << 24) | 0x00123456, // Regular entry. + ((CSR_LEAF | CSR_DESCRIPTOR) << 24) | 0x000000f9, // Beyond the upper limit. + ((CSR_DIRECTORY | CSR_UNIT) << 24) | 0x00000001, + 0xffff0000, // Over the upper limit. + }; + struct kunit *test = kunit_get_current_test(); + + KUNIT_ASSERT_GE(test, offset, CSR_REGISTER_BASE | CSR_CONFIG_ROM); + KUNIT_ASSERT_LT(test, offset, CSR_REGISTER_BASE | CSR_CONFIG_ROM_END); + KUNIT_ASSERT_NOT_NULL(test, payload); + KUNIT_ASSERT_EQ(test, length, 4); + + unsigned int index = (offset - (CSR_REGISTER_BASE | CSR_CONFIG_ROM)) / sizeof(u32); + __be32 *quadlet = payload; + + if (index < ARRAY_SIZE(config_rom_first_part)) + *quadlet = cpu_to_be32(config_rom_first_part[index]); + else + *quadlet = 0; + + return RCODE_COMPLETE; +} + +static void test_parser_with_overflowed_case(struct kunit *test) +{ + static const u32 corrected_config_rom[] = { + 0x04000000, + 0x31333934, + 0x00008002, + 0x00000000, + 0x00000000, + 0x00030000, + 0x03123456, + 0x00000000, // Sanitized. + 0xd1000001, + 0x00000000, // Sanitized. + }; + struct fw_device *device = test->priv; + + kunit_activate_static_stub(test, fw_run_transaction, stub_run_transaction_malformed); + + device->card->link_speed = SCODE_BETA; + + KUNIT_EXPECT_EQ(test, read_config_rom(device, 0), RCODE_COMPLETE); + + KUNIT_EXPECT_EQ(test, device->config_rom_length, ARRAY_SIZE(corrected_config_rom)); + KUNIT_EXPECT_MEMEQ(test, device->config_rom, corrected_config_rom, + sizeof(corrected_config_rom)); + + kunit_deactivate_static_stub(test, fw_run_transaction); +} + +static const struct fw_card_driver dummy_card_driver; + +static int config_rom_parser_test_init(struct kunit *test) +{ + struct fw_device *device; + struct device *dev; + + device = kunit_kzalloc(test, sizeof(*device), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, device); + + device->node = kunit_kzalloc(test, sizeof(*device->node), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, device->node); + kref_init(&device->node->kref); + + device->card = kunit_kzalloc(test, sizeof(*device->card), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, device->card); + + dev = kunit_device_register(test, "dummy-device"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + fw_card_initialize(device->card, &dummy_card_driver, dev); + + test->priv = device; + + return 0; +} + +static void config_rom_parser_test_exit(struct kunit *test) +{ + struct fw_device *device = test->priv; + + kunit_device_unregister(test, device->card->device); + kunit_kfree(test, device->card); + kunit_kfree(test, device->node); + kunit_kfree(test, device); +} + +static struct kunit_case config_rom_parser_test_cases[] = { + KUNIT_CASE_PARAM(test_parser_with_regular_cases, parser_test_gen_params), + KUNIT_CASE(test_parser_with_overflowed_case), + {} +}; + +static struct kunit_suite config_rom_parser_test_suite = { + .name = "firewire-config-rom-parser", + .init = config_rom_parser_test_init, + .exit = config_rom_parser_test_exit, + .test_cases = config_rom_parser_test_cases, +}; +kunit_test_suite(config_rom_parser_test_suite); diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c index 94992791f02e..b35e8901cc4b 100644 --- a/drivers/firewire/core-card.c +++ b/drivers/firewire/core-card.c @@ -153,8 +153,8 @@ static void update_config_roms(void) list_for_each_entry (card, &card_list, link) { generate_config_rom(card, tmp_config_rom); - card->driver->set_config_rom(card, tmp_config_rom, - config_rom_length); + smp_load_acquire(&card->driver)->set_config_rom(card, tmp_config_rom, + config_rom_length); } } @@ -231,7 +231,7 @@ static int reset_bus(struct fw_card *card, bool short_reset) trace_bus_reset_initiate(card->index, card->generation, short_reset); - return card->driver->update_phy_reg(card, reg, 0, bit); + return smp_load_acquire(&card->driver)->update_phy_reg(card, reg, 0, bit); } void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset) @@ -643,11 +643,12 @@ int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid, // * == WQ_FREEZABLE The target device would not be available when being freezed. // * == WQ_HIGHPRI High priority to process semi-realtime timestamped data. // * == WQ_SYSFS Parameters are available via sysfs. - // * max_active == 4 A hardIRQ could notify events for a pair of requests and - // response AR/AT contexts. + // * max_active == 4 + 2 A hardIRQ could notify events for a pair of requests and + // response AR/AT contexts. Additional 2 capacity are for the + // internal handling of local AT request and response packets. async_wq = alloc_workqueue("firewire-async-card%u", WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS, - 4, card->index); + 6, card->index); if (!async_wq) return -ENOMEM; @@ -659,7 +660,8 @@ int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid, scoped_guard(mutex, &card_mutex) { generate_config_rom(card, tmp_config_rom); - ret = card->driver->enable(card, tmp_config_rom, config_rom_length); + ret = smp_load_acquire(&card->driver)->enable(card, tmp_config_rom, + config_rom_length); if (ret < 0) { card->isoc_wq = NULL; card->async_wq = NULL; @@ -687,6 +689,15 @@ EXPORT_SYMBOL(fw_card_add); * .set_config_rom() is never called since the card is taken out of card_list * before switching to the dummy driver. */ +struct fw_card_driver_closer { + const struct fw_card_driver driver; + + // For shutdown operations. + struct fw_card *card; + struct list_head packet_list; + spinlock_t packet_list_lock; + struct work_struct work; +}; static int dummy_read_phy_reg(struct fw_card *card, int address) { @@ -699,14 +710,31 @@ static int dummy_update_phy_reg(struct fw_card *card, int address, return -ENODEV; } -static void dummy_send_request(struct fw_card *card, struct fw_packet *packet) +static void dummy_packet_callback_work(struct work_struct *work) { - packet->callback(packet, card, RCODE_CANCELLED); + struct fw_card_driver_closer *closer = container_of(work, typeof(*closer), work); + unsigned long flags; + struct fw_packet *p; + + spin_lock_irqsave(&closer->packet_list_lock, flags); + while ((p = list_first_entry_or_null(&closer->packet_list, typeof(*p), link_for_local))) { + list_del(&p->link_for_local); + spin_unlock_irqrestore(&closer->packet_list_lock, flags); + + p->callback(p, closer->card, RCODE_CANCELLED); + + spin_lock_irqsave(&closer->packet_list_lock, flags); + } + spin_unlock_irqrestore(&closer->packet_list_lock, flags); } -static void dummy_send_response(struct fw_card *card, struct fw_packet *packet) +static void dummy_schedule_packet_callback(struct fw_card *card, struct fw_packet *packet) { - packet->callback(packet, card, RCODE_CANCELLED); + struct fw_card_driver_closer *closer = container_of(card->driver, typeof(*closer), driver); + + scoped_guard(spinlock_irqsave, &closer->packet_list_lock) + list_add_tail(&packet->link_for_local, &closer->packet_list); + queue_work(card->async_wq, &closer->work); } static int dummy_cancel_packet(struct fw_card *card, struct fw_packet *packet) @@ -761,23 +789,6 @@ static int dummy_flush_iso_completions(struct fw_iso_context *ctx) return -ENODEV; } -static const struct fw_card_driver dummy_driver_template = { - .read_phy_reg = dummy_read_phy_reg, - .update_phy_reg = dummy_update_phy_reg, - .send_request = dummy_send_request, - .send_response = dummy_send_response, - .cancel_packet = dummy_cancel_packet, - .enable_phys_dma = dummy_enable_phys_dma, - .read_csr = dummy_read_csr, - .write_csr = dummy_write_csr, - .allocate_iso_context = dummy_allocate_iso_context, - .start_iso = dummy_start_iso, - .set_iso_channels = dummy_set_iso_channels, - .queue_iso = dummy_queue_iso, - .flush_queue_iso = dummy_flush_queue_iso, - .flush_iso_completions = dummy_flush_iso_completions, -}; - void fw_card_release(struct kref *kref) { struct fw_card *card = container_of(kref, struct fw_card, kref); @@ -788,7 +799,31 @@ EXPORT_SYMBOL_GPL(fw_card_release); void fw_core_remove_card(struct fw_card *card) { - struct fw_card_driver dummy_driver = dummy_driver_template; + struct fw_card_driver_closer closer = { + // Switch off most of the card driver interface. + .driver = { + .enable = NULL, + .disable = card->driver->disable, + .read_phy_reg = dummy_read_phy_reg, + .update_phy_reg = dummy_update_phy_reg, + .set_config_rom = NULL, + .send_request = dummy_schedule_packet_callback, + .send_response = dummy_schedule_packet_callback, + .cancel_packet = dummy_cancel_packet, + .enable_phys_dma = dummy_enable_phys_dma, + .read_csr = dummy_read_csr, + .write_csr = dummy_write_csr, + .allocate_iso_context = dummy_allocate_iso_context, + .free_iso_context = card->driver->free_iso_context, + .start_iso = dummy_start_iso, + .set_iso_channels = dummy_set_iso_channels, + .queue_iso = dummy_queue_iso, + .flush_queue_iso = dummy_flush_queue_iso, + .flush_iso_completions = dummy_flush_iso_completions, + .stop_iso = card->driver->stop_iso, + }, + .card = card, + }; might_sleep(); @@ -799,16 +834,19 @@ void fw_core_remove_card(struct fw_card *card) scoped_guard(mutex, &card_mutex) list_del_init(&card->link); - /* Switch off most of the card driver interface. */ - dummy_driver.free_iso_context = card->driver->free_iso_context; - dummy_driver.stop_iso = card->driver->stop_iso; - dummy_driver.disable = card->driver->disable; - card->driver = &dummy_driver; + INIT_LIST_HEAD(&closer.packet_list); + spin_lock_init(&closer.packet_list_lock); + INIT_WORK_ONSTACK(&closer.work, dummy_packet_callback_work); + + // Ensure the entire contents of the driver are visible to other CPUs. + smp_store_release(&card->driver, &closer.driver); - drain_workqueue(card->isoc_wq); - drain_workqueue(card->async_wq); + flush_workqueue(card->isoc_wq); + flush_workqueue(card->async_wq); card->driver->disable(card); - fw_cancel_pending_transactions(card); + + scoped_guard(spinlock_irqsave, &card->transactions.lock) + fw_cancel_pending_transactions(card); scoped_guard(spinlock_irqsave, &card->lock) fw_destroy_nodes(card); @@ -820,6 +858,10 @@ void fw_core_remove_card(struct fw_card *card) destroy_workqueue(card->isoc_wq); destroy_workqueue(card->async_wq); + disable_work_sync(&closer.work); + destroy_work_on_stack(&closer.work); + + WARN_ON(!list_empty(&closer.packet_list)); WARN_ON(!list_empty(&card->transactions.list)); } EXPORT_SYMBOL(fw_core_remove_card); @@ -842,12 +884,16 @@ EXPORT_SYMBOL(fw_core_remove_card); */ int fw_card_read_cycle_time(struct fw_card *card, u32 *cycle_time) { - if (card->driver->read_csr == dummy_read_csr) + if (smp_load_acquire(&card->driver)->read_csr == dummy_read_csr) return -ENODEV; // It's possible to switch to dummy driver between the above and the below. This is the best // effort to return -ENODEV. - *cycle_time = card->driver->read_csr(card, CSR_CYCLE_TIME); + *cycle_time = smp_load_acquire(&card->driver)->read_csr(card, CSR_CYCLE_TIME); return 0; } EXPORT_SYMBOL_GPL(fw_card_read_cycle_time); + +#ifdef CONFIG_FIREWIRE_KUNIT_CONFIG_ROM_PARSER_AND_GENERATOR_TEST +#include "config-rom-generator-test.c" +#endif diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index 664952a67a11..87efd65e6b00 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -47,14 +47,14 @@ #define FW_CDEV_VERSION_AUTO_FLUSH_ISO_OVERFLOW 5 #define FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP 6 -static DEFINE_SPINLOCK(phy_receiver_list_lock); +static DEFINE_MUTEX(phy_receiver_list_mutex); static LIST_HEAD(phy_receiver_list); struct client { u32 version; struct fw_device *device; - spinlock_t lock; + struct mutex mutex; bool in_shutdown; struct xarray resource_xa; struct list_head event_list; @@ -133,16 +133,17 @@ struct iso_resource_params { s32 bandwidth; }; +enum { + ISO_RES_AUTO_ALLOC, + ISO_RES_AUTO_REALLOC, + ISO_RES_AUTO_DEALLOC, +}; + struct iso_resource_auto { struct client_resource resource; struct client *client; - /* Schedule work and access todo only with client->lock held. */ struct delayed_work work; - enum { - ISO_RES_AUTO_ALLOC, - ISO_RES_AUTO_REALLOC, - ISO_RES_AUTO_DEALLOC, - } todo; + atomic_t todo; // one of ISO_RES_AUTO_XXX. int generation; struct iso_resource_params params; struct iso_resource_event *e_alloc, *e_dealloc; @@ -196,9 +197,7 @@ static int is_outbound_transaction_resource(const struct client_resource *resour static void schedule_iso_resource_auto(struct iso_resource_auto *r, unsigned long delay) { - client_get(r->client); - if (!queue_delayed_work(fw_workqueue, &r->work, delay)) - client_put(r->client); + queue_delayed_work(fw_workqueue, &r->work, delay); } /* @@ -316,7 +315,7 @@ static int fw_device_op_open(struct inode *inode, struct file *file) } client->device = device; - spin_lock_init(&client->lock); + mutex_init(&client->mutex); xa_init_flags(&client->resource_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_BH); INIT_LIST_HEAD(&client->event_list); init_waitqueue_head(&client->wait); @@ -339,50 +338,61 @@ static void queue_event(struct client *client, struct event *event, event->v[1].data = data1; event->v[1].size = size1; - scoped_guard(spinlock_irqsave, &client->lock) { - if (client->in_shutdown) + scoped_guard(mutex, &client->mutex) { + if (client->in_shutdown) { kfree(event); - else + } else { list_add_tail(&event->link, &client->event_list); - } - wake_up_interruptible(&client->wait); + wake_up_interruptible(&client->wait); + } + } } -static int dequeue_event(struct client *client, - char __user *buffer, size_t count) +static ssize_t dequeue_event(struct client *client, char __user *buffer, size_t count) { struct event *event; - size_t size, total; - int i, ret; - ret = wait_event_interruptible(client->wait, - !list_empty(&client->event_list) || - fw_device_is_shutdown(client->device)); - if (ret < 0) - return ret; + // After the following block, the event pointer above is guaranteed to have a correct value. + { + mutex_lock(&client->mutex); + + // This could be replaced with wait_var_event_any_lock() if poll_wait() alternative + // would be introduced. + int ret = ___wait_event(client->wait, + !list_empty(&client->event_list) || fw_device_is_shutdown(client->device), + TASK_INTERRUPTIBLE, 0, 0, + mutex_unlock(&client->mutex); + schedule(); + mutex_lock(&client->mutex) + ); + if (ret < 0) { + mutex_unlock(&client->mutex); + return ret; + } - if (list_empty(&client->event_list) && - fw_device_is_shutdown(client->device)) - return -ENODEV; + if (fw_device_is_shutdown(client->device)) { + mutex_unlock(&client->mutex); + return -ENODEV; + } - scoped_guard(spinlock_irq, &client->lock) { event = list_first_entry(&client->event_list, struct event, link); list_del(&event->link); + + mutex_unlock(&client->mutex); } - total = 0; - for (i = 0; i < ARRAY_SIZE(event->v) && total < count; i++) { - size = min(event->v[i].size, count - total); - if (copy_to_user(buffer + total, event->v[i].data, size)) { + ssize_t ret = 0; + + for (int i = 0; i < ARRAY_SIZE(event->v) && ret < count; i++) { + size_t size = min(event->v[i].size, count - ret); + if (copy_to_user(buffer + ret, event->v[i].data, size)) { ret = -EFAULT; - goto out; + break; } - total += size; + ret += size; } - ret = total; - out: kfree(event); return ret; @@ -398,19 +408,27 @@ static ssize_t fw_device_op_read(struct file *file, char __user *buffer, static void fill_bus_reset_event(struct fw_cdev_event_bus_reset *event, struct client *client) +__must_hold(&client->device->client_list_mutex) { - struct fw_card *card = client->device->card; - - guard(spinlock_irq)(&card->lock); + lockdep_assert_held(&client->device->client_list_mutex); + // This member is related to the above mutex. In detail, see 93b37905f70 ("firewire: cdev: + // prevent race between first get_info ioctl and bus reset event queuing"). event->closure = client->bus_reset_closure; event->type = FW_CDEV_EVENT_BUS_RESET; + event->generation = client->device->generation; + smp_rmb(); event->node_id = client->device->node_id; - event->local_node_id = card->local_node->node_id; - event->bm_node_id = card->bm_node_id; - event->irm_node_id = card->irm_node->node_id; - event->root_node_id = card->root_node->node_id; + + struct fw_card *card = client->device->card; + + scoped_guard(spinlock_irq, &card->lock) { + event->local_node_id = card->local_node->node_id; + event->bm_node_id = card->bm_node_id; + event->irm_node_id = card->irm_node->node_id; + event->root_node_id = card->root_node->node_id; + } } static void for_each_client(struct fw_device *device, @@ -439,11 +457,11 @@ static void queue_bus_reset_event(struct client *client) queue_event(client, &e->event, &e->reset, sizeof(e->reset), NULL, 0); - guard(spinlock_irq)(&client->lock); - - xa_for_each(&client->resource_xa, index, resource) { - if (is_iso_resource_auto(resource)) - schedule_iso_resource_auto(to_iso_resource_auto(resource), 0); + scoped_guard(mutex, &client->mutex) { + xa_for_each(&client->resource_xa, index, resource) { + if (is_iso_resource_auto(resource)) + schedule_iso_resource_auto(to_iso_resource_auto(resource), 0); + } } } @@ -488,8 +506,6 @@ union ioctl_arg { static int ioctl_get_info(struct client *client, union ioctl_arg *arg) { struct fw_cdev_get_info *a = &arg->get_info; - struct fw_cdev_event_bus_reset bus_reset; - unsigned long ret = 0; client->version = a->version; a->version = FW_CDEV_KERNEL_VERSION; @@ -497,52 +513,58 @@ static int ioctl_get_info(struct client *client, union ioctl_arg *arg) scoped_guard(rwsem_read, &fw_device_rwsem) { if (a->rom != 0) { - size_t want = a->rom_length; - size_t have = client->device->config_rom_length * 4; + size_t length = min_t(size_t, a->rom_length, + client->device->config_rom_length * 4); - ret = copy_to_user(u64_to_uptr(a->rom), client->device->config_rom, - min(want, have)); - if (ret != 0) + if (copy_to_user(u64_to_uptr(a->rom), client->device->config_rom, length)) return -EFAULT; } a->rom_length = client->device->config_rom_length * 4; } - guard(mutex)(&client->device->client_list_mutex); + scoped_guard(mutex, &client->device->client_list_mutex) { + // Coordinate concurrent access to this member with bus reset event handling, see + // 93b37905f70 ("firewire: cdev: prevent race between first get_info ioctl and bus + // reset event queuing"). + client->bus_reset_closure = a->bus_reset_closure; + + if (a->bus_reset != 0) { + struct fw_cdev_event_bus_reset bus_reset; + + memset(&bus_reset, 0, sizeof(bus_reset)); + fill_bus_reset_event(&bus_reset, client); + + // This structure has 4 bytes of trailing padding under the System V ABI + // on most architectures (due to 8-byte alignment of the long long type), + // except for Intel386 (where long long type is aligned to 4 bytes). In + // either case, the effective length is 36 bytes. + if (copy_to_user(u64_to_uptr(a->bus_reset), &bus_reset, 36)) + return -EFAULT; + } - client->bus_reset_closure = a->bus_reset_closure; - if (a->bus_reset != 0) { - fill_bus_reset_event(&bus_reset, client); - /* unaligned size of bus_reset is 36 bytes */ - ret = copy_to_user(u64_to_uptr(a->bus_reset), &bus_reset, 36); + if (list_empty(&client->link)) + list_add_tail(&client->link, &client->device->client_list); } - if (ret == 0 && list_empty(&client->link)) - list_add_tail(&client->link, &client->device->client_list); - return ret ? -EFAULT : 0; + return 0; } static int add_client_resource(struct client *client, struct client_resource *resource, - gfp_t gfp_mask) + client_resource_release_fn_t release) { - scoped_guard(spinlock_irqsave, &client->lock) { + scoped_guard(mutex, &client->mutex) { u32 index; int ret; if (client->in_shutdown) return -ECANCELED; - if (gfpflags_allow_blocking(gfp_mask)) { - ret = xa_alloc(&client->resource_xa, &index, resource, xa_limit_32b, - GFP_NOWAIT); - } else { - ret = xa_alloc_bh(&client->resource_xa, &index, resource, - xa_limit_32b, GFP_NOWAIT); - } + ret = xa_alloc(&client->resource_xa, &index, resource, xa_limit_32b, GFP_KERNEL); if (ret < 0) return ret; resource->handle = index; + resource->release = release; client_get(client); } @@ -556,7 +578,7 @@ static int release_client_resource(struct client *client, u32 handle, unsigned long index = handle; struct client_resource *resource; - scoped_guard(spinlock_irq, &client->lock) { + scoped_guard(mutex, &client->mutex) { if (client->in_shutdown) return -EINVAL; @@ -589,7 +611,7 @@ static void complete_transaction(struct fw_card *card, int rcode, u32 request_ts struct client *client = e->client; unsigned long index = e->r.resource.handle; - scoped_guard(spinlock_irqsave, &client->lock) { + scoped_guard(mutex, &client->mutex) { xa_erase(&client->resource_xa, index); if (client->in_shutdown) wake_up(&client->tx_flush_wait); @@ -661,8 +683,8 @@ static int init_request(struct client *client, request->length < 4) return -EINVAL; - e = kmalloc(sizeof(*e) + request->length, GFP_KERNEL); - if (e == NULL) + e = kzalloc_flex(*e, rsp.with_tstamp.data, DIV_ROUND_UP(request->length, sizeof(u32))); + if (!e) return -ENOMEM; e->client = client; @@ -687,8 +709,7 @@ static int init_request(struct client *client, goto failed; } - e->r.resource.release = release_transaction; - ret = add_client_resource(client, &e->r.resource, GFP_KERNEL); + ret = add_client_resource(client, &e->r.resource, release_transaction); if (ret < 0) goto failed; @@ -760,8 +781,8 @@ static void handle_request(struct fw_card *card, struct fw_request *request, if (is_fcp) fw_request_get(request); - r = kmalloc_obj(*r, GFP_ATOMIC); - e = kmalloc_obj(*e, GFP_ATOMIC); + r = kmalloc_obj(*r); + e = kmalloc_obj(*e); if (r == NULL || e == NULL) goto failed; @@ -771,8 +792,7 @@ static void handle_request(struct fw_card *card, struct fw_request *request, r->data = payload; r->length = length; - r->resource.release = release_request; - ret = add_client_resource(handler->client, &r->resource, GFP_ATOMIC); + ret = add_client_resource(handler->client, &r->resource, release_request); if (ret < 0) goto failed; @@ -875,8 +895,7 @@ static int ioctl_allocate(struct client *client, union ioctl_arg *arg) } a->offset = r->handler.offset; - r->resource.release = release_address_handler; - ret = add_client_resource(client, &r->resource, GFP_KERNEL); + ret = add_client_resource(client, &r->resource, release_address_handler); if (ret < 0) { release_address_handler(client, &r->resource); return ret; @@ -975,8 +994,7 @@ static int ioctl_add_descriptor(struct client *client, union ioctl_arg *arg) if (ret < 0) goto failed; - r->resource.release = release_descriptor; - ret = add_client_resource(client, &r->resource, GFP_KERNEL); + ret = add_client_resource(client, &r->resource, release_descriptor); if (ret < 0) { fw_core_remove_descriptor(&r->descriptor); goto failed; @@ -1002,7 +1020,7 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle, struct client *client = data; struct iso_interrupt_event *e; - e = kmalloc(sizeof(*e) + header_length, GFP_KERNEL); + e = kzalloc_flex(*e, interrupt.header, header_length); if (e == NULL) return; @@ -1327,30 +1345,27 @@ static void iso_resource_auto_work(struct work_struct *work) struct iso_resource_auto *r = from_work(r, work, work.work); struct client *client = r->client; unsigned long index = r->resource.handle; - int current_generation, resource_generation, channel, bandwidth, todo; - u64 reset_jiffies; + int channel, bandwidth; bool free; - scoped_guard(spinlock_irq, &client->lock) { - reset_jiffies = client->device->card->reset_jiffies; - current_generation = client->device->generation; - resource_generation = r->generation; - r->generation = current_generation; - todo = r->todo; - } + u64 reset_jiffies = client->device->card->reset_jiffies; + int current_generation = client->device->generation; + + int resource_generation = xchg(&r->generation, current_generation); // But no need to be atomic. + int todo = atomic_read(&r->todo); switch (todo) { case ISO_RES_AUTO_ALLOC: // Allow 1000ms grace period for other reallocations. if (time_is_after_jiffies64(reset_jiffies + secs_to_jiffies(1))) { schedule_iso_resource_auto(r, msecs_to_jiffies(333)); - goto out; + return; } break; case ISO_RES_AUTO_REALLOC: // We could be called twice within the same generation. if (resource_generation == current_generation) - goto out; + return; break; case ISO_RES_AUTO_DEALLOC: default: @@ -1364,23 +1379,23 @@ static void iso_resource_auto_work(struct work_struct *work) if (todo == ISO_RES_AUTO_DEALLOC) { free = true; - e = r->e_dealloc; - r->e_dealloc = NULL; + e = xchg(&r->e_dealloc, NULL); // But no need to be atomic. } else { free = false; // Is this generation outdated already? As long as this resource sticks in the // xarray, it will be scheduled again for a newer generation or at shutdown. if (channel == -EAGAIN) - goto out; + return; bool success = channel >= 0 || bandwidth > 0; if (!success) { // Allocation or reallocation failure? Pull this resource out of the // xarray and prepare for deletion, unless the client is shutting down. - scoped_guard(spinlock_irq, &client->lock) { + scoped_guard(mutex, &client->mutex) { if (!client->in_shutdown && xa_erase(&client->resource_xa, index)) { + // For the incrementation by add_client_resource(). client_put(client); free = true; } @@ -1389,24 +1404,20 @@ static void iso_resource_auto_work(struct work_struct *work) if (todo == ISO_RES_AUTO_REALLOC) { if (success) - goto out; + return; // Notify the userspace client of the failure through a deallocation event. - e = r->e_dealloc; - r->e_dealloc = NULL; + e = xchg(&r->e_dealloc, NULL); // But no need to be atomic. } else { - // Transit from allocation to reallocation, except if the client requested - // deallocation in the meantime. - scoped_guard(spinlock_irq, &client->lock) { - if (r->todo == ISO_RES_AUTO_ALLOC) - r->todo = ISO_RES_AUTO_REALLOC; - } + // Transit from allocation to reallocation. Use compare-and-swap atomic + // operation because the todo member can be set with ISO_RES_AUTO_DEALLOC + // by release_iso_resource_auto() in parallel. + atomic_cmpxchg_relaxed(&r->todo, ISO_RES_AUTO_ALLOC, ISO_RES_AUTO_REALLOC); if (channel >= 0) r->params.channels_mask = BIT_ULL(channel); - e = r->e_alloc; - r->e_alloc = NULL; + e = xchg(&r->e_alloc, NULL); // But no need to be atomic. } } @@ -1422,18 +1433,17 @@ static void iso_resource_auto_work(struct work_struct *work) kfree(r->e_alloc); kfree(r->e_dealloc); kfree(r); + + // For the incrementation by ioctl_allocate_iso_resource(). + client_put(client); } - out: - client_put(client); } static void release_iso_resource_auto(struct client *client, struct client_resource *resource) { struct iso_resource_auto *r = to_iso_resource_auto(resource); - guard(spinlock_irq)(&client->lock); - - r->todo = ISO_RES_AUTO_DEALLOC; + atomic_set(&r->todo, ISO_RES_AUTO_DEALLOC); schedule_iso_resource_auto(r, 0); } @@ -1454,7 +1464,7 @@ static int ioctl_allocate_iso_resource(struct client *client, union ioctl_arg *a INIT_DELAYED_WORK(&r->work, iso_resource_auto_work); r->client = client; - r->todo = ISO_RES_AUTO_ALLOC; + atomic_set(&r->todo, ISO_RES_AUTO_ALLOC); r->e_alloc = e1; r->e_dealloc = e2; @@ -1463,11 +1473,11 @@ static int ioctl_allocate_iso_resource(struct client *client, union ioctl_arg *a e2->iso_resource.closure = request->closure; e2->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED; - r->resource.release = release_iso_resource_auto; - err = add_client_resource(client, &r->resource, GFP_KERNEL); + err = add_client_resource(client, &r->resource, release_iso_resource_auto); if (err < 0) return err; request->handle = r->resource.handle; + client_get(client); retain_and_null_ptr(e1); retain_and_null_ptr(e2); @@ -1490,12 +1500,10 @@ static void iso_resource_once_work(struct work_struct *work) struct iso_resource_once *r = from_work(r, work, work); struct client *client = r->client; struct iso_resource_event *e = r->event; - int generation, channel, bandwidth; - - scoped_guard(spinlock_irq, &client->lock) - generation = client->device->generation; + int channel; - bandwidth = r->params.bandwidth; + int generation = client->device->generation; + int bandwidth = r->params.bandwidth; fw_iso_resource_manage(client->device->card, generation, r->params.channels_mask, &channel, &bandwidth, r->todo == ISO_RES_ONCE_ALLOC); @@ -1688,7 +1696,7 @@ static int ioctl_send_phy_packet(struct client *client, union ioctl_arg *arg) if (!client->device->is_local) return -ENOSYS; - e = kzalloc(sizeof(*e) + sizeof(a->data), GFP_KERNEL); + e = kzalloc_flex(*e, phy_packet.with_tstamp.data, ARRAY_SIZE(a->data)); if (e == NULL) return -ENOMEM; @@ -1723,7 +1731,7 @@ static int ioctl_send_phy_packet(struct client *client, union ioctl_arg *arg) trace_async_phy_outbound_initiate((uintptr_t)&e->p, card->index, e->p.generation, e->p.header[1], e->p.header[2]); - card->driver->send_request(card, &e->p); + smp_load_acquire(&card->driver)->send_request(card, &e->p); return 0; } @@ -1736,9 +1744,7 @@ static int ioctl_receive_phy_packets(struct client *client, union ioctl_arg *arg if (!client->device->is_local) return -ENOSYS; - // NOTE: This can be without irq when we can guarantee that __fw_send_request() for local - // destination never runs in any type of IRQ context. - scoped_guard(spinlock_irq, &phy_receiver_list_lock) + scoped_guard(mutex, &phy_receiver_list_mutex) list_move_tail(&client->phy_receiver_link, &phy_receiver_list); client->phy_receiver_closure = a->closure; @@ -1750,18 +1756,15 @@ void fw_cdev_handle_phy_packet(struct fw_card *card, struct fw_packet *p) { struct client *client; - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for local - // destination never runs in any type of IRQ context. - guard(spinlock_irqsave)(&phy_receiver_list_lock); + guard(mutex)(&phy_receiver_list_mutex); list_for_each_entry(client, &phy_receiver_list, phy_receiver_link) { - struct inbound_phy_packet_event *e; - if (client->device->card != card) continue; - e = kmalloc(sizeof(*e) + 8, GFP_ATOMIC); - if (e == NULL) + struct inbound_phy_packet_event *e = + kzalloc_flex(*e, phy_packet.with_tstamp.data, 2); + if (!e) break; if (client->version < FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP) { @@ -1911,11 +1914,11 @@ static bool has_outbound_transactions(struct client *client) struct client_resource *resource; unsigned long index; - guard(spinlock_irq)(&client->lock); - - xa_for_each(&client->resource_xa, index, resource) { - if (is_outbound_transaction_resource(resource)) - return true; + scoped_guard(mutex, &client->mutex) { + xa_for_each(&client->resource_xa, index, resource) { + if (is_outbound_transaction_resource(resource)) + return true; + } } return false; @@ -1928,9 +1931,7 @@ static int fw_device_op_release(struct inode *inode, struct file *file) struct client_resource *resource; unsigned long index; - // NOTE: This can be without irq when we can guarantee that __fw_send_request() for local - // destination never runs in any type of IRQ context. - scoped_guard(spinlock_irq, &phy_receiver_list_lock) + scoped_guard(mutex, &phy_receiver_list_mutex) list_del(&client->phy_receiver_link); scoped_guard(mutex, &client->device->client_list_mutex) @@ -1944,7 +1945,7 @@ static int fw_device_op_release(struct inode *inode, struct file *file) fw_iso_buffer_destroy(&client->buffer, client->device->card); // Freeze client->resource_xa and client->event_list. - scoped_guard(spinlock_irq, &client->lock) + scoped_guard(mutex, &client->mutex) client->in_shutdown = true; wait_event(client->tx_flush_wait, !has_outbound_transactions(client)); diff --git a/drivers/firewire/core-device.c b/drivers/firewire/core-device.c index cbac66916240..a68100d3da0b 100644 --- a/drivers/firewire/core-device.c +++ b/drivers/firewire/core-device.c @@ -261,14 +261,14 @@ EXPORT_SYMBOL(fw_bus_type); int fw_device_enable_phys_dma(struct fw_device *device) { - int generation = device->generation; + struct fw_card *card = device->card; /* device->node_id, accessed below, must not be older than generation */ + int generation = device->generation; smp_rmb(); + int node_id = device->node_id; - return device->card->driver->enable_phys_dma(device->card, - device->node_id, - generation); + return smp_load_acquire(&card->driver)->enable_phys_dma(device->card, node_id, generation); } EXPORT_SYMBOL(fw_device_enable_phys_dma); @@ -1442,3 +1442,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event) #ifdef CONFIG_FIREWIRE_KUNIT_DEVICE_ATTRIBUTE_TEST #include "device-attribute-test.c" #endif + +#ifdef CONFIG_FIREWIRE_KUNIT_CONFIG_ROM_PARSER_AND_GENERATOR_TEST +#include "config-rom-parser-test.c" +#endif diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index d2b40a1a565e..fd6cbbf79d0b 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -144,8 +144,8 @@ struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, i { struct fw_iso_context *ctx; - ctx = card->driver->allocate_iso_context(card, type, channel, header_size, - header_storage_size); + ctx = smp_load_acquire(&card->driver)->allocate_iso_context(card, type, channel, + header_size, header_storage_size); if (IS_ERR(ctx)) return ctx; @@ -173,7 +173,9 @@ void fw_iso_context_destroy(struct fw_iso_context *ctx) trace_isoc_inbound_single_destroy(ctx); trace_isoc_inbound_multiple_destroy(ctx); - ctx->card->driver->free_iso_context(ctx); + struct fw_card *card = ctx->card; + + smp_load_acquire(&card->driver)->free_iso_context(ctx); } EXPORT_SYMBOL(fw_iso_context_destroy); @@ -184,7 +186,9 @@ int fw_iso_context_start(struct fw_iso_context *ctx, trace_isoc_inbound_single_start(ctx, cycle, sync, tags); trace_isoc_inbound_multiple_start(ctx, cycle, sync, tags); - return ctx->card->driver->start_iso(ctx, cycle, sync, tags); + struct fw_card *card = ctx->card; + + return smp_load_acquire(&card->driver)->start_iso(ctx, cycle, sync, tags); } EXPORT_SYMBOL(fw_iso_context_start); @@ -192,7 +196,9 @@ int fw_iso_context_set_channels(struct fw_iso_context *ctx, u64 *channels) { trace_isoc_inbound_multiple_channels(ctx, *channels); - return ctx->card->driver->set_iso_channels(ctx, channels); + struct fw_card *card = ctx->card; + + return smp_load_acquire(&card->driver)->set_iso_channels(ctx, channels); } int fw_iso_context_queue(struct fw_iso_context *ctx, @@ -204,7 +210,9 @@ int fw_iso_context_queue(struct fw_iso_context *ctx, trace_isoc_inbound_single_queue(ctx, payload, packet); trace_isoc_inbound_multiple_queue(ctx, payload, packet); - return ctx->card->driver->queue_iso(ctx, packet, buffer, payload); + struct fw_card *card = ctx->card; + + return smp_load_acquire(&card->driver)->queue_iso(ctx, packet, buffer, payload); } EXPORT_SYMBOL(fw_iso_context_queue); @@ -214,7 +222,9 @@ void fw_iso_context_queue_flush(struct fw_iso_context *ctx) trace_isoc_inbound_single_flush(ctx); trace_isoc_inbound_multiple_flush(ctx); - ctx->card->driver->flush_queue_iso(ctx); + struct fw_card *card = ctx->card; + + smp_load_acquire(&card->driver)->flush_queue_iso(ctx); } EXPORT_SYMBOL(fw_iso_context_queue_flush); @@ -248,7 +258,9 @@ int fw_iso_context_flush_completions(struct fw_iso_context *ctx) disable_work_sync(&ctx->work); - err = ctx->card->driver->flush_iso_completions(ctx); + struct fw_card *card = ctx->card; + + err = smp_load_acquire(&card->driver)->flush_iso_completions(ctx); enable_work(&ctx->work); @@ -270,7 +282,9 @@ int fw_iso_context_stop(struct fw_iso_context *ctx) if (WARN_ON_ONCE(current_work() == &ctx->work)) return 0; - err = ctx->card->driver->stop_iso(ctx); + struct fw_card *card = ctx->card; + + err = smp_load_acquire(&card->driver)->stop_iso(ctx); cancel_work_sync(&ctx->work); diff --git a/drivers/firewire/core-topology.c b/drivers/firewire/core-topology.c index ee6b54f89859..512b7e4473af 100644 --- a/drivers/firewire/core-topology.c +++ b/drivers/firewire/core-topology.c @@ -474,9 +474,12 @@ static void update_topology_map(__be32 *buffer, size_t buffer_size, int root_nod void fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, int self_id_count, u32 *self_ids, bool bm_abdicate) +__must_not_hold(&card->topology_map.lock) { struct fw_node *local_node; + lockdep_assert_not_held(&card->topology_map.lock); + trace_bus_reset_handle(card->index, generation, node_id, bm_abdicate, self_ids, self_id_count); scoped_guard(spinlock, &card->lock) { diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c index 016070560c74..0ebe70a4d440 100644 --- a/drivers/firewire/core-transaction.c +++ b/drivers/firewire/core-transaction.c @@ -24,6 +24,7 @@ #include <linux/timer.h> #include <linux/types.h> #include <linux/workqueue.h> +#include <kunit/static_stub.h> #include <asm/byteorder.h> @@ -35,151 +36,165 @@ #define HEADER_DESTINATION_IS_BROADCAST(header) \ ((async_header_get_destination(header) & 0x3f) == 0x3f) -/* returns 0 if the split timeout handler is already running */ +// Returns 0 if the split timeout handler is already running. +// +// card->transactions.lock must be acquired in advance. static int try_cancel_split_timeout(struct fw_transaction *t) { + lockdep_assert_held(&t->card->transactions.lock); + if (t->is_split_transaction) return timer_delete(&t->split_timeout_timer); else return 1; } -// card->transactions.lock must be acquired in advance. static void remove_transaction_entry(struct fw_card *card, struct fw_transaction *entry) +__must_hold(&card->transactions.lock) { + lockdep_assert_held(&card->transactions.lock); + list_del_init(&entry->link); card->transactions.tlabel_mask &= ~(1ULL << entry->tlabel); } -// Must be called without holding card->transactions.lock. -void fw_cancel_pending_transactions(struct fw_card *card) +static void invoke_callback(struct fw_transaction *t, int rcode, u32 response_tstamp, void *data, + size_t data_length) { - struct fw_transaction *t, *tmp; - LIST_HEAD(pending_list); - - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. - scoped_guard(spinlock_irqsave, &card->transactions.lock) { - list_for_each_entry_safe(t, tmp, &card->transactions.list, link) { - if (try_cancel_split_timeout(t)) - list_move(&t->link, &pending_list); - } - } - - list_for_each_entry_safe(t, tmp, &pending_list, link) { - list_del(&t->link); - - if (!t->with_tstamp) { - t->callback.without_tstamp(card, RCODE_CANCELLED, NULL, 0, - t->callback_data); - } else { - t->callback.with_tstamp(card, RCODE_CANCELLED, t->packet.timestamp, 0, - NULL, 0, t->callback_data); - } + if (!t->with_tstamp) { + t->callback.without_tstamp(t->card, rcode, data, data_length, t->callback_data); + } else { + t->callback.with_tstamp(t->card, rcode, t->packet.timestamp, response_tstamp, + data, data_length, t->callback_data); } } // card->transactions.lock must be acquired in advance. #define find_and_pop_transaction_entry(card, condition) \ ({ \ - struct fw_transaction *iter, *t = NULL; \ + struct fw_transaction *iter, *__t = NULL; \ list_for_each_entry(iter, &card->transactions.list, link) { \ if (condition) { \ - t = iter; \ + __t = iter; \ break; \ } \ } \ - if (t) { \ - if (try_cancel_split_timeout(t)) \ - remove_transaction_entry(card, t); \ + if (__t) { \ + if (try_cancel_split_timeout(__t)) \ + remove_transaction_entry(card, __t); \ else \ - t = NULL; \ + __t = NULL; \ } \ - t; \ + __t; \ }) -static int close_transaction(struct fw_transaction *transaction, struct fw_card *card, int rcode, - u32 response_tstamp) +// Ensure that the transaction is not on the pending list when scheduling this work. Cancelling +// the work after scheduling makes no sense, as the initiator must be notified of the transaction +// result. +static void schedule_error_callback(struct fw_card *card, struct fw_transaction *t, int rcode, + u32 response_timestamp) +__must_hold(&card->transactions.lock) { - struct fw_transaction *t; + lockdep_assert_held(&card->transactions.lock); - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. - scoped_guard(spinlock_irqsave, &card->transactions.lock) { - t = find_and_pop_transaction_entry(card, iter == transaction); - if (!t) - return -ENOENT; - } + // To detect programming mistakes. + WARN_ON(!list_empty(&t->link)); - if (!t->with_tstamp) { - t->callback.without_tstamp(card, rcode, NULL, 0, t->callback_data); - } else { - t->callback.with_tstamp(card, rcode, t->packet.timestamp, response_tstamp, NULL, 0, - t->callback_data); - } + t->rcode = rcode; + t->response_timestamp = response_timestamp; - return 0; + queue_work(t->card->async_wq, &t->error_work); } -/* - * Only valid for transactions that are potentially pending (ie have - * been sent). +void fw_cancel_pending_transactions(struct fw_card *card) +__must_hold(&card->transactions.lock) +{ + struct fw_transaction *t, *tmp; + + lockdep_assert_held(&card->transactions.lock); + + list_for_each_entry_safe(t, tmp, &card->transactions.list, link) { + if (try_cancel_split_timeout(t)) { + remove_transaction_entry(card, t); + schedule_error_callback(card, t, RCODE_CANCELLED, 0); + } + } +} + +/** + * fw_cancel_transaction: cancel transaction. + * @card: interface where the transaction has been requested + * @transaction: transaction instance to be cancelled + * + * Terminate the transaction by either canceling the request subaction or the response subaction + * of the split transaction, then schedule the transaction callback to notify the cancellation. + * The transaction instance should be kept until the callback will be invoked. + * + * Context: Process context. + * Returns: 0 on success, -ENOENT if the transaction instance is not found in the queue for the + * pending transaction. */ -int fw_cancel_transaction(struct fw_card *card, - struct fw_transaction *transaction) +int fw_cancel_transaction(struct fw_card *card, struct fw_transaction *transaction) +__must_not_hold(&card->transactions.lock) { - u32 tstamp; + lockdep_assert_not_held(&card->transactions.lock); - /* - * Cancel the packet transmission if it's still queued. That - * will call the packet transmission callback which cancels - * the transaction. - */ + // Allow the call of struct fw_card_driver.cancel_packet() to wait for any event. + might_sleep(); - if (card->driver->cancel_packet(card, &transaction->packet) == 0) + // Cancel the packet transmission if it's still queued. That will schedule the packet + // transmission callback which cancels the transaction. + if (smp_load_acquire(&card->driver)->cancel_packet(card, &transaction->packet) == 0) return 0; - /* - * If the request packet has already been sent, we need to see - * if the transaction is still pending and remove it in that case. - */ + u32 curr_cycle_time = 0; - if (transaction->packet.ack == 0) { - // The timestamp is reused since it was just read now. - tstamp = transaction->packet.timestamp; - } else { - u32 curr_cycle_time = 0; + // Timestamping on behalf of hardware. + (void)fw_card_read_cycle_time(card, &curr_cycle_time); - (void)fw_card_read_cycle_time(card, &curr_cycle_time); - tstamp = cycle_time_to_ohci_tstamp(curr_cycle_time); + u32 tstamp = cycle_time_to_ohci_tstamp(curr_cycle_time); + + // If the request packet has already been sent, we need to see if the transaction is still + // pending and remove it in that case (e.g. the split transaction). + scoped_guard(spinlock_irqsave, &card->transactions.lock) { + if (!find_and_pop_transaction_entry(card, iter == transaction)) + return -ENOENT; + schedule_error_callback(card, transaction, RCODE_CANCELLED, tstamp); } - return close_transaction(transaction, card, RCODE_CANCELLED, tstamp); + return 0; } EXPORT_SYMBOL(fw_cancel_transaction); +static void error_callback_work(struct work_struct *work) +{ + struct fw_transaction *t = from_work(t, work, error_work); + + invoke_callback(t, t->rcode, t->response_timestamp, NULL, 0); +} + +// card->transactions.lock must be released in advance. static void split_transaction_timeout_callback(struct timer_list *timer) { struct fw_transaction *t = timer_container_of(t, timer, split_timeout_timer); struct fw_card *card = t->card; - scoped_guard(spinlock_irqsave, &card->transactions.lock) { + lockdep_assert_not_held(&card->transactions.lock); + + scoped_guard(spinlock, &card->transactions.lock) { if (list_empty(&t->link)) return; remove_transaction_entry(card, t); + schedule_error_callback(card, t, RCODE_CANCELLED, t->split_timeout_cycle); } - if (!t->with_tstamp) { - t->callback.without_tstamp(card, RCODE_CANCELLED, NULL, 0, t->callback_data); - } else { - t->callback.with_tstamp(card, RCODE_CANCELLED, t->packet.timestamp, - t->split_timeout_cycle, NULL, 0, t->callback_data); - } } // card->transactions.lock should be acquired in advance for the linked list. static void start_split_transaction_timeout(struct fw_transaction *t, unsigned int delta) { + lockdep_assert_held(&t->card->transactions.lock); + if (list_empty(&t->link) || WARN_ON(t->is_split_transaction)) return; @@ -192,54 +207,59 @@ static u32 compute_split_timeout_timestamp(struct fw_card *card, u32 request_tim static void transmit_complete_callback(struct fw_packet *packet, struct fw_card *card, int status) +__must_not_hold(&card->split_timeout.lock) +__must_not_hold(&card->transactions.lock) { struct fw_transaction *t = container_of(packet, struct fw_transaction, packet); + lockdep_assert_not_held(&card->split_timeout.lock); + lockdep_assert_not_held(&card->transactions.lock); + trace_async_request_outbound_complete((uintptr_t)t, card->index, packet->generation, packet->speed, status, packet->timestamp); switch (status) { - case ACK_COMPLETE: - close_transaction(t, card, RCODE_COMPLETE, packet->timestamp); - break; case ACK_PENDING: { unsigned int delta; - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. scoped_guard(spinlock_irqsave, &card->split_timeout.lock) { t->split_timeout_cycle = compute_split_timeout_timestamp(card, packet->timestamp) & 0xffff; delta = card->split_timeout.jiffies; } - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. scoped_guard(spinlock_irqsave, &card->transactions.lock) start_split_transaction_timeout(t, delta); - break; + return; } + case ACK_COMPLETE: + status = RCODE_COMPLETE; + break; case ACK_BUSY_X: case ACK_BUSY_A: case ACK_BUSY_B: - close_transaction(t, card, RCODE_BUSY, packet->timestamp); + status = RCODE_BUSY; break; case ACK_DATA_ERROR: - close_transaction(t, card, RCODE_DATA_ERROR, packet->timestamp); + status = RCODE_DATA_ERROR; break; case ACK_TYPE_ERROR: - close_transaction(t, card, RCODE_TYPE_ERROR, packet->timestamp); + status = RCODE_TYPE_ERROR; break; default: - /* - * In this case the ack is really a juju specific - * rcode, so just forward that to the callback. - */ - close_transaction(t, card, status, packet->timestamp); + // In this case the ack is really a juju specific rcode, so just forward that to + // the callback. break; } + + scoped_guard(spinlock_irqsave, &card->transactions.lock) { + if (!find_and_pop_transaction_entry(card, iter == t)) + return; + } + + invoke_callback(t, status, packet->timestamp, NULL, 0); } static void fw_fill_request(struct fw_packet *packet, int tcode, int tlabel, @@ -370,8 +390,8 @@ __must_hold(&card->transactions.lock) * * In case of request types without payload, @data is NULL and @length is 0. * - * After the transaction is completed successfully or unsuccessfully, the - * @callback will be called. Among its parameters is the response code which + * After the transaction is completed successfully or unsuccessfully, the @callback will be called + * in process context. Among its parameters is the response code which * is either one of the rcodes per IEEE 1394 or, in case of internal errors, * the firewire-core specific %RCODE_SEND_ERROR. The other firewire-core * specific rcodes (%RCODE_CANCELLED, %RCODE_BUSY, %RCODE_GENERATION, @@ -382,60 +402,62 @@ __must_hold(&card->transactions.lock) * than when the request packet actually hits the wire. On the other hand, * transaction completion and hence execution of @callback may happen even * before fw_send_request() returns. + * + * Context: Any context. */ void __fw_send_request(struct fw_card *card, struct fw_transaction *t, int tcode, int destination_id, int generation, int speed, unsigned long long offset, void *payload, size_t length, union fw_transaction_callback callback, bool with_tstamp, void *callback_data) +__must_not_hold(&card->transactions.lock) { int tlabel; + lockdep_assert_not_held(&card->transactions.lock); + + t->card = card; + t->callback = callback; + t->with_tstamp = with_tstamp; + t->callback_data = callback_data; + INIT_WORK(&t->error_work, error_callback_work); + INIT_LIST_HEAD(&t->link); + /* * Allocate tlabel from the bitmap and put the transaction on * the list while holding the card spinlock. */ - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. scoped_guard(spinlock_irqsave, &card->transactions.lock) tlabel = allocate_tlabel(card); if (tlabel < 0) { - if (!with_tstamp) { - callback.without_tstamp(card, RCODE_SEND_ERROR, NULL, 0, callback_data); - } else { - // Timestamping on behalf of hardware. - u32 curr_cycle_time = 0; - u32 tstamp; + // Timestamping on behalf of hardware. + u32 curr_cycle_time = 0; + u32 tstamp; - (void)fw_card_read_cycle_time(card, &curr_cycle_time); - tstamp = cycle_time_to_ohci_tstamp(curr_cycle_time); + (void)fw_card_read_cycle_time(card, &curr_cycle_time); + tstamp = cycle_time_to_ohci_tstamp(curr_cycle_time); + + t->packet.timestamp = tstamp; + scoped_guard(spinlock_irqsave, &card->transactions.lock) + schedule_error_callback(card, t, RCODE_SEND_ERROR, tstamp); - callback.with_tstamp(card, RCODE_SEND_ERROR, tstamp, tstamp, NULL, 0, - callback_data); - } return; } t->node_id = destination_id; t->tlabel = tlabel; - t->card = card; t->is_split_transaction = false; timer_setup(&t->split_timeout_timer, split_transaction_timeout_callback, 0); - t->callback = callback; - t->with_tstamp = with_tstamp; - t->callback_data = callback_data; t->packet.callback = transmit_complete_callback; - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. - scoped_guard(spinlock_irqsave, &card->lock) { - // The node_id field of fw_card can be updated when handling SelfIDComplete. - fw_fill_request(&t->packet, tcode, t->tlabel, destination_id, card->node_id, - generation, speed, offset, payload, length); - } + // The node_id field of fw_card can be updated when handling SelfIDComplete. + int node_id; + + scoped_guard(spinlock_irqsave, &card->lock) + node_id = card->node_id; + fw_fill_request(&t->packet, tcode, t->tlabel, destination_id, node_id, generation, speed, + offset, payload, length); - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. scoped_guard(spinlock_irqsave, &card->transactions.lock) list_add_tail(&t->link, &card->transactions.list); @@ -444,7 +466,7 @@ void __fw_send_request(struct fw_card *card, struct fw_transaction *t, int tcode t->packet.header, payload, tcode_is_read_request(tcode) ? 0 : length / 4); - card->driver->send_request(card, &t->packet); + smp_load_acquire(&card->driver)->send_request(card, &t->packet); } EXPORT_SYMBOL_GPL(__fw_send_request); @@ -485,9 +507,15 @@ int fw_run_transaction(struct fw_card *card, int tcode, int destination_id, int generation, int speed, unsigned long long offset, void *payload, size_t length) { + KUNIT_STATIC_STUB_REDIRECT(fw_run_transaction, card, tcode, destination_id, generation, + speed, offset, payload, length); + struct transaction_callback_data d; struct fw_transaction t; + // Due to the call of wait_for_completion(). + might_sleep(); + timer_setup_on_stack(&t.split_timeout_timer, NULL, 0); init_completion(&d.done); d.payload = payload; @@ -524,6 +552,9 @@ void fw_send_phy_config(struct fw_card *card, long timeout = msecs_to_jiffies(100); u32 data = 0; + // Due to the call of wait_for_completion_timeout(). + might_sleep(); + phy_packet_set_packet_identifier(&data, PHY_PACKET_PACKET_IDENTIFIER_PHY_CONFIG); if (node_id != FW_PHY_CONFIG_NO_NODE_ID) { @@ -532,7 +563,7 @@ void fw_send_phy_config(struct fw_card *card, } if (gap_count == FW_PHY_CONFIG_CURRENT_GAP_COUNT) { - gap_count = card->driver->read_phy_reg(card, 1); + gap_count = smp_load_acquire(&card->driver)->read_phy_reg(card, 1); if (gap_count < 0) return; @@ -555,7 +586,7 @@ void fw_send_phy_config(struct fw_card *card, phy_config_packet.generation, phy_config_packet.header[1], phy_config_packet.header[2]); - card->driver->send_request(card, &phy_config_packet); + smp_load_acquire(&card->driver)->send_request(card, &phy_config_packet); wait_for_completion_timeout(&phy_config_done, timeout); } @@ -596,6 +627,12 @@ static struct fw_address_handler *lookup_enclosing_address_handler( static DEFINE_SPINLOCK(address_handler_list_lock); static LIST_HEAD(address_handler_list); +/** + * var fw_high_memory_region - An address range for high memory. + * + * Represents an address range within the Middle Address Space defined by the 1394 OHCI + * specification. + */ const struct fw_address_region fw_high_memory_region = { .start = FW_MAX_PHYSICAL_RANGE, .end = 0xffffe0000000ULL, }; EXPORT_SYMBOL(fw_high_memory_region); @@ -631,24 +668,21 @@ static int put_address_handler(struct fw_address_handler *handler) } /** - * fw_core_add_address_handler() - register for incoming requests - * @handler: callback - * @region: region in the IEEE 1212 node space address range + * fw_core_add_address_handler() - register a handler for incoming transaction requests to a + * specific address region. + * @handler: The handler including a callback function for an incoming transaction request. + * @region: The region in the IEEE 1212 node space address range. * - * region->start, ->end, and handler->length have to be quadlet-aligned. + * &fw_address_handler.length, &fw_address_region.start, and &fw_address_region.end are specified + * in bytes and must be multiples of a quadlet. &fw_address_handler.offset is available after + * successful registration. * * When a request is received that falls within the specified address range, the specified callback - * is invoked. The parameters passed to the callback give the details of the particular request. - * The callback is invoked in the workqueue context in most cases. However, if the request is - * initiated by the local node, the callback is invoked in the initiator's context. - * - * To be called in process context. - * Return value: 0 on success, non-zero otherwise. - * - * The start offset of the handler's address region is determined by - * fw_core_add_address_handler() and is returned in handler->offset. + * is invoked. The parameters passed to the callback give the details of the particular request. * * Address allocations are exclusive, except for the FCP registers. + * + * Returns: 0 on success, non-zero otherwise. */ int fw_core_add_address_handler(struct fw_address_handler *handler, const struct fw_address_region *region) @@ -689,16 +723,19 @@ int fw_core_add_address_handler(struct fw_address_handler *handler, EXPORT_SYMBOL(fw_core_add_address_handler); /** - * fw_core_remove_address_handler() - unregister an address handler - * @handler: callback + * fw_core_remove_address_handler() - unregister an address handler for an incoming transaction + * request. + * @handler: The registered handler. * - * To be called in process context. + * After this function returns, the handler's callback is guaranteed not to be running on any CPU. * - * When fw_core_remove_address_handler() returns, @handler->callback() is - * guaranteed to not run on any CPU anymore. + * Context: Process context. */ void fw_core_remove_address_handler(struct fw_address_handler *handler) { + // Due to synchronize_rcu(). + might_sleep(); + scoped_guard(spinlock, &address_handler_list_lock) list_del_rcu(&handler->link); @@ -861,11 +898,14 @@ __must_hold(&card->split_timeout.lock) static struct fw_request *allocate_request(struct fw_card *card, struct fw_packet *p) +__must_not_hold(&card->split_timeout.lock) { struct fw_request *request; u32 *data, length; int request_tcode; + lockdep_assert_not_held(&card->split_timeout.lock); + request_tcode = async_header_get_tcode(p->header); switch (request_tcode) { case TCODE_WRITE_QUADLET_REQUEST: @@ -895,14 +935,12 @@ static struct fw_request *allocate_request(struct fw_card *card, return NULL; } - request = kmalloc(sizeof(*request) + length, GFP_ATOMIC); - if (request == NULL) + request = kzalloc_flex(*request, data, DIV_ROUND_UP(length, sizeof(u32))); + if (!request) return NULL; kref_init(&request->kref); - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. - scoped_guard(spinlock_irqsave, &card->split_timeout.lock) + scoped_guard(spinlock, &card->split_timeout.lock) request->response.timestamp = compute_split_timeout_timestamp(card, p->timestamp); request->response.speed = p->speed; @@ -926,8 +964,8 @@ static struct fw_request *allocate_request(struct fw_card *card, * @request: firewire request data for the transaction. * @rcode: response code to send. * - * Submit a response packet into the asynchronous response transmission queue. The @request - * is going to be released when the transmission successfully finishes later. + * Submit a response packet into the asynchronous response transmission queue. The contents of + * @request are opaque to the caller. The @request is released when the transmission finishes. */ void fw_send_response(struct fw_card *card, struct fw_request *request, int rcode) @@ -957,7 +995,7 @@ void fw_send_response(struct fw_card *card, request->response.header, data, data ? data_length / 4 : 0); - card->driver->send_response(card, &request->response); + smp_load_acquire(&card->driver)->send_response(card, &request->response); } EXPORT_SYMBOL(fw_send_response); @@ -1108,6 +1146,10 @@ void fw_core_handle_request(struct fw_card *card, struct fw_packet *p) unsigned long long offset; unsigned int tcode; + // Allow the call of allocate_request() to perform object allocation with GFP_KERNEL, + // as well as the address handlers to work with mutexes. + might_sleep(); + if (p->ack != ACK_PENDING && p->ack != ACK_COMPLETE) return; @@ -1140,12 +1182,18 @@ void fw_core_handle_request(struct fw_card *card, struct fw_packet *p) EXPORT_SYMBOL(fw_core_handle_request); void fw_core_handle_response(struct fw_card *card, struct fw_packet *p) +__must_not_hold(&card->transactions.lock) { struct fw_transaction *t = NULL; u32 *data; size_t data_length; int tcode, tlabel, source, rcode; + lockdep_assert_not_held(&card->transactions.lock); + + // Allow the call of struct fw_card_driver.cancel_packet() to wait for any event. + might_sleep(); + tcode = async_header_get_tcode(p->header); tlabel = async_header_get_tlabel(p->header); source = async_header_get_source(p->header); @@ -1180,8 +1228,6 @@ void fw_core_handle_response(struct fw_card *card, struct fw_packet *p) break; } - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. scoped_guard(spinlock_irqsave, &card->transactions.lock) { t = find_and_pop_transaction_entry(card, iter->node_id == source && iter->tlabel == tlabel); @@ -1200,14 +1246,9 @@ void fw_core_handle_response(struct fw_card *card, struct fw_packet *p) * The response handler may be executed while the request handler * is still pending. Cancel the request handler. */ - card->driver->cancel_packet(card, &t->packet); + smp_load_acquire(&card->driver)->cancel_packet(card, &t->packet); - if (!t->with_tstamp) { - t->callback.without_tstamp(card, rcode, data, data_length, t->callback_data); - } else { - t->callback.with_tstamp(card, rcode, t->packet.timestamp, p->timestamp, data, - data_length, t->callback_data); - } + invoke_callback(t, rcode, p->timestamp, data, data_length); } EXPORT_SYMBOL(fw_core_handle_response); @@ -1245,9 +1286,12 @@ static void handle_topology_map(struct fw_card *card, struct fw_request *request int tcode, int destination, int source, int generation, unsigned long long offset, void *payload, size_t length, void *callback_data) +__must_not_hold(&card->topology_map.lock) { int start; + lockdep_assert_not_held(&card->topology_map.lock); + if (!tcode_is_read_request(tcode)) { fw_send_response(card, request, RCODE_TYPE_ERROR); return; @@ -1260,9 +1304,7 @@ static void handle_topology_map(struct fw_card *card, struct fw_request *request start = (offset - topology_map_region.start) / 4; - // NOTE: This can be without irqsave when we can guarantee that fw_send_request() for local - // destination never runs in any type of IRQ context. - scoped_guard(spinlock_irqsave, &card->topology_map.lock) + scoped_guard(spinlock, &card->topology_map.lock) memcpy(payload, &card->topology_map.buffer[start], length); fw_send_response(card, request, RCODE_COMPLETE); @@ -1282,6 +1324,8 @@ __must_hold(&card->split_timeout.lock) { unsigned int cycles; + lockdep_assert_held(&card->split_timeout.lock); + cycles = card->split_timeout.hi * 8000 + (card->split_timeout.lo >> 19); /* minimum per IEEE 1394, maximum which doesn't overflow OHCI */ @@ -1321,50 +1365,44 @@ static void handle_registers(struct fw_card *card, struct fw_request *request, case CSR_BUS_TIME: case CSR_BUSY_TIMEOUT: if (tcode == TCODE_READ_QUADLET_REQUEST) - *data = cpu_to_be32(card->driver->read_csr(card, reg)); + *data = cpu_to_be32(smp_load_acquire(&card->driver)->read_csr(card, reg)); else if (tcode == TCODE_WRITE_QUADLET_REQUEST) - card->driver->write_csr(card, reg, be32_to_cpu(*data)); + smp_load_acquire(&card->driver)->write_csr(card, reg, be32_to_cpu(*data)); else rcode = RCODE_TYPE_ERROR; break; case CSR_RESET_START: if (tcode == TCODE_WRITE_QUADLET_REQUEST) - card->driver->write_csr(card, CSR_STATE_CLEAR, - CSR_STATE_BIT_ABDICATE); + smp_load_acquire(&card->driver)->write_csr(card, CSR_STATE_CLEAR, + CSR_STATE_BIT_ABDICATE); else rcode = RCODE_TYPE_ERROR; break; case CSR_SPLIT_TIMEOUT_HI: - if (tcode == TCODE_READ_QUADLET_REQUEST) { - *data = cpu_to_be32(card->split_timeout.hi); - } else if (tcode == TCODE_WRITE_QUADLET_REQUEST) { - // NOTE: This can be without irqsave when we can guarantee that - // __fw_send_request() for local destination never runs in any type of IRQ - // context. - scoped_guard(spinlock_irqsave, &card->split_timeout.lock) { + scoped_guard(spinlock, &card->split_timeout.lock) { + if (tcode == TCODE_READ_QUADLET_REQUEST) { + *data = cpu_to_be32(card->split_timeout.hi); + } else if (tcode == TCODE_WRITE_QUADLET_REQUEST) { card->split_timeout.hi = be32_to_cpu(*data) & 7; update_split_timeout(card); + } else { + rcode = RCODE_TYPE_ERROR; } - } else { - rcode = RCODE_TYPE_ERROR; } break; case CSR_SPLIT_TIMEOUT_LO: - if (tcode == TCODE_READ_QUADLET_REQUEST) { - *data = cpu_to_be32(card->split_timeout.lo); - } else if (tcode == TCODE_WRITE_QUADLET_REQUEST) { - // NOTE: This can be without irqsave when we can guarantee that - // __fw_send_request() for local destination never runs in any type of IRQ - // context. - scoped_guard(spinlock_irqsave, &card->split_timeout.lock) { + scoped_guard(spinlock, &card->split_timeout.lock) { + if (tcode == TCODE_READ_QUADLET_REQUEST) { + *data = cpu_to_be32(card->split_timeout.lo); + } else if (tcode == TCODE_WRITE_QUADLET_REQUEST) { card->split_timeout.lo = be32_to_cpu(*data) & 0xfff80000; update_split_timeout(card); + } else { + rcode = RCODE_TYPE_ERROR; } - } else { - rcode = RCODE_TYPE_ERROR; } break; diff --git a/drivers/firewire/device-attribute-test.c b/drivers/firewire/device-attribute-test.c index 97478a96d1c9..2c482afca8f1 100644 --- a/drivers/firewire/device-attribute-test.c +++ b/drivers/firewire/device-attribute-test.c @@ -86,7 +86,7 @@ static void device_attr_simple_avc(struct kunit *test) .type = &fw_device_type, }, .config_rom = simple_avc_config_rom, - .config_rom_length = sizeof(simple_avc_config_rom), + .config_rom_length = ARRAY_SIZE(simple_avc_config_rom), }; static const struct fw_unit unit0 = { .device = { @@ -168,7 +168,7 @@ static void device_attr_legacy_avc(struct kunit *test) .type = &fw_device_type, }, .config_rom = legacy_avc_config_rom, - .config_rom_length = sizeof(legacy_avc_config_rom), + .config_rom_length = ARRAY_SIZE(legacy_avc_config_rom), }; static const struct fw_unit unit0 = { .device = { diff --git a/drivers/firewire/ohci-serdes-test.c b/drivers/firewire/ohci-serdes-test.c index 258f668619ef..87b136e041de 100644 --- a/drivers/firewire/ohci-serdes-test.c +++ b/drivers/firewire/ohci-serdes-test.c @@ -43,7 +43,7 @@ static void test_self_id_receive_buffer_deserialization(struct kunit *test) static void test_at_data_serdes(struct kunit *test) { static const __le32 expected[] = { - cpu_to_le32(0x00020e80), + cpu_to_le32(0x00820e80), cpu_to_le32(0xffc2ffff), cpu_to_le32(0xe0000000), }; @@ -56,7 +56,7 @@ static void test_at_data_serdes(struct kunit *test) unsigned int destination_id = ohci1394_at_data_get_destination_id(expected); u64 destination_offset = ohci1394_at_data_get_destination_offset(expected); - KUNIT_EXPECT_FALSE(test, has_src_bus_id); + KUNIT_EXPECT_TRUE(test, has_src_bus_id); KUNIT_EXPECT_EQ(test, 0x02, speed); KUNIT_EXPECT_EQ(test, 0x03, tlabel); KUNIT_EXPECT_EQ(test, 0x02, retry); diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 46dd53ecedc7..c06e8d87239c 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -162,7 +162,13 @@ struct context { struct at_context { struct context context; struct work_struct work; - bool flushing; +}; + +// The local-to-local transaction is handled by the work item in the following structure. +struct at_local { + struct list_head list; + spinlock_t lock; + struct work_struct work; }; struct iso_context { @@ -217,6 +223,9 @@ struct fw_ohci { struct at_context at_request_ctx; struct at_context at_response_ctx; + struct at_local at_request_local; + struct at_local at_response_local; + u32 it_context_support; u32 it_context_mask; /* unoccupied IT contexts */ struct iso_context *it_context_list; @@ -1338,9 +1347,7 @@ static void at_context_flush(struct at_context *ctx) disable_work_sync(&ctx->work); - WRITE_ONCE(ctx->flushing, true); ohci_at_context_work(&ctx->work); - WRITE_ONCE(ctx->flushing, false); enable_work(&ctx->work); } @@ -1362,8 +1369,10 @@ static int handle_at_packet(struct context *context, struct driver_data *driver_data; struct fw_packet *packet; int evt; + // Check whether this is called from at_context_flush(). + bool in_flushing = current_work() != &ctx->work; - if (last->transfer_status == 0 && !READ_ONCE(ctx->flushing)) + if (last->transfer_status == 0 && !in_flushing) /* This descriptor isn't done yet, stop iteration. */ return 0; @@ -1395,7 +1404,7 @@ static int handle_at_packet(struct context *context, break; case OHCI1394_evt_missing_ack: - if (READ_ONCE(ctx->flushing)) + if (in_flushing) packet->ack = RCODE_GENERATION; else { /* @@ -1417,7 +1426,7 @@ static int handle_at_packet(struct context *context, break; case OHCI1394_evt_no_status: - if (READ_ONCE(ctx->flushing)) { + if (in_flushing) { packet->ack = RCODE_GENERATION; break; } @@ -1456,11 +1465,11 @@ static int handle_at_packet(struct context *context, static u32 get_cycle_time(struct fw_ohci *ohci); -static void handle_local_rom(struct fw_ohci *ohci, - struct fw_packet *packet, u32 csr) +static void handle_local_rom(struct fw_ohci *ohci, struct fw_packet *packet, u32 csr, + struct fw_packet *response) { - struct fw_packet response; - int tcode, length, i; + int tcode, rcode, length, i; + void *payload = NULL; tcode = async_header_get_tcode(packet->header); if (tcode_is_block_packet(tcode)) @@ -1470,25 +1479,22 @@ static void handle_local_rom(struct fw_ohci *ohci, i = csr - CSR_CONFIG_ROM; if (i + length > CONFIG_ROM_SIZE) { - fw_fill_response(&response, packet->header, - RCODE_ADDRESS_ERROR, NULL, 0); + rcode = RCODE_ADDRESS_ERROR; + length = 0; } else if (!tcode_is_read_request(tcode)) { - fw_fill_response(&response, packet->header, - RCODE_TYPE_ERROR, NULL, 0); + rcode = RCODE_TYPE_ERROR; + length = 0; } else { - fw_fill_response(&response, packet->header, RCODE_COMPLETE, - (void *) ohci->config_rom + i, length); + rcode = RCODE_COMPLETE; + payload = (u8 *)ohci->config_rom + i; } - // Timestamping on behalf of the hardware. - response.timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); - fw_core_handle_response(&ohci->card, &response); + fw_fill_response(response, packet->header, rcode, payload, length); } -static void handle_local_lock(struct fw_ohci *ohci, - struct fw_packet *packet, u32 csr) +static void handle_local_lock(struct fw_ohci *ohci, struct fw_packet *packet, u32 csr, + struct fw_packet *response) { - struct fw_packet response; int tcode, length, ext_tcode, sel, try; __be32 *payload, lock_old; u32 lock_arg, lock_data; @@ -1506,9 +1512,8 @@ static void handle_local_lock(struct fw_ohci *ohci, lock_arg = 0; lock_data = 0; } else { - fw_fill_response(&response, packet->header, - RCODE_TYPE_ERROR, NULL, 0); - goto out; + fw_fill_response(response, packet->header, RCODE_TYPE_ERROR, NULL, 0); + return; } sel = (csr - CSR_BUS_MANAGER_ID) / 4; @@ -1520,86 +1525,163 @@ static void handle_local_lock(struct fw_ohci *ohci, if (reg_read(ohci, OHCI1394_CSRControl) & 0x80000000) { lock_old = cpu_to_be32(reg_read(ohci, OHCI1394_CSRData)); - fw_fill_response(&response, packet->header, - RCODE_COMPLETE, - &lock_old, sizeof(lock_old)); - goto out; + fw_fill_response(response, packet->header, RCODE_COMPLETE, &lock_old, + sizeof(lock_old)); + return; } ohci_err(ohci, "swap not done (CSR lock timeout)\n"); - fw_fill_response(&response, packet->header, RCODE_BUSY, NULL, 0); + fw_fill_response(response, packet->header, RCODE_BUSY, NULL, 0); +} - out: - // Timestamping on behalf of the hardware. - response.timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); - fw_core_handle_response(&ohci->card, &response); +static bool in_config_rom_csr_registers(u64 offset) +{ + return in_range(offset, CSR_CONFIG_ROM, CONFIG_ROM_SIZE); } -static void handle_local_request(struct at_context *ctx, struct fw_packet *packet) +// 5.5.1 Bus Management CSR Registers. +static bool in_bus_management_csr_registers(u64 offset) { - struct fw_ohci *ohci = ctx->context.ohci; - u64 offset, csr; + // 0x22c = CSR_MAINT_CONTROL following to CSR_CHANNELS_AVAILABLE_LO. + return in_range(offset, CSR_BUS_MANAGER_ID, 0x22c - CSR_BUS_MANAGER_ID); +} - if (ctx == &ohci->at_request_ctx) { - packet->ack = ACK_PENDING; - packet->callback(packet, &ohci->card, packet->ack); - } +static void handle_at_request_local_packet(struct fw_ohci *ohci, struct fw_packet *packet) +{ + // Emulate split transaction. + packet->ack = ACK_PENDING; + packet->callback(packet, &ohci->card, packet->ack); - offset = async_header_get_offset(packet->header); - csr = offset - CSR_REGISTER_BASE; + u64 csr_offset = async_header_get_offset(packet->header) - CSR_REGISTER_BASE; - /* Handle config rom reads. */ - if (csr >= CSR_CONFIG_ROM && csr < CSR_CONFIG_ROM_END) - handle_local_rom(ohci, packet, csr); - else switch (csr) { - case CSR_BUS_MANAGER_ID: - case CSR_BANDWIDTH_AVAILABLE: - case CSR_CHANNELS_AVAILABLE_HI: - case CSR_CHANNELS_AVAILABLE_LO: - handle_local_lock(ohci, packet, csr); - break; - default: - if (ctx == &ohci->at_request_ctx) - fw_core_handle_request(&ohci->card, packet); + if (in_config_rom_csr_registers(csr_offset) || in_bus_management_csr_registers(csr_offset)) { + struct fw_packet response; + + memset(&response, 0, sizeof(response)); + + if (in_bus_management_csr_registers(csr_offset)) + handle_local_lock(ohci, packet, csr_offset, &response); else - fw_core_handle_response(&ohci->card, packet); - break; - } + handle_local_rom(ohci, packet, csr_offset, &response); - if (ctx == &ohci->at_response_ctx) { - packet->ack = ACK_COMPLETE; - packet->callback(packet, &ohci->card, packet->ack); + // Timestamping on behalf of the hardware. + response.timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); + + // Finish the transaction immediately. + fw_core_handle_response(&ohci->card, &response); + } else { + fw_core_handle_request(&ohci->card, packet); } } -static void at_context_transmit(struct at_context *ctx, struct fw_packet *packet) +static void handle_at_response_local_packet(struct fw_ohci *ohci, struct fw_packet *packet) { - struct fw_ohci *ohci = ctx->context.ohci; - unsigned long flags; - int ret; + u64 csr_offset = async_header_get_offset(packet->header) - CSR_REGISTER_BASE; - spin_lock_irqsave(&ohci->lock, flags); + // The transaction is already finished by handle_local_at_request_packet(). + if (WARN_ON(in_config_rom_csr_registers(csr_offset)) || + WARN_ON(in_bus_management_csr_registers(csr_offset))) + return; - if (async_header_get_destination(packet->header) == ohci->node_id && - ohci->generation == packet->generation) { - spin_unlock_irqrestore(&ohci->lock, flags); + fw_core_handle_response(&ohci->card, packet); - // Timestamping on behalf of the hardware. - packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); + packet->ack = ACK_COMPLETE; + packet->callback(packet, &ohci->card, packet->ack); +} - handle_local_request(ctx, packet); - return; +static void handle_at_local_packets(struct at_local *local, struct fw_ohci *ohci, + void (*handle_at_local_packet)(struct fw_ohci *, struct fw_packet *)) +{ + struct fw_packet *packet; + + spin_lock(&local->lock); + + while ((packet = list_first_entry_or_null(&local->list, typeof(*packet), link_for_local))) { + list_del_init(&packet->link_for_local); + spin_unlock(&local->lock); + + if (unlikely(packet->ack != 0)) { + // This case is active when the call of at_context_queue_packet() returns + // error in at_context_transmit(). + packet->callback(packet, &ohci->card, packet->ack); + } else { + handle_at_local_packet(ohci, packet); + } + + spin_lock(&local->lock); } - ret = at_context_queue_packet(ctx, packet); - spin_unlock_irqrestore(&ohci->lock, flags); + spin_unlock(&local->lock); +} - if (ret < 0) { - // Timestamping on behalf of the hardware. - packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); +static void at_request_local_work(struct work_struct *work) +{ + struct at_local *local = from_work(local, work, work); + struct fw_ohci *ohci = container_of(local, struct fw_ohci, at_request_local); + + handle_at_local_packets(local, ohci, handle_at_request_local_packet); +} - packet->callback(packet, &ohci->card, packet->ack); +static void at_response_local_work(struct work_struct *work) +{ + struct at_local *local = from_work(local, work, work); + struct fw_ohci *ohci = container_of(local, struct fw_ohci, at_response_local); + + handle_at_local_packets(local, ohci, handle_at_response_local_packet); +} + +static void at_local_init(struct at_local *local, work_func_t func) +{ + spin_lock_init(&local->lock); + INIT_LIST_HEAD(&local->list); + INIT_WORK(&local->work, func); +} + +static bool destination_is_local(const struct fw_packet *packet, const struct fw_ohci *ohci) +__must_hold(&ohci->lock) +{ + lockdep_assert_held(&ohci->lock); + + return (async_header_get_destination(packet->header) == ohci->node_id && + ohci->generation == packet->generation); +} + +static void queue_work_for_at_local_packet(struct at_context *ctx, struct fw_packet *packet, + struct fw_ohci *ohci) +{ + struct at_local *local; + + if (ctx == &ohci->at_request_ctx) + local = &ohci->at_request_local; + else + local = &ohci->at_response_local; + + // Timestamping on behalf of the hardware. + packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); + + scoped_guard(spinlock_irqsave, &local->lock) + list_add_tail(&packet->link_for_local, &local->list); + queue_work(ohci->card.async_wq, &local->work); +} + +static void at_context_transmit(struct at_context *ctx, struct fw_packet *packet) +{ + struct fw_ohci *ohci = ctx->context.ohci; + bool use_work = true; + + // These members are used to cancel the packet. + packet->driver_data = NULL; + INIT_LIST_HEAD(&packet->link_for_local); + + scoped_guard(spinlock_irqsave, &ohci->lock) { + if (!destination_is_local(packet, ohci)) { + if (!at_context_queue_packet(ctx, packet)) + use_work = false; + } } + + if (use_work) + queue_work_for_at_local_packet(ctx, packet, ohci); } static void detect_dead_context(struct fw_ohci *ohci, @@ -2456,6 +2538,9 @@ static void ohci_disable(struct fw_card *card) flush_work(&ohci->at_request_ctx.work); flush_work(&ohci->at_response_ctx.work); + flush_work(&ohci->at_request_local.work); + flush_work(&ohci->at_response_local.work); + for (i = 0; i < ohci->n_ir; ++i) { if (!(ohci->ir_context_mask & BIT(i))) flush_work(&ohci->ir_context_list[i].base.work); @@ -2467,6 +2552,9 @@ static void ohci_disable(struct fw_card *card) at_context_flush(&ohci->at_request_ctx); at_context_flush(&ohci->at_response_ctx); + + at_request_local_work(&ohci->at_request_local.work); + at_response_local_work(&ohci->at_response_local.work); } static int ohci_set_config_rom(struct fw_card *card, @@ -2568,31 +2656,56 @@ static int ohci_cancel_packet(struct fw_card *card, struct fw_packet *packet) { struct fw_ohci *ohci = fw_ohci(card); struct at_context *ctx = &ohci->at_request_ctx; - struct driver_data *driver_data = packet->driver_data; + struct at_local *local = &ohci->at_request_local; int ret = -ENOENT; + // handle_at_request_local_packet() can reach here by calling fw_core_handle_response(). + if (current_work() != &local->work) + disable_work_sync(&local->work); + // Avoid dead lock due to programming mistake. if (WARN_ON_ONCE(current_work() == &ctx->work)) return 0; disable_work_sync(&ctx->work); - if (packet->ack != 0) - goto out; + bool use_local_work = false; - if (packet->payload_mapped) - dma_unmap_single(ohci->card.device, packet->payload_bus, - packet->payload_length, DMA_TO_DEVICE); + if (!packet->ack) { + // NOTE: This function relies on this value, thus it is not thread-safe. + struct driver_data *driver_data = packet->driver_data; - driver_data->packet = NULL; - packet->ack = RCODE_CANCELLED; + if (driver_data) { + if (packet->payload_mapped) { + dma_unmap_single(ohci->card.device, packet->payload_bus, + packet->payload_length, DMA_TO_DEVICE); + } - // Timestamping on behalf of the hardware. - packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); + // Invalidate the packet queued in AT request context. + driver_data->packet = NULL; - packet->callback(packet, &ohci->card, packet->ack); - ret = 0; - out: - enable_work(&ctx->work); + scoped_guard(spinlock_irqsave, &local->lock) + list_add_tail(&packet->link_for_local, &local->list); + use_local_work = true; + } else { + scoped_guard(spinlock_irqsave, &local->lock) { + if (!list_empty(&packet->link_for_local)) + use_local_work = true; + } + } + } + + enable_and_queue_work(card->async_wq, &ctx->work); + + if (use_local_work) { + // Timestamping on behalf of the hardware. + packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci)); + packet->ack = RCODE_CANCELLED; + + ret = 0; + } + + if (current_work() != &local->work) + enable_and_queue_work(card->async_wq, &local->work); return ret; } @@ -3666,6 +3779,9 @@ static int pci_probe(struct pci_dev *dev, return err; INIT_WORK(&ohci->at_response_ctx.work, ohci_at_context_work); + at_local_init(&ohci->at_request_local, at_request_local_work); + at_local_init(&ohci->at_response_local, at_response_local_work); + reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, ~0); ohci->ir_context_channels = ~0ULL; ohci->ir_context_support = reg_read(ohci, OHCI1394_IsoRecvIntMaskSet); @@ -3761,7 +3877,7 @@ static void pci_remove(struct pci_dev *dev) dev_notice(&dev->dev, "removing fw-ohci device\n"); } -static int __maybe_unused pci_suspend(struct device *dev) +static int pci_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct fw_ohci *ohci = pci_get_drvdata(pdev); @@ -3773,7 +3889,7 @@ static int __maybe_unused pci_suspend(struct device *dev) } -static int __maybe_unused pci_resume(struct device *dev) +static int pci_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct fw_ohci *ohci = pci_get_drvdata(pdev); @@ -3804,14 +3920,14 @@ static const struct pci_device_id pci_table[] = { MODULE_DEVICE_TABLE(pci, pci_table); -static SIMPLE_DEV_PM_OPS(pci_pm_ops, pci_suspend, pci_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(pci_pm_ops, pci_suspend, pci_resume); static struct pci_driver fw_ohci_pci_driver = { .name = ohci_driver_name, .id_table = pci_table, .probe = pci_probe, .remove = pci_remove, - .driver.pm = &pci_pm_ops, + .driver.pm = pm_sleep_ptr(&pci_pm_ops), }; static int __init fw_ohci_init(void) diff --git a/drivers/firewire/ohci.h b/drivers/firewire/ohci.h index 218666cfe14a..63ccf2f44504 100644 --- a/drivers/firewire/ohci.h +++ b/drivers/firewire/ohci.h @@ -178,7 +178,7 @@ static inline bool ohci1394_at_data_get_src_bus_id(const __le32 *data) { - return !!((data[0] & OHCI1394_AT_DATA_Q0_srcBusID_MASK) >> OHCI1394_AT_DATA_Q0_srcBusID_SHIFT); + return !!((le32_to_cpu(data[0]) & OHCI1394_AT_DATA_Q0_srcBusID_MASK) >> OHCI1394_AT_DATA_Q0_srcBusID_SHIFT); } static inline void ohci1394_at_data_set_src_bus_id(__le32 *data, bool src_bus_id) diff --git a/drivers/firewire/uapi-test.c b/drivers/firewire/uapi-test.c index bc3f10a2e516..0bdacb1caeb8 100644 --- a/drivers/firewire/uapi-test.c +++ b/drivers/firewire/uapi-test.c @@ -72,11 +72,31 @@ static void structure_layout_event_phy_packet2(struct kunit *test) KUNIT_EXPECT_EQ(test, 24, offsetof(struct fw_cdev_event_phy_packet2, data)); } +static void structure_layout_event_bus_reset(struct kunit *test) +{ +#if defined(CONFIG_X86_32) + // 4 bytes alignment for aggregate type including 8 bytes storage types. + KUNIT_EXPECT_EQ(test, 36, sizeof(struct fw_cdev_event_bus_reset)); +#else + KUNIT_EXPECT_EQ(test, 40, sizeof(struct fw_cdev_event_bus_reset)); +#endif + + KUNIT_EXPECT_EQ(test, 0, offsetof(struct fw_cdev_event_bus_reset, closure)); + KUNIT_EXPECT_EQ(test, 8, offsetof(struct fw_cdev_event_bus_reset, type)); + KUNIT_EXPECT_EQ(test, 12, offsetof(struct fw_cdev_event_bus_reset, node_id)); + KUNIT_EXPECT_EQ(test, 16, offsetof(struct fw_cdev_event_bus_reset, local_node_id)); + KUNIT_EXPECT_EQ(test, 20, offsetof(struct fw_cdev_event_bus_reset, bm_node_id)); + KUNIT_EXPECT_EQ(test, 24, offsetof(struct fw_cdev_event_bus_reset, irm_node_id)); + KUNIT_EXPECT_EQ(test, 28, offsetof(struct fw_cdev_event_bus_reset, root_node_id)); + KUNIT_EXPECT_EQ(test, 32, offsetof(struct fw_cdev_event_bus_reset, generation)); +} + static struct kunit_case structure_layout_test_cases[] = { KUNIT_CASE(structure_layout_event_response), KUNIT_CASE(structure_layout_event_request3), KUNIT_CASE(structure_layout_event_response2), KUNIT_CASE(structure_layout_event_phy_packet2), + KUNIT_CASE(structure_layout_event_bus_reset), {} }; diff --git a/include/linux/firewire.h b/include/linux/firewire.h index fd35a6570cd8..9c563fbdbbf0 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -92,17 +92,17 @@ struct fw_card { u64 reset_jiffies; struct { - int current_tlabel; - u64 tlabel_mask; - struct list_head list; + int current_tlabel __guarded_by(&lock); + u64 tlabel_mask __guarded_by(&lock); + struct list_head list __guarded_by(&lock); spinlock_t lock; } transactions; struct { - u32 hi; - u32 lo; - unsigned int cycles; - unsigned int jiffies; + u32 hi __guarded_by(&lock); + u32 lo __guarded_by(&lock); + unsigned int cycles __guarded_by(&lock); + unsigned int jiffies __guarded_by(&lock); spinlock_t lock; } split_timeout; @@ -139,7 +139,7 @@ struct fw_card { u32 broadcast_channel; struct { - __be32 buffer[(CSR_TOPOLOGY_MAP_END - CSR_TOPOLOGY_MAP) / 4]; + __be32 buffer[(CSR_TOPOLOGY_MAP_END - CSR_TOPOLOGY_MAP) / 4] __guarded_by(&lock); spinlock_t lock; } topology_map; @@ -172,10 +172,10 @@ struct fw_attribute_group { }; enum fw_device_quirk { - // See afa1282a35d3 ("firewire: core: check for 1394a compliant IRM, fix inaccessibility of Sony camcorder"). + // See 10389536742c ("firewire: core: check for 1394a compliant IRM, fix inaccessibility of Sony camcorder"). FW_DEVICE_QUIRK_IRM_IS_1394_1995_ONLY = BIT(0), - // See a509e43ff338 ("firewire: core: fix unstable I/O with Canon camcorder"). + // See 6044565af458 ("firewire: core: fix unstable I/O with Canon camcorder"). FW_DEVICE_QUIRK_IRM_IGNORES_BUS_MANAGER = BIT(1), // MOTU Audio Express transfers acknowledge packet with 0x10 for pending state. @@ -224,7 +224,7 @@ struct fw_device { struct mutex client_list_mutex; struct list_head client_list; - const u32 *config_rom; + const u32 *config_rom __counted_by_ptr(config_rom_length); size_t config_rom_length; int config_rom_retries; unsigned is_local:1; @@ -286,28 +286,87 @@ struct fw_request; typedef void (*fw_packet_callback_t)(struct fw_packet *packet, struct fw_card *card, int status); + +/** + * typedef fw_transaction_callback_t - Function to handle the result of outgoing transaction. + * @card: the interface to send the request at + * @rcode: the response code + * @data: the data for the response subaction + * @length: the length of data + * @callback_data: the data registered with this callback + * + * This callback is called from a workqueue in process context after the outgoing transaction is + * completed successfully or unsuccessfully. + * + * Among its parameters is the response code which is either one of the rcodes per IEEE 1394 or, in + * case of internal errors, the firewire-core specific %RCODE_SEND_ERROR. The other firewire-core + * specific rcodes (%RCODE_CANCELLED, %RCODE_BUSY, %RCODE_GENERATION, %RCODE_NO_ACK) denote + * transaction timeout, busy responder, stale request generation, or missing ACK respectively. + * + * Context: Process context. + */ typedef void (*fw_transaction_callback_t)(struct fw_card *card, int rcode, void *data, size_t length, void *callback_data); + +/** + * typedef fw_transaction_callback_with_tstamp_t - Function to handle the result of outgoing + * transaction with hardware time stamps. + * @card: the interface to send the request at + * @rcode: the response code + * @request_tstamp: the time stamp at which the request subaction finished + * @response_tstamp: the time stamp at which the response subaction finished + * @data: the data for the response subaction + * @length: the length of data + * @callback_data: the data registered with this callback + * + * This callback is called from a workqueue in process context after the outgoing transaction is + * completed successfully or unsuccessfully. + * + * Among its parameters is the response code which is either one of the rcodes per IEEE 1394 or, in + * case of internal errors, the firewire-core specific %RCODE_SEND_ERROR. The other firewire-core + * specific rcodes (%RCODE_CANCELLED, %RCODE_BUSY, %RCODE_GENERATION, %RCODE_NO_ACK) denote + * transaction timeout, busy responder, stale request generation, or missing ACK respectively. + * + * The timestamp consists of the low order 3 bits of second field and the full 13 bits of count + * field of isochronous cycle time register. + * + * Context: Process context. + */ typedef void (*fw_transaction_callback_with_tstamp_t)(struct fw_card *card, int rcode, u32 request_tstamp, u32 response_tstamp, void *data, size_t length, void *callback_data); +/** + * union fw_transaction_callback - A union to store the outgoing transaction callback. + * @without_tstamp: Callback without hardware timestamps. + * @with_tstamp: Callback with hardware timestamps. + */ union fw_transaction_callback { fw_transaction_callback_t without_tstamp; fw_transaction_callback_with_tstamp_t with_tstamp; }; -/* - * This callback handles an inbound request subaction. It is called in - * RCU read-side context, therefore must not sleep. +/** + * typedef fw_address_callback_t - Function to handle the request of the incoming transaction. + * @card: the card instance which receives the request + * @request: the request instance. + * @tcode: the transaction code + * @destination: the destination node ID + * @source: the source node ID + * @generation: the bus generation in which the request was sent + * @offset: the destination offset in source node. + * @data: the request content if available. + * @length: the length of data. + * @callback_data: the data registered with this function. * - * The callback should not initiate outbound request subactions directly. - * Otherwise there is a danger of recursion of inbound and outbound - * transactions from and to the local node. + * This callback handles an inbound request subaction. * - * The callback is responsible that fw_send_response() is called on the @request, except for FCP - * registers for which the core takes care of that. + * The contents of @request are opaque to the callback. The callback is responsible for calling + * fw_send_response() for @request, except for FCP registers, for which the core handles the + * response. + * + * Context: Process context. */ typedef void (*fw_address_callback_t)(struct fw_card *card, struct fw_request *request, @@ -322,26 +381,36 @@ struct fw_packet { int generation; u32 header[4]; size_t header_length; - void *payload; + void *payload __counted_by_ptr(payload_length); size_t payload_length; dma_addr_t payload_bus; bool payload_mapped; u32 timestamp; + // Used to handle the local-to-local packets in the AT request/response contexts. + struct list_head link_for_local; + /* * This callback is called when the packet transmission has completed. * For successful transmission, the status code is the ack received * from the destination. Otherwise it is one of the juju-specific * rcodes: RCODE_SEND_ERROR, _CANCELLED, _BUSY, _GENERATION, _NO_ACK. - * The callback can be called from workqueue and thus must never block. + * The callback is called from a workqueue. It is not preferable to block it so long. */ fw_packet_callback_t callback; int ack; - struct list_head link; void *driver_data; }; +/** + * struct fw_transaction - A structure to hold outgoing transaction state. + * + * All structure members are for internal use only. Once passed to __fw_send_request(), this + * structure holds the state of an outgoing transaction. It must not be freed until the callback + * reports the transaction result. + */ struct fw_transaction { + /* private: */ int node_id; /* The generation is implied; it is always the current. */ int tlabel; struct list_head link; @@ -359,20 +428,44 @@ struct fw_transaction { union fw_transaction_callback callback; bool with_tstamp; void *callback_data; + + // For some error cases. + struct work_struct error_work; + int rcode; + u32 response_timestamp; }; +/** + * struct fw_address_handler - A handler for incoming transaction requests to a registered address + * region. + * @offset: The registered offset, available after successful registration. + * @length: The length of the address region in bytes, a multiple of a quadlet. + * @address_callback: Callback invoked when a transaction request is received for the registered + * address region. + * @callback_data: Data passed to @address_callback. + * + * This structure represents a handler registered to receive incoming asynchronous requests to a + * specific region of the address space on the local node. + */ struct fw_address_handler { u64 offset; u64 length; fw_address_callback_t address_callback; void *callback_data; - // Only for core functions. + /* private: */ struct list_head link; struct kref kref; struct completion done; }; +/** + * struct fw_address_region - An address range. + * @start: The starting offset in bytes, a multiple of a quadlet. + * @end: The ending offset in bytes, a multiple of a quadlet. + * + * Represents a range of addresses in the local node's address space. + */ struct fw_address_region { u64 start; u64 end; @@ -411,9 +504,10 @@ void __fw_send_request(struct fw_card *card, struct fw_transaction *t, int tcode * A variation of __fw_send_request() to generate callback for response subaction without time * stamp. * - * The callback is invoked in the workqueue context in most cases. However, if an error is detected - * before queueing or the destination address refers to the local node, it is invoked in the - * current context instead. + * After the transaction is completed successfully or unsuccessfully, the @callback will be called + * in process context. + * + * Context: Any context. */ static inline void fw_send_request(struct fw_card *card, struct fw_transaction *t, int tcode, int destination_id, int generation, int speed, @@ -444,9 +538,10 @@ static inline void fw_send_request(struct fw_card *card, struct fw_transaction * * * A variation of __fw_send_request() to generate callback for response subaction with time stamp. * - * The callback is invoked in the workqueue context in most cases. However, if an error is detected - * before queueing or the destination address refers to the local node, it is invoked in the current - * context instead. + * After the transaction is completed successfully or unsuccessfully, the @callback will be called + * in process context. + * + * Context: Any context. */ static inline void fw_send_request_with_tstamp(struct fw_card *card, struct fw_transaction *t, int tcode, int destination_id, int generation, int speed, unsigned long long offset, diff --git a/include/uapi/linux/firewire-cdev.h b/include/uapi/linux/firewire-cdev.h index 05e3aa8fa8bc..4f83d6babc92 100644 --- a/include/uapi/linux/firewire-cdev.h +++ b/include/uapi/linux/firewire-cdev.h @@ -293,7 +293,7 @@ struct fw_cdev_event_request3 { /** * struct fw_cdev_event_iso_interrupt - Sent when an iso packet was completed * @closure: See &fw_cdev_event_common; - * set by %FW_CDEV_CREATE_ISO_CONTEXT ioctl + * set by %FW_CDEV_IOC_CREATE_ISO_CONTEXT ioctl * @type: See &fw_cdev_event_common; always %FW_CDEV_EVENT_ISO_INTERRUPT * @cycle: Cycle counter of the last completed packet * @header_length: Total length of following headers, in bytes @@ -353,7 +353,7 @@ struct fw_cdev_event_iso_interrupt { /** * struct fw_cdev_event_iso_interrupt_mc - An iso buffer chunk was completed * @closure: See &fw_cdev_event_common; - * set by %FW_CDEV_CREATE_ISO_CONTEXT ioctl + * set by %FW_CDEV_IOC_CREATE_ISO_CONTEXT ioctl * @type: %FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL * @completed: Offset into the receive buffer; data before this offset is valid * diff --git a/tools/firewire/nosy-dump.c b/tools/firewire/nosy-dump.c index 9a906de3a9ef..7ddb50521c29 100644 --- a/tools/firewire/nosy-dump.c +++ b/tools/firewire/nosy-dump.c @@ -947,6 +947,8 @@ int main(int argc, const char *argv[]) output = fopen(option_output, "w"); if (output == NULL) { fprintf(stderr, "Could not open %s, %m\n", option_output); + if (input != NULL) + fclose(input); return -1; } } @@ -973,7 +975,7 @@ int main(int argc, const char *argv[]) while (run) { if (input != NULL) { if (fread(&length, sizeof length, 1, input) != 1) - return 0; + break; fread(buf, 1, length, input); } else { poll(pollfds, 2, -1); @@ -1014,7 +1016,11 @@ int main(int argc, const char *argv[]) if (output != NULL) fclose(output); - close(fd); + if (input != NULL) + fclose(input); + + if (fd >= 0) + close(fd); poptFreeContext(con); |
