mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
firewire updates for v7.4
- Guarantee asynchronous packet and transaction callbacks in process context Both the 1394 OHCI driver and the core transaction layer have been changed to guarantee that asynchronous packet and transaction callbacks are invoked in process context. In v6.17, the execution context depended on the transaction destination: callbacks for transactions destined for remote nodes were invoked from a workqueue, whereas callbacks for transactions destined for the local node or terminated due to internal errors were invoked in the transaction initiator's context. All such callbacks are now guaranteed to execute in process context, eliminating the need to account for different execution contexts when implementing callback functions. - Fill kerneldoc for transaction layer The above change guarantees a consistent execution context for packet and transaction callbacks, making it possible to document their execution context in kerneldoc. - Enable compiler-based context analysis for the transaction layer The core transaction layer is sufficiently complex that compiler-based context analysis can help detect invalid call paths. - Fix a reference count leak in the cdev layer A reference count leak was reported when a user space application delegated isochronous resource management. This issue has now been fixed. - Add KUnit tests for the configuration ROM parser and generator In recent years, questions have been raised about how the configuration ROM parser and generator handle boundary conditions and edge cases. KUnit tests have been added to verify their behavior in these situations. KUNIT_STATIC_STUB_REDIRECT is used to stub the transaction responder so that it returns the configuration ROM contents. - Misc fixes for kerneldoc and nosy-dump application -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQQE66IEYNDXNBPeGKSsLtaWM8LwEwUCatyWDwAKCRCsLtaWM8Lw E7nWAQC7LGjJxng/GKj/fsx5ah6olDribI3braCTyqirydhXxwD+OrBuwrIvWAbs h7z5afAPGozUZJgR0HO+p+BKfEG9DA0= =Fx7F -----END PGP SIGNATURE----- Merge tag 'firewire-updates-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394 Pull firewire updates from Takashi Sakamoto: - Guarantee asynchronous packet and transaction callbacks in process context Both the 1394 OHCI driver and the core transaction layer have been changed to guarantee that asynchronous packet and transaction callbacks are invoked in process context. In v6.17, the execution context depended on the transaction destination: callbacks for transactions destined for remote nodes were invoked from a workqueue, whereas callbacks for transactions destined for the local node or terminated due to internal errors were invoked in the transaction initiator's context. All such callbacks are now guaranteed to execute in process context, eliminating the need to account for different execution contexts when implementing callback functions. - Fill kerneldoc for transaction layer The above change guarantees a consistent execution context for packet and transaction callbacks, making it possible to document their execution context in kerneldoc. - Enable compiler-based context analysis for the transaction layer The core transaction layer is sufficiently complex that compiler-based context analysis can help detect invalid call paths. - Fix a reference count leak in the cdev layer A reference count leak was reported when a user space application delegated isochronous resource management. This issue has now been fixed. - Add KUnit tests for the configuration ROM parser and generator In recent years, questions have been raised about how the configuration ROM parser and generator handle boundary conditions and edge cases. KUnit tests have been added to verify their behavior in these situations. KUNIT_STATIC_STUB_REDIRECT is used to stub the transaction responder so that it returns the configuration ROM contents. - Misc fixes for kerneldoc and nosy-dump application * tag 'firewire-updates-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394: (88 commits) firewire: core: replace drain_workqueue() with flush_workqueue() firewire: core: adjust context_unsafe() usage for lockdep checks firewire: core: invoke packet callback from workqueue when removing card instance firewire: core: use designated initializer for dummy driver on card removal firewire: core: use context analysis to check lock state when scheduling error callbacks firewire: core: check precondition when scheduling error callbacks firewire: core: use workqueue to cancel all pending transactions on card removal firewire: core: reuse helper function for scheduling error callbacks firewire: core: don't disable error work for transaction firewire: core: use release/acquire pair when replacing underlying driver on card removal firewire: core: add kerneldoc for inbound transaction-related symbols firewire: core: add kerneldoc for outbound transaction-related symbols firewire: core: use appropriate spinlock variants for transaction-related locks firewire: core: narrow the card lock scope when accessing node_id firewire: core: use context analysis annotations and lockdep checks for topology map lock firewire: core: use context analysis annotations and lockdep checks for split timeout lock firewire: core: use context analysis annotations and lockdep checks for transactions lock firewire: core: enable context analysis for transaction layer firewire: core: add might_sleep() checks to transaction layer firewire: core: fulfill kerneldoc for fw_cancel_transaction() ...
This commit is contained in:
commit
065af32bd4
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
407
drivers/firewire/config-rom-generator-test.c
Normal file
407
drivers/firewire/config-rom-generator-test.c
Normal file
|
|
@ -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);
|
||||
355
drivers/firewire/config-rom-parser-test.c
Normal file
355
drivers/firewire/config-rom-parser-test.c
Normal file
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
||||
drain_workqueue(card->isoc_wq);
|
||||
drain_workqueue(card->async_wq);
|
||||
// Ensure the entire contents of the driver are visible to other CPUs.
|
||||
smp_store_release(&card->driver, &closer.driver);
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
if (list_empty(&client->event_list) &&
|
||||
fw_device_is_shutdown(client->device))
|
||||
return -ENODEV;
|
||||
// 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 (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;
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
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;
|
||||
int channel;
|
||||
|
||||
scoped_guard(spinlock_irq, &client->lock)
|
||||
generation = client->device->generation;
|
||||
|
||||
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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
t->rcode = rcode;
|
||||
t->response_timestamp = response_timestamp;
|
||||
|
||||
queue_work(t->card->async_wq, &t->error_work);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* 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)
|
||||
__must_not_hold(&card->transactions.lock)
|
||||
{
|
||||
lockdep_assert_not_held(&card->transactions.lock);
|
||||
|
||||
// Allow the call of struct fw_card_driver.cancel_packet() to wait for any event.
|
||||
might_sleep();
|
||||
|
||||
// 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;
|
||||
|
||||
u32 curr_cycle_time = 0;
|
||||
|
||||
// Timestamping on behalf of hardware.
|
||||
(void)fw_card_read_cycle_time(card, &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 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only valid for transactions that are potentially pending (ie have
|
||||
* been sent).
|
||||
*/
|
||||
int fw_cancel_transaction(struct fw_card *card,
|
||||
struct fw_transaction *transaction)
|
||||
{
|
||||
u32 tstamp;
|
||||
|
||||
/*
|
||||
* Cancel the packet transmission if it's still queued. That
|
||||
* will call the packet transmission callback which cancels
|
||||
* the transaction.
|
||||
*/
|
||||
|
||||
if (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.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
(void)fw_card_read_cycle_time(card, &curr_cycle_time);
|
||||
tstamp = cycle_time_to_ohci_tstamp(curr_cycle_time);
|
||||
}
|
||||
|
||||
return close_transaction(transaction, card, RCODE_CANCELLED, tstamp);
|
||||
}
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
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);
|
||||
fw_fill_response(response, packet->header, RCODE_BUSY, NULL, 0);
|
||||
}
|
||||
|
||||
static void handle_local_request(struct at_context *ctx, struct fw_packet *packet)
|
||||
static bool in_config_rom_csr_registers(u64 offset)
|
||||
{
|
||||
struct fw_ohci *ohci = ctx->context.ohci;
|
||||
u64 offset, csr;
|
||||
return in_range(offset, CSR_CONFIG_ROM, CONFIG_ROM_SIZE);
|
||||
}
|
||||
|
||||
if (ctx == &ohci->at_request_ctx) {
|
||||
packet->ack = ACK_PENDING;
|
||||
packet->callback(packet, &ohci->card, packet->ack);
|
||||
}
|
||||
// 5.5.1 Bus Management CSR Registers.
|
||||
static bool in_bus_management_csr_registers(u64 offset)
|
||||
{
|
||||
// 0x22c = CSR_MAINT_CONTROL following to CSR_CHANNELS_AVAILABLE_LO.
|
||||
return in_range(offset, CSR_BUS_MANAGER_ID, 0x22c - CSR_BUS_MANAGER_ID);
|
||||
}
|
||||
|
||||
offset = async_header_get_offset(packet->header);
|
||||
csr = offset - CSR_REGISTER_BASE;
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
u64 csr_offset = async_header_get_offset(packet->header) - CSR_REGISTER_BASE;
|
||||
|
||||
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);
|
||||
|
||||
// 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 handle_at_response_local_packet(struct fw_ohci *ohci, struct fw_packet *packet)
|
||||
{
|
||||
u64 csr_offset = async_header_get_offset(packet->header) - CSR_REGISTER_BASE;
|
||||
|
||||
// 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;
|
||||
|
||||
fw_core_handle_response(&ohci->card, packet);
|
||||
|
||||
packet->ack = ACK_COMPLETE;
|
||||
packet->callback(packet, &ohci->card, packet->ack);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (ctx == &ohci->at_response_ctx) {
|
||||
packet->ack = ACK_COMPLETE;
|
||||
packet->callback(packet, &ohci->card, packet->ack);
|
||||
}
|
||||
spin_unlock(&local->lock);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
bool use_work = true;
|
||||
|
||||
spin_lock_irqsave(&ohci->lock, flags);
|
||||
// These members are used to cancel the packet.
|
||||
packet->driver_data = NULL;
|
||||
INIT_LIST_HEAD(&packet->link_for_local);
|
||||
|
||||
if (async_header_get_destination(packet->header) == ohci->node_id &&
|
||||
ohci->generation == packet->generation) {
|
||||
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||
|
||||
// Timestamping on behalf of the hardware.
|
||||
packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
|
||||
|
||||
handle_local_request(ctx, packet);
|
||||
return;
|
||||
scoped_guard(spinlock_irqsave, &ohci->lock) {
|
||||
if (!destination_is_local(packet, ohci)) {
|
||||
if (!at_context_queue_packet(ctx, packet))
|
||||
use_work = false;
|
||||
}
|
||||
}
|
||||
|
||||
ret = at_context_queue_packet(ctx, packet);
|
||||
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||
|
||||
if (ret < 0) {
|
||||
// Timestamping on behalf of the hardware.
|
||||
packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
|
||||
|
||||
packet->callback(packet, &ohci->card, packet->ack);
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user