summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-21 13:39:25 +0100
committerMark Brown <broonie@kernel.org>2026-10-21 13:39:25 +0100
commitde87643cd2865fed133fd594cc10f162b1648d62 (patch)
tree99ce27d9b31c0f95c460f834013019fda76d8b98
parent6efe1fb4b34f71e414462659f3a1c57bc55ce402 (diff)
parent32906613748b3a42bb29472ff406f8ba2538cb31 (diff)
downloadlinux-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.rst5
-rw-r--r--drivers/firewire/.kunitconfig1
-rw-r--r--drivers/firewire/Kconfig16
-rw-r--r--drivers/firewire/Makefile2
-rw-r--r--drivers/firewire/config-rom-generator-test.c407
-rw-r--r--drivers/firewire/config-rom-parser-test.c355
-rw-r--r--drivers/firewire/core-card.c124
-rw-r--r--drivers/firewire/core-cdev.c301
-rw-r--r--drivers/firewire/core-device.c12
-rw-r--r--drivers/firewire/core-iso.c32
-rw-r--r--drivers/firewire/core-topology.c3
-rw-r--r--drivers/firewire/core-transaction.c414
-rw-r--r--drivers/firewire/device-attribute-test.c4
-rw-r--r--drivers/firewire/ohci-serdes-test.c4
-rw-r--r--drivers/firewire/ohci.c312
-rw-r--r--drivers/firewire/ohci.h2
-rw-r--r--drivers/firewire/uapi-test.c20
-rw-r--r--include/linux/firewire.h153
-rw-r--r--include/uapi/linux/firewire-cdev.h4
-rw-r--r--tools/firewire/nosy-dump.c10
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);