summaryrefslogtreecommitdiffstats
path: root/drivers/firewire
diff options
context:
space:
mode:
authorKumar Kartikeya Dwivedi <memxor@gmail.com>2026-09-04 10:43:19 +0200
committerAlexei Starovoitov <ast@kernel.org>2026-09-04 07:58:36 -0700
commit6668ed271eaefaa63e686bdfbedaeb7b8e492722 (patch)
tree58e8288fead484f6755ad0815fc74c3742663761 /drivers/firewire
downloadlinux-stable-6668ed271eaefaa63e686bdfbedaeb7b8e492722.tar.gz
linux-stable-6668ed271eaefaa63e686bdfbedaeb7b8e492722.zip
selftests/bpf: Reject graph kptr use after RCU unlockgrafted
Add a sleepable verifier test that loads a graph-node local kptr in an explicit RCU read-side critical section, then passes its node to bpf_rbtree_remove() after the section ends. Before the verifier fix, the stale NON_OWN_REF flag makes the node look like a live borrowed reference and the program is accepted. After the fix, the pointer is demoted without NON_OWN_REF and the graph kfunc argument is rejected. Also exercise a graph kptr loaded while a spin lock provides implicit RCU protection. The pointer must be invalidated when the lock is released, which guards the required ordering between non-owning-reference invalidation and RCU demotion. Update the existing fault-protected load test state description. The post-unlock pointer no longer carries NON_OWN_REF, but remains readable because the load is rewritten to use BPF_PROBE_MEM. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Link: https://lore.kernel.org/r/20260904084325.52250-7-memxor@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'drivers/firewire')
-rw-r--r--drivers/firewire/.kunitconfig8
-rw-r--r--drivers/firewire/Kconfig162
-rw-r--r--drivers/firewire/Makefile22
-rw-r--r--drivers/firewire/core-card.c837
-rw-r--r--drivers/firewire/core-cdev.c1988
-rw-r--r--drivers/firewire/core-device.c1444
-rw-r--r--drivers/firewire/core-iso.c459
-rw-r--r--drivers/firewire/core-topology.c509
-rw-r--r--drivers/firewire/core-trace.c16
-rw-r--r--drivers/firewire/core-transaction.c1508
-rw-r--r--drivers/firewire/core.h325
-rw-r--r--drivers/firewire/device-attribute-test.c253
-rw-r--r--drivers/firewire/init_ohci1394_dma.c306
-rw-r--r--drivers/firewire/net.c1711
-rw-r--r--drivers/firewire/nosy-user.h26
-rw-r--r--drivers/firewire/nosy.c719
-rw-r--r--drivers/firewire/nosy.h238
-rw-r--r--drivers/firewire/ohci-serdes-test.c122
-rw-r--r--drivers/firewire/ohci.c3835
-rw-r--r--drivers/firewire/ohci.h398
-rw-r--r--drivers/firewire/packet-header-definitions.h236
-rw-r--r--drivers/firewire/packet-serdes-test.c917
-rw-r--r--drivers/firewire/phy-packet-definitions.h302
-rw-r--r--drivers/firewire/sbp2.c1622
-rw-r--r--drivers/firewire/self-id-sequence-helper-test.c152
-rw-r--r--drivers/firewire/uapi-test.c90
26 files changed, 18205 insertions, 0 deletions
diff --git a/drivers/firewire/.kunitconfig b/drivers/firewire/.kunitconfig
new file mode 100644
index 000000000..21b7e9eef
--- /dev/null
+++ b/drivers/firewire/.kunitconfig
@@ -0,0 +1,8 @@
+CONFIG_KUNIT=y
+CONFIG_PCI=y
+CONFIG_FIREWIRE=y
+CONFIG_FIREWIRE_KUNIT_UAPI_TEST=y
+CONFIG_FIREWIRE_KUNIT_DEVICE_ATTRIBUTE_TEST=y
+CONFIG_FIREWIRE_KUNIT_PACKET_SERDES_TEST=y
+CONFIG_FIREWIRE_KUNIT_SELF_ID_SEQUENCE_HELPER_TEST=y
+CONFIG_FIREWIRE_KUNIT_OHCI_SERDES_TEST=y
diff --git a/drivers/firewire/Kconfig b/drivers/firewire/Kconfig
new file mode 100644
index 000000000..a5f5e2502
--- /dev/null
+++ b/drivers/firewire/Kconfig
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "IEEE 1394 (FireWire) support"
+ depends on PCI || COMPILE_TEST
+ # firewire-core does not depend on PCI but is
+ # not useful without PCI controller driver
+
+config FIREWIRE
+ tristate "FireWire driver stack"
+ select CRC_ITU_T
+ help
+ This is the new-generation IEEE 1394 (FireWire) driver stack
+ a.k.a. Juju, a new implementation designed for robustness and
+ simplicity.
+ See http://ieee1394.docs.kernel.org/en/latest/migration.html
+ for information about migration from the older Linux 1394 stack
+ to the new driver stack.
+
+ To compile this driver as a module, say M here: the module will be
+ called firewire-core.
+
+config FIREWIRE_KUNIT_UAPI_TEST
+ tristate "KUnit tests for layout of structure in UAPI" if !KUNIT_ALL_TESTS
+ depends on FIREWIRE && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the KUnit tests whether structures exposed to user
+ space have expected layout.
+
+ 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_KUNIT_DEVICE_ATTRIBUTE_TEST
+ tristate "KUnit tests for device attributes" if !KUNIT_ALL_TESTS
+ depends on FIREWIRE && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the KUnit tests for device attribute for node and
+ unit.
+
+ 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_KUNIT_PACKET_SERDES_TEST
+ tristate "KUnit tests for packet serialization/deserialization" if !KUNIT_ALL_TESTS
+ depends on FIREWIRE && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the KUnit tests for packet serialization and
+ deserialization.
+
+ 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_KUNIT_SELF_ID_SEQUENCE_HELPER_TEST
+ tristate "KUnit tests for helpers of self ID sequence" if !KUNIT_ALL_TESTS
+ depends on FIREWIRE && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the KUnit tests for helpers of self ID sequence.
+
+ 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
+ help
+ Enable this driver if you have a FireWire controller based
+ on the OHCI specification. For all practical purposes, this
+ is the only chipset in use, so say Y here.
+
+ To compile this driver as a module, say M here: The module will be
+ called firewire-ohci.
+
+config FIREWIRE_KUNIT_OHCI_SERDES_TEST
+ tristate "KUnit tests for serialization/deserialization of data in buffers/registers" if !KUNIT_ALL_TESTS
+ depends on FIREWIRE && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the KUnit tests to check serialization and deserialization
+ of data in buffers and registers defined in 1394 OHCI specification.
+
+ 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_SBP2
+ tristate "Storage devices (SBP-2 protocol)"
+ depends on FIREWIRE && SCSI
+ help
+ This option enables you to use SBP-2 devices connected to a
+ FireWire bus. SBP-2 devices include storage devices like
+ harddisks and DVD drives, also some other FireWire devices
+ like scanners.
+
+ To compile this driver as a module, say M here: The module will be
+ called firewire-sbp2.
+
+ You should also enable support for disks, CD-ROMs, etc. in the SCSI
+ configuration section.
+
+config FIREWIRE_NET
+ tristate "IP networking over 1394"
+ depends on FIREWIRE && INET
+ help
+ This enables IPv4/IPv6 over IEEE 1394, providing IP connectivity
+ with other implementations of RFC 2734/3146 as found on several
+ operating systems. Multicast support is currently limited.
+
+ To compile this driver as a module, say M here: The module will be
+ called firewire-net.
+
+config FIREWIRE_NOSY
+ tristate "Nosy - a FireWire traffic sniffer for PCILynx cards"
+ depends on PCI
+ help
+ Nosy is an IEEE 1394 packet sniffer that is used for protocol
+ analysis and in development of IEEE 1394 drivers, applications,
+ or firmwares.
+
+ This driver lets you use a Texas Instruments PCILynx 1394 to PCI
+ link layer controller TSB12LV21/A/B as a low-budget bus analyzer.
+ PCILynx is a nowadays very rare IEEE 1394 controller which is
+ not OHCI 1394 compliant.
+
+ The following cards are known to be based on PCILynx or PCILynx-2:
+ IOI IOI-1394TT (PCI card), Unibrain Fireboard 400 PCI Lynx-2
+ (PCI card), Newer Technology FireWire 2 Go (CardBus card),
+ Apple Power Mac G3 blue & white and G4 with PCI graphics
+ (onboard controller).
+
+ To compile this driver as a module, say M here: The module will be
+ called nosy. Source code of a userspace interface to nosy, called
+ nosy-dump, can be found in tools/firewire/ of the kernel sources.
+
+ If unsure, say N.
+
+endmenu
diff --git a/drivers/firewire/Makefile b/drivers/firewire/Makefile
new file mode 100644
index 000000000..1ff550e93
--- /dev/null
+++ b/drivers/firewire/Makefile
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Linux IEEE 1394 implementation
+#
+
+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
+firewire-sbp2-y += sbp2.o
+firewire-net-y += net.o
+
+obj-$(CONFIG_FIREWIRE) += firewire-core.o
+obj-$(CONFIG_FIREWIRE_OHCI) += firewire-ohci.o
+obj-$(CONFIG_FIREWIRE_SBP2) += firewire-sbp2.o
+obj-$(CONFIG_FIREWIRE_NET) += firewire-net.o
+obj-$(CONFIG_FIREWIRE_NOSY) += nosy.o
+obj-$(CONFIG_PROVIDE_OHCI1394_DMA_INIT) += init_ohci1394_dma.o
+
+obj-$(CONFIG_FIREWIRE_KUNIT_UAPI_TEST) += uapi-test.o
+obj-$(CONFIG_FIREWIRE_KUNIT_PACKET_SERDES_TEST) += packet-serdes-test.o
+obj-$(CONFIG_FIREWIRE_KUNIT_SELF_ID_SEQUENCE_HELPER_TEST) += self-id-sequence-helper-test.o
+obj-$(CONFIG_FIREWIRE_KUNIT_OHCI_SERDES_TEST) += ohci-serdes-test.o
diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c
new file mode 100644
index 000000000..a754c6366
--- /dev/null
+++ b/drivers/firewire/core-card.c
@@ -0,0 +1,837 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/bug.h>
+#include <linux/completion.h>
+#include <linux/crc-itu-t.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include <linux/atomic.h>
+#include <asm/byteorder.h>
+
+#include "core.h"
+#include <trace/events/firewire.h>
+
+#define define_fw_printk_level(func, kern_level) \
+void func(const struct fw_card *card, const char *fmt, ...) \
+{ \
+ struct va_format vaf; \
+ va_list args; \
+ \
+ va_start(args, fmt); \
+ vaf.fmt = fmt; \
+ vaf.va = &args; \
+ printk(kern_level KBUILD_MODNAME " %s: %pV", \
+ dev_name(card->device), &vaf); \
+ va_end(args); \
+}
+define_fw_printk_level(fw_err, KERN_ERR);
+define_fw_printk_level(fw_notice, KERN_NOTICE);
+
+int fw_compute_block_crc(__be32 *block)
+{
+ int length;
+ u16 crc;
+
+ length = (be32_to_cpu(block[0]) >> 16) & 0xff;
+ crc = crc_itu_t(0, (u8 *)&block[1], length * 4);
+ *block |= cpu_to_be32(crc);
+
+ return length;
+}
+
+static DEFINE_MUTEX(card_mutex);
+static LIST_HEAD(card_list);
+
+static LIST_HEAD(descriptor_list);
+static int descriptor_count;
+
+static __be32 tmp_config_rom[256];
+/* ROM header, bus info block, root dir header, capabilities = 7 quadlets */
+static size_t config_rom_length = 1 + 4 + 1 + 1;
+
+#define BIB_CRC(v) ((v) << 0)
+#define BIB_CRC_LENGTH(v) ((v) << 16)
+#define BIB_INFO_LENGTH(v) ((v) << 24)
+#define BIB_BUS_NAME 0x31333934 /* "1394" */
+#define BIB_LINK_SPEED(v) ((v) << 0)
+#define BIB_GENERATION(v) ((v) << 4)
+#define BIB_MAX_ROM(v) ((v) << 8)
+#define BIB_MAX_RECEIVE(v) ((v) << 12)
+#define BIB_CYC_CLK_ACC(v) ((v) << 16)
+#define BIB_PMC ((1) << 27)
+#define BIB_BMC ((1) << 28)
+#define BIB_ISC ((1) << 29)
+#define BIB_CMC ((1) << 30)
+#define BIB_IRMC ((1) << 31)
+#define NODE_CAPABILITIES 0x0c0083c0 /* per IEEE 1394 clause 8.3.2.6.5.2 */
+
+/*
+ * IEEE-1394 specifies a default SPLIT_TIMEOUT value of 800 cycles (100 ms),
+ * but we have to make it longer because there are many devices whose firmware
+ * is just too slow for that.
+ */
+#define DEFAULT_SPLIT_TIMEOUT (2 * 8000)
+
+static void generate_config_rom(struct fw_card *card, __be32 *config_rom)
+{
+ struct fw_descriptor *desc;
+ int i, j, k, length;
+
+ /*
+ * Initialize contents of config rom buffer. On the OHCI
+ * controller, block reads to the config rom accesses the host
+ * memory, but quadlet read access the hardware bus info block
+ * registers. That's just crack, but it means we should make
+ * sure the contents of bus info block in host memory matches
+ * the version stored in the OHCI registers.
+ */
+
+ config_rom[0] = cpu_to_be32(
+ BIB_CRC_LENGTH(4) | BIB_INFO_LENGTH(4) | BIB_CRC(0));
+ config_rom[1] = cpu_to_be32(BIB_BUS_NAME);
+ config_rom[2] = cpu_to_be32(
+ BIB_LINK_SPEED(card->link_speed) |
+ BIB_GENERATION(card->config_rom_generation++ % 14 + 2) |
+ BIB_MAX_ROM(2) |
+ BIB_MAX_RECEIVE(card->max_receive) |
+ BIB_BMC | BIB_ISC | BIB_CMC | BIB_IRMC);
+ config_rom[3] = cpu_to_be32(card->guid >> 32);
+ config_rom[4] = cpu_to_be32(card->guid);
+
+ /* Generate root directory. */
+ config_rom[6] = cpu_to_be32(NODE_CAPABILITIES);
+ i = 7;
+ j = 7 + descriptor_count;
+
+ /* Generate root directory entries for descriptors. */
+ list_for_each_entry (desc, &descriptor_list, link) {
+ if (desc->immediate > 0)
+ config_rom[i++] = cpu_to_be32(desc->immediate);
+ config_rom[i] = cpu_to_be32(desc->key | (j - i));
+ i++;
+ j += desc->length;
+ }
+
+ /* Update root directory length. */
+ config_rom[5] = cpu_to_be32((i - 5 - 1) << 16);
+
+ /* End of root directory, now copy in descriptors. */
+ list_for_each_entry (desc, &descriptor_list, link) {
+ for (k = 0; k < desc->length; k++)
+ config_rom[i + k] = cpu_to_be32(desc->data[k]);
+ i += desc->length;
+ }
+
+ /* Calculate CRCs for all blocks in the config rom. This
+ * assumes that CRC length and info length are identical for
+ * the bus info block, which is always the case for this
+ * implementation. */
+ for (i = 0; i < j; i += length + 1)
+ length = fw_compute_block_crc(config_rom + i);
+
+ WARN_ON(j != config_rom_length);
+}
+
+static void update_config_roms(void)
+{
+ struct fw_card *card;
+
+ 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);
+ }
+}
+
+static size_t required_space(struct fw_descriptor *desc)
+{
+ /* descriptor + entry into root dir + optional immediate entry */
+ return desc->length + 1 + (desc->immediate > 0 ? 1 : 0);
+}
+
+int fw_core_add_descriptor(struct fw_descriptor *desc)
+{
+ size_t i;
+
+ /*
+ * Check descriptor is valid; the length of all blocks in the
+ * descriptor has to add up to exactly the length of the
+ * block.
+ */
+ i = 0;
+ while (i < desc->length)
+ i += (desc->data[i] >> 16) + 1;
+
+ if (i != desc->length)
+ return -EINVAL;
+
+ guard(mutex)(&card_mutex);
+
+ if (config_rom_length + required_space(desc) > 256)
+ return -EBUSY;
+
+ list_add_tail(&desc->link, &descriptor_list);
+ config_rom_length += required_space(desc);
+ descriptor_count++;
+ if (desc->immediate > 0)
+ descriptor_count++;
+ update_config_roms();
+
+ return 0;
+}
+EXPORT_SYMBOL(fw_core_add_descriptor);
+
+void fw_core_remove_descriptor(struct fw_descriptor *desc)
+{
+ guard(mutex)(&card_mutex);
+
+ list_del(&desc->link);
+ config_rom_length -= required_space(desc);
+ descriptor_count--;
+ if (desc->immediate > 0)
+ descriptor_count--;
+ update_config_roms();
+}
+EXPORT_SYMBOL(fw_core_remove_descriptor);
+
+static int reset_bus(struct fw_card *card, bool short_reset)
+{
+ int reg = short_reset ? 5 : 1;
+ int bit = short_reset ? PHY_BUS_SHORT_RESET : PHY_BUS_RESET;
+
+ trace_bus_reset_initiate(card->index, card->generation, short_reset);
+
+ return card->driver->update_phy_reg(card, reg, 0, bit);
+}
+
+void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset)
+{
+ trace_bus_reset_schedule(card->index, card->generation, short_reset);
+
+ /* We don't try hard to sort out requests of long vs. short resets. */
+ card->br_short = short_reset;
+
+ /* Use an arbitrary short delay to combine multiple reset requests. */
+ fw_card_get(card);
+ if (!queue_delayed_work(fw_workqueue, &card->br_work, delayed ? msecs_to_jiffies(10) : 0))
+ fw_card_put(card);
+}
+EXPORT_SYMBOL(fw_schedule_bus_reset);
+
+static void br_work(struct work_struct *work)
+{
+ struct fw_card *card = from_work(card, work, br_work.work);
+
+ /* Delay for 2s after last reset per IEEE 1394 clause 8.2.1. */
+ if (card->reset_jiffies != 0 &&
+ time_is_after_jiffies64(card->reset_jiffies + secs_to_jiffies(2))) {
+ trace_bus_reset_postpone(card->index, card->generation, card->br_short);
+
+ if (!queue_delayed_work(fw_workqueue, &card->br_work, secs_to_jiffies(2)))
+ fw_card_put(card);
+ return;
+ }
+
+ fw_send_phy_config(card, FW_PHY_CONFIG_NO_NODE_ID, card->generation,
+ FW_PHY_CONFIG_CURRENT_GAP_COUNT);
+ reset_bus(card, card->br_short);
+ fw_card_put(card);
+}
+
+static void allocate_broadcast_channel(struct fw_card *card, int generation)
+{
+ int channel, bandwidth = 0;
+
+ if (!card->broadcast_channel_allocated) {
+ fw_iso_resource_manage(card, generation, 1ULL << 31,
+ &channel, &bandwidth, true);
+ if (channel != 31) {
+ fw_notice(card, "failed to allocate broadcast channel\n");
+ return;
+ }
+ card->broadcast_channel_allocated = true;
+ }
+
+ device_for_each_child(card->device, (void *)(long)generation,
+ fw_device_set_broadcast_channel);
+}
+
+void fw_schedule_bm_work(struct fw_card *card, unsigned long delay)
+{
+ fw_card_get(card);
+ if (!schedule_delayed_work(&card->bm_work, delay))
+ fw_card_put(card);
+}
+
+enum bm_contention_outcome {
+ // The bus management contention window is not expired.
+ BM_CONTENTION_OUTCOME_WITHIN_WINDOW = 0,
+ // The IRM node has link off.
+ BM_CONTENTION_OUTCOME_IRM_HAS_LINK_OFF,
+ // The IRM node complies IEEE 1394:1994 only.
+ BM_CONTENTION_OUTCOME_IRM_COMPLIES_1394_1995_ONLY,
+ // Another bus reset, BM work has been rescheduled.
+ BM_CONTENTION_OUTCOME_AT_NEW_GENERATION,
+ // We have been unable to send the lock request to IRM node due to some local problem.
+ BM_CONTENTION_OUTCOME_LOCAL_PROBLEM_AT_TRANSACTION,
+ // The lock request failed, maybe the IRM isn't really IRM capable after all.
+ BM_CONTENTION_OUTCOME_IRM_IS_NOT_CAPABLE_FOR_IRM,
+ // Somebody else is BM.
+ BM_CONTENTION_OUTCOME_IRM_HOLDS_ANOTHER_NODE_AS_BM,
+ // The local node succeeds after contending for bus manager.
+ BM_CONTENTION_OUTCOME_IRM_HOLDS_LOCAL_NODE_AS_BM,
+};
+
+static enum bm_contention_outcome contend_for_bm(struct fw_card *card)
+__must_hold(&card->lock)
+{
+ int generation = card->generation;
+ int local_id = card->local_node->node_id;
+ __be32 data[2] = {
+ cpu_to_be32(BUS_MANAGER_ID_NOT_REGISTERED),
+ cpu_to_be32(local_id),
+ };
+ bool grace = time_is_before_jiffies64(card->reset_jiffies + msecs_to_jiffies(125));
+ struct fw_node *irm_node;
+ struct fw_device *irm_device;
+ int irm_node_id, irm_device_quirks = 0;
+ int rcode;
+
+ lockdep_assert_held(&card->lock);
+
+ if (!grace) {
+ if (!is_next_generation(generation, card->bm_generation) || card->bm_abdicate)
+ return BM_CONTENTION_OUTCOME_WITHIN_WINDOW;
+ }
+
+ irm_node = card->irm_node;
+ if (!irm_node->link_on) {
+ fw_notice(card, "IRM has link off, making local node (%02x) root\n", local_id);
+ return BM_CONTENTION_OUTCOME_IRM_HAS_LINK_OFF;
+ }
+
+ // NOTE: It is likely that the quirk detection for IRM device has not done yet.
+ irm_device = fw_node_get_device(irm_node);
+ if (irm_device)
+ irm_device_quirks = READ_ONCE(irm_device->quirks);
+ if ((irm_device_quirks & FW_DEVICE_QUIRK_IRM_IS_1394_1995_ONLY) &&
+ !(irm_device_quirks & FW_DEVICE_QUIRK_IRM_IGNORES_BUS_MANAGER)) {
+ fw_notice(card, "IRM is not 1394a compliant, making local node (%02x) root\n",
+ local_id);
+ return BM_CONTENTION_OUTCOME_IRM_COMPLIES_1394_1995_ONLY;
+ }
+
+ irm_node_id = irm_node->node_id;
+
+ spin_unlock_irq(&card->lock);
+
+ rcode = fw_run_transaction(card, TCODE_LOCK_COMPARE_SWAP, irm_node_id, generation,
+ SCODE_100, CSR_REGISTER_BASE + CSR_BUS_MANAGER_ID, data,
+ sizeof(data));
+
+ spin_lock_irq(&card->lock);
+
+ switch (rcode) {
+ case RCODE_GENERATION:
+ return BM_CONTENTION_OUTCOME_AT_NEW_GENERATION;
+ case RCODE_SEND_ERROR:
+ return BM_CONTENTION_OUTCOME_LOCAL_PROBLEM_AT_TRANSACTION;
+ case RCODE_COMPLETE:
+ {
+ int bm_id = be32_to_cpu(data[0]);
+
+ // Used by cdev layer for "struct fw_cdev_event_bus_reset".
+ if (bm_id != BUS_MANAGER_ID_NOT_REGISTERED)
+ card->bm_node_id = 0xffc0 & bm_id;
+ else
+ card->bm_node_id = local_id;
+
+ if (bm_id != BUS_MANAGER_ID_NOT_REGISTERED)
+ return BM_CONTENTION_OUTCOME_IRM_HOLDS_ANOTHER_NODE_AS_BM;
+ else
+ return BM_CONTENTION_OUTCOME_IRM_HOLDS_LOCAL_NODE_AS_BM;
+ }
+ default:
+ if (!(irm_device_quirks & FW_DEVICE_QUIRK_IRM_IGNORES_BUS_MANAGER)) {
+ fw_notice(card, "BM lock failed (%s), making local node (%02x) root\n",
+ fw_rcode_string(rcode), local_id);
+ return BM_CONTENTION_OUTCOME_IRM_COMPLIES_1394_1995_ONLY;
+ } else {
+ return BM_CONTENTION_OUTCOME_IRM_IS_NOT_CAPABLE_FOR_IRM;
+ }
+ }
+}
+
+DEFINE_FREE(node_unref, struct fw_node *, if (_T) fw_node_put(_T))
+DEFINE_FREE(card_unref, struct fw_card *, if (_T) fw_card_put(_T))
+
+static void bm_work(struct work_struct *work)
+{
+ static const char gap_count_table[] = {
+ 63, 5, 7, 8, 10, 13, 16, 18, 21, 24, 26, 29, 32, 35, 37, 40
+ };
+ struct fw_card *card __free(card_unref) = from_work(card, work, bm_work.work);
+ struct fw_node *root_node __free(node_unref) = NULL;
+ int root_id, new_root_id, irm_id, local_id;
+ int expected_gap_count, generation;
+ bool stand_for_root = false;
+
+ spin_lock_irq(&card->lock);
+
+ if (card->local_node == NULL) {
+ spin_unlock_irq(&card->lock);
+ return;
+ }
+
+ generation = card->generation;
+
+ root_node = fw_node_get(card->root_node);
+
+ root_id = root_node->node_id;
+ irm_id = card->irm_node->node_id;
+ local_id = card->local_node->node_id;
+
+ if (card->bm_generation != generation) {
+ enum bm_contention_outcome result = contend_for_bm(card);
+
+ switch (result) {
+ case BM_CONTENTION_OUTCOME_WITHIN_WINDOW:
+ spin_unlock_irq(&card->lock);
+ fw_schedule_bm_work(card, msecs_to_jiffies(125));
+ return;
+ case BM_CONTENTION_OUTCOME_IRM_HAS_LINK_OFF:
+ stand_for_root = true;
+ break;
+ case BM_CONTENTION_OUTCOME_IRM_COMPLIES_1394_1995_ONLY:
+ stand_for_root = true;
+ break;
+ case BM_CONTENTION_OUTCOME_AT_NEW_GENERATION:
+ // BM work has been rescheduled.
+ spin_unlock_irq(&card->lock);
+ return;
+ case BM_CONTENTION_OUTCOME_LOCAL_PROBLEM_AT_TRANSACTION:
+ // Let's try again later and hope that the local problem has gone away by
+ // then.
+ spin_unlock_irq(&card->lock);
+ fw_schedule_bm_work(card, msecs_to_jiffies(125));
+ return;
+ case BM_CONTENTION_OUTCOME_IRM_IS_NOT_CAPABLE_FOR_IRM:
+ // Let's do a bus reset and pick the local node as root, and thus, IRM.
+ stand_for_root = true;
+ break;
+ case BM_CONTENTION_OUTCOME_IRM_HOLDS_ANOTHER_NODE_AS_BM:
+ if (local_id == irm_id) {
+ // Only acts as IRM.
+ spin_unlock_irq(&card->lock);
+ allocate_broadcast_channel(card, generation);
+ spin_lock_irq(&card->lock);
+ }
+ fallthrough;
+ case BM_CONTENTION_OUTCOME_IRM_HOLDS_LOCAL_NODE_AS_BM:
+ default:
+ card->bm_generation = generation;
+ break;
+ }
+ }
+
+ // We're bus manager for this generation, so next step is to make sure we have an active
+ // cycle master and do gap count optimization.
+ if (!stand_for_root) {
+ if (card->gap_count == GAP_COUNT_MISMATCHED) {
+ // If self IDs have inconsistent gap counts, do a
+ // bus reset ASAP. The config rom read might never
+ // complete, so don't wait for it. However, still
+ // send a PHY configuration packet prior to the
+ // bus reset. The PHY configuration packet might
+ // fail, but 1394-2008 8.4.5.2 explicitly permits
+ // it in this case, so it should be safe to try.
+ stand_for_root = true;
+
+ // We must always send a bus reset if the gap count
+ // is inconsistent, so bypass the 5-reset limit.
+ card->bm_retries = 0;
+ } else {
+ // Now investigate root node.
+ struct fw_device *root_device = fw_node_get_device(root_node);
+
+ if (root_device == NULL) {
+ // Either link_on is false, or we failed to read the
+ // config rom. In either case, pick another root.
+ stand_for_root = true;
+ } else {
+ bool root_device_is_running =
+ atomic_read(&root_device->state) == FW_DEVICE_RUNNING;
+
+ if (!root_device_is_running) {
+ // If we haven't probed this device yet, bail out now
+ // and let's try again once that's done.
+ spin_unlock_irq(&card->lock);
+ return;
+ } else if (!root_device->cmc) {
+ // Current root has an active link layer and we
+ // successfully read the config rom, but it's not
+ // cycle master capable.
+ stand_for_root = true;
+ }
+ }
+ }
+ }
+
+ if (stand_for_root) {
+ new_root_id = local_id;
+ } else {
+ // We will send out a force root packet for this node as part of the gap count
+ // optimization on behalf of the node.
+ new_root_id = root_id;
+ }
+
+ /*
+ * Pick a gap count from 1394a table E-1. The table doesn't cover
+ * the typically much larger 1394b beta repeater delays though.
+ */
+ if (!card->beta_repeaters_present &&
+ root_node->max_hops < ARRAY_SIZE(gap_count_table))
+ expected_gap_count = gap_count_table[root_node->max_hops];
+ else
+ expected_gap_count = 63;
+
+ // Finally, figure out if we should do a reset or not. If we have done less than 5 resets
+ // with the same physical topology and we have either a new root or a new gap count
+ // setting, let's do it.
+ if (card->bm_retries++ < 5 && (card->gap_count != expected_gap_count || new_root_id != root_id)) {
+ int card_gap_count = card->gap_count;
+
+ spin_unlock_irq(&card->lock);
+
+ fw_notice(card, "phy config: new root=%x, gap_count=%d\n",
+ new_root_id, expected_gap_count);
+ fw_send_phy_config(card, new_root_id, generation, expected_gap_count);
+ /*
+ * Where possible, use a short bus reset to minimize
+ * disruption to isochronous transfers. But in the event
+ * of a gap count inconsistency, use a long bus reset.
+ *
+ * As noted in 1394a 8.4.6.2, nodes on a mixed 1394/1394a bus
+ * may set different gap counts after a bus reset. On a mixed
+ * 1394/1394a bus, a short bus reset can get doubled. Some
+ * nodes may treat the double reset as one bus reset and others
+ * may treat it as two, causing a gap count inconsistency
+ * again. Using a long bus reset prevents this.
+ */
+ reset_bus(card, card_gap_count != 0);
+ /* Will allocate broadcast channel after the reset. */
+ } else {
+ struct fw_device *root_device = fw_node_get_device(root_node);
+
+ spin_unlock_irq(&card->lock);
+
+ if (root_device && root_device->cmc) {
+ // Make sure that the cycle master sends cycle start packets.
+ __be32 data = cpu_to_be32(CSR_STATE_BIT_CMSTR);
+ int rcode = fw_run_transaction(card, TCODE_WRITE_QUADLET_REQUEST,
+ root_id, generation, SCODE_100,
+ CSR_REGISTER_BASE + CSR_STATE_SET,
+ &data, sizeof(data));
+ if (rcode == RCODE_GENERATION)
+ return;
+ }
+
+ if (local_id == irm_id)
+ allocate_broadcast_channel(card, generation);
+ }
+}
+
+void fw_card_initialize(struct fw_card *card,
+ const struct fw_card_driver *driver,
+ struct device *device)
+{
+ static atomic_t index = ATOMIC_INIT(-1);
+
+ card->index = atomic_inc_return(&index);
+ card->driver = driver;
+ card->device = device;
+
+ card->transactions.current_tlabel = 0;
+ card->transactions.tlabel_mask = 0;
+ INIT_LIST_HEAD(&card->transactions.list);
+ spin_lock_init(&card->transactions.lock);
+
+ spin_lock_init(&card->topology_map.lock);
+
+ card->split_timeout.hi = DEFAULT_SPLIT_TIMEOUT / 8000;
+ card->split_timeout.lo = (DEFAULT_SPLIT_TIMEOUT % 8000) << 19;
+ card->split_timeout.cycles = DEFAULT_SPLIT_TIMEOUT;
+ card->split_timeout.jiffies = isoc_cycles_to_jiffies(DEFAULT_SPLIT_TIMEOUT);
+ spin_lock_init(&card->split_timeout.lock);
+
+ card->color = 0;
+ card->broadcast_channel = BROADCAST_CHANNEL_INITIAL;
+
+ kref_init(&card->kref);
+ init_completion(&card->done);
+
+ spin_lock_init(&card->lock);
+
+ card->local_node = NULL;
+
+ INIT_DELAYED_WORK(&card->br_work, br_work);
+ INIT_DELAYED_WORK(&card->bm_work, bm_work);
+}
+EXPORT_SYMBOL(fw_card_initialize);
+
+DEFINE_FREE(workqueue_destroy, struct workqueue_struct *, if (_T) destroy_workqueue(_T))
+
+int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid,
+ unsigned int supported_isoc_contexts)
+{
+ struct workqueue_struct *isoc_wq __free(workqueue_destroy) = NULL;
+ struct workqueue_struct *async_wq __free(workqueue_destroy) = NULL;
+ int ret;
+
+ // This workqueue should be:
+ // * != WQ_BH Sleepable.
+ // * == WQ_UNBOUND Any core can process data for isoc context. The
+ // implementation of unit protocol could consumes the core
+ // longer somehow.
+ // * != WQ_MEM_RECLAIM Not used for any backend of block device.
+ // * == WQ_FREEZABLE Isochronous communication is at regular interval in real
+ // time, thus should be drained if possible at freeze phase.
+ // * == WQ_HIGHPRI High priority to process semi-realtime timestamped data.
+ // * == WQ_SYSFS Parameters are available via sysfs.
+ // * max_active == n_it + n_ir A hardIRQ could notify events for multiple isochronous
+ // contexts if they are scheduled to the same cycle.
+ isoc_wq = alloc_workqueue("firewire-isoc-card%u",
+ WQ_UNBOUND | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS,
+ supported_isoc_contexts, card->index);
+ if (!isoc_wq)
+ return -ENOMEM;
+
+ // This workqueue should be:
+ // * != WQ_BH Sleepable.
+ // * == WQ_UNBOUND Any core can process data for asynchronous context.
+ // * == WQ_MEM_RECLAIM Used for any backend of block device.
+ // * == 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.
+ async_wq = alloc_workqueue("firewire-async-card%u",
+ WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS,
+ 4, card->index);
+ if (!async_wq)
+ return -ENOMEM;
+
+ card->isoc_wq = isoc_wq;
+ card->async_wq = async_wq;
+ card->max_receive = max_receive;
+ card->link_speed = link_speed;
+ card->guid = guid;
+
+ scoped_guard(mutex, &card_mutex) {
+ generate_config_rom(card, tmp_config_rom);
+ ret = card->driver->enable(card, tmp_config_rom, config_rom_length);
+ if (ret < 0) {
+ card->isoc_wq = NULL;
+ card->async_wq = NULL;
+ return ret;
+ }
+ retain_and_null_ptr(isoc_wq);
+ retain_and_null_ptr(async_wq);
+
+ list_add_tail(&card->link, &card_list);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(fw_card_add);
+
+/*
+ * The next few functions implement a dummy driver that is used once a card
+ * driver shuts down an fw_card. This allows the driver to cleanly unload,
+ * as all IO to the card will be handled (and failed) by the dummy driver
+ * instead of calling into the module. Only functions for iso context
+ * shutdown still need to be provided by the card driver.
+ *
+ * .read/write_csr() should never be called anymore after the dummy driver
+ * was bound since they are only used within request handler context.
+ * .set_config_rom() is never called since the card is taken out of card_list
+ * before switching to the dummy driver.
+ */
+
+static int dummy_read_phy_reg(struct fw_card *card, int address)
+{
+ return -ENODEV;
+}
+
+static int dummy_update_phy_reg(struct fw_card *card, int address,
+ int clear_bits, int set_bits)
+{
+ return -ENODEV;
+}
+
+static void dummy_send_request(struct fw_card *card, struct fw_packet *packet)
+{
+ packet->callback(packet, card, RCODE_CANCELLED);
+}
+
+static void dummy_send_response(struct fw_card *card, struct fw_packet *packet)
+{
+ packet->callback(packet, card, RCODE_CANCELLED);
+}
+
+static int dummy_cancel_packet(struct fw_card *card, struct fw_packet *packet)
+{
+ return -ENOENT;
+}
+
+static int dummy_enable_phys_dma(struct fw_card *card,
+ int node_id, int generation)
+{
+ return -ENODEV;
+}
+
+static struct fw_iso_context *dummy_allocate_iso_context(struct fw_card *card, int type,
+ int channel, size_t header_size, size_t header_storage_size)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static u32 dummy_read_csr(struct fw_card *card, int csr_offset)
+{
+ return 0;
+}
+
+static void dummy_write_csr(struct fw_card *card, int csr_offset, u32 value)
+{
+}
+
+static int dummy_start_iso(struct fw_iso_context *ctx,
+ s32 cycle, u32 sync, u32 tags)
+{
+ return -ENODEV;
+}
+
+static int dummy_set_iso_channels(struct fw_iso_context *ctx, u64 *channels)
+{
+ return -ENODEV;
+}
+
+static int dummy_queue_iso(struct fw_iso_context *ctx, struct fw_iso_packet *p,
+ struct fw_iso_buffer *buffer, unsigned long payload)
+{
+ return -ENODEV;
+}
+
+static void dummy_flush_queue_iso(struct fw_iso_context *ctx)
+{
+}
+
+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);
+
+ complete(&card->done);
+}
+EXPORT_SYMBOL_GPL(fw_card_release);
+
+void fw_core_remove_card(struct fw_card *card)
+{
+ struct fw_card_driver dummy_driver = dummy_driver_template;
+
+ might_sleep();
+
+ card->driver->update_phy_reg(card, 4,
+ PHY_LINK_ACTIVE | PHY_CONTENDER, 0);
+ fw_schedule_bus_reset(card, false, true);
+
+ 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;
+
+ drain_workqueue(card->isoc_wq);
+ drain_workqueue(card->async_wq);
+ card->driver->disable(card);
+ fw_cancel_pending_transactions(card);
+
+ scoped_guard(spinlock_irqsave, &card->lock)
+ fw_destroy_nodes(card);
+
+ /* Wait for all users, especially device workqueue jobs, to finish. */
+ fw_card_put(card);
+ wait_for_completion(&card->done);
+
+ destroy_workqueue(card->isoc_wq);
+ destroy_workqueue(card->async_wq);
+
+ WARN_ON(!list_empty(&card->transactions.list));
+}
+EXPORT_SYMBOL(fw_core_remove_card);
+
+/**
+ * fw_card_read_cycle_time: read from Isochronous Cycle Timer Register of 1394 OHCI in MMIO region
+ * for controller card.
+ * @card: The instance of card for 1394 OHCI controller.
+ * @cycle_time: The mutual reference to value of cycle time for the read operation.
+ *
+ * Read value from Isochronous Cycle Timer Register of 1394 OHCI in MMIO region for the given
+ * controller card. This function accesses the region without any lock primitives or IRQ mask.
+ * When returning successfully, the content of @value argument has value aligned to host endianness,
+ * formetted by CYCLE_TIME CSR Register of IEEE 1394 std.
+ *
+ * Context: Any context.
+ * Return:
+ * * 0 - Read successfully.
+ * * -ENODEV - The controller is unavailable due to being removed or unbound.
+ */
+int fw_card_read_cycle_time(struct fw_card *card, u32 *cycle_time)
+{
+ if (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);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fw_card_read_cycle_time);
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c
new file mode 100644
index 000000000..e49d8a58b
--- /dev/null
+++ b/drivers/firewire/core-cdev.c
@@ -0,0 +1,1988 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Char device for device raw access
+ *
+ * Copyright (C) 2005-2007 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/bug.h>
+#include <linux/compat.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/firewire.h>
+#include <linux/firewire-cdev.h>
+#include <linux/irqflags.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched.h> /* required for linux/wait.h */
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/time.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+
+#include "core.h"
+#include <trace/events/firewire.h>
+
+#include "packet-header-definitions.h"
+
+/*
+ * ABI version history is documented in linux/firewire-cdev.h.
+ */
+#define FW_CDEV_KERNEL_VERSION 6
+#define FW_CDEV_VERSION_EVENT_REQUEST2 4
+#define FW_CDEV_VERSION_ALLOCATE_REGION_END 4
+#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 LIST_HEAD(phy_receiver_list);
+
+struct client {
+ u32 version;
+ struct fw_device *device;
+
+ spinlock_t lock;
+ bool in_shutdown;
+ struct xarray resource_xa;
+ struct list_head event_list;
+ wait_queue_head_t wait;
+ wait_queue_head_t tx_flush_wait;
+ u64 bus_reset_closure;
+
+ struct fw_iso_context *iso_context;
+ struct mutex iso_context_mutex;
+ u64 iso_closure;
+ struct fw_iso_buffer buffer;
+ unsigned long vm_start;
+
+ struct list_head phy_receiver_link;
+ u64 phy_receiver_closure;
+
+ struct list_head link;
+ struct kref kref;
+};
+
+static inline void client_get(struct client *client)
+{
+ kref_get(&client->kref);
+}
+
+static void client_release(struct kref *kref)
+{
+ struct client *client = container_of(kref, struct client, kref);
+
+ fw_device_put(client->device);
+ kfree(client);
+}
+
+static void client_put(struct client *client)
+{
+ kref_put(&client->kref, client_release);
+}
+
+struct client_resource;
+typedef void (*client_resource_release_fn_t)(struct client *,
+ struct client_resource *);
+struct client_resource {
+ client_resource_release_fn_t release;
+ int handle;
+};
+
+struct address_handler_resource {
+ struct client_resource resource;
+ struct fw_address_handler handler;
+ __u64 closure;
+ struct client *client;
+};
+
+struct outbound_transaction_resource {
+ struct client_resource resource;
+ struct fw_transaction transaction;
+};
+
+struct inbound_transaction_resource {
+ struct client_resource resource;
+ struct fw_card *card;
+ struct fw_request *request;
+ bool is_fcp;
+ void *data;
+ size_t length;
+};
+
+struct descriptor_resource {
+ struct client_resource resource;
+ struct fw_descriptor descriptor;
+ u32 data[];
+};
+
+struct iso_resource_params {
+ u64 channels_mask;
+ s32 bandwidth;
+};
+
+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;
+ int generation;
+ struct iso_resource_params params;
+ struct iso_resource_event *e_alloc, *e_dealloc;
+};
+
+struct iso_resource_once {
+ struct client *client;
+ struct work_struct work;
+ enum {
+ ISO_RES_ONCE_ALLOC,
+ ISO_RES_ONCE_DEALLOC,
+ } todo;
+ struct iso_resource_params params;
+ struct iso_resource_event *event;
+};
+
+static struct address_handler_resource *to_address_handler_resource(struct client_resource *resource)
+{
+ return container_of(resource, struct address_handler_resource, resource);
+}
+
+static struct inbound_transaction_resource *to_inbound_transaction_resource(struct client_resource *resource)
+{
+ return container_of(resource, struct inbound_transaction_resource, resource);
+}
+
+static struct descriptor_resource *to_descriptor_resource(struct client_resource *resource)
+{
+ return container_of(resource, struct descriptor_resource, resource);
+}
+
+static struct iso_resource_auto *to_iso_resource_auto(struct client_resource *resource)
+{
+ return container_of(resource, struct iso_resource_auto, resource);
+}
+
+static void release_iso_resource_auto(struct client *, struct client_resource *);
+
+static int is_iso_resource_auto(const struct client_resource *resource)
+{
+ return resource->release == release_iso_resource_auto;
+}
+
+static void release_transaction(struct client *client,
+ struct client_resource *resource);
+
+static int is_outbound_transaction_resource(const struct client_resource *resource)
+{
+ return resource->release == release_transaction;
+}
+
+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);
+}
+
+/*
+ * dequeue_event() just kfree()'s the event, so the event has to be
+ * the first field in a struct XYZ_event.
+ */
+struct event {
+ struct { void *data; size_t size; } v[2];
+ struct list_head link;
+};
+
+struct bus_reset_event {
+ struct event event;
+ struct fw_cdev_event_bus_reset reset;
+};
+
+struct outbound_transaction_event {
+ struct event event;
+ struct client *client;
+ struct outbound_transaction_resource r;
+ union {
+ struct fw_cdev_event_response without_tstamp;
+ struct fw_cdev_event_response2 with_tstamp;
+ } rsp;
+};
+
+struct inbound_transaction_event {
+ struct event event;
+ union {
+ struct fw_cdev_event_request request;
+ struct fw_cdev_event_request2 request2;
+ struct fw_cdev_event_request3 with_tstamp;
+ } req;
+};
+
+struct iso_interrupt_event {
+ struct event event;
+ struct fw_cdev_event_iso_interrupt interrupt;
+};
+
+struct iso_interrupt_mc_event {
+ struct event event;
+ struct fw_cdev_event_iso_interrupt_mc interrupt;
+};
+
+struct iso_resource_event {
+ struct event event;
+ struct fw_cdev_event_iso_resource iso_resource;
+};
+
+struct outbound_phy_packet_event {
+ struct event event;
+ struct client *client;
+ struct fw_packet p;
+ union {
+ struct fw_cdev_event_phy_packet without_tstamp;
+ struct fw_cdev_event_phy_packet2 with_tstamp;
+ } phy_packet;
+};
+
+struct inbound_phy_packet_event {
+ struct event event;
+ union {
+ struct fw_cdev_event_phy_packet without_tstamp;
+ struct fw_cdev_event_phy_packet2 with_tstamp;
+ } phy_packet;
+};
+
+#ifdef CONFIG_COMPAT
+static void __user *u64_to_uptr(u64 value)
+{
+ if (in_compat_syscall())
+ return compat_ptr(value);
+ else
+ return (void __user *)(unsigned long)value;
+}
+
+static u64 uptr_to_u64(void __user *ptr)
+{
+ if (in_compat_syscall())
+ return ptr_to_compat(ptr);
+ else
+ return (u64)(unsigned long)ptr;
+}
+#else
+static inline void __user *u64_to_uptr(u64 value)
+{
+ return (void __user *)(unsigned long)value;
+}
+
+static inline u64 uptr_to_u64(void __user *ptr)
+{
+ return (u64)(unsigned long)ptr;
+}
+#endif /* CONFIG_COMPAT */
+
+static int fw_device_op_open(struct inode *inode, struct file *file)
+{
+ struct fw_device *device;
+ struct client *client;
+
+ device = fw_device_get_by_devt(inode->i_rdev);
+ if (device == NULL)
+ return -ENODEV;
+
+ if (fw_device_is_shutdown(device)) {
+ fw_device_put(device);
+ return -ENODEV;
+ }
+
+ client = kzalloc_obj(*client);
+ if (client == NULL) {
+ fw_device_put(device);
+ return -ENOMEM;
+ }
+
+ client->device = device;
+ spin_lock_init(&client->lock);
+ xa_init_flags(&client->resource_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_BH);
+ INIT_LIST_HEAD(&client->event_list);
+ init_waitqueue_head(&client->wait);
+ init_waitqueue_head(&client->tx_flush_wait);
+ INIT_LIST_HEAD(&client->phy_receiver_link);
+ INIT_LIST_HEAD(&client->link);
+ kref_init(&client->kref);
+ mutex_init(&client->iso_context_mutex);
+
+ file->private_data = client;
+
+ return nonseekable_open(inode, file);
+}
+
+static void queue_event(struct client *client, struct event *event,
+ void *data0, size_t size0, void *data1, size_t size1)
+{
+ event->v[0].data = data0;
+ event->v[0].size = size0;
+ event->v[1].data = data1;
+ event->v[1].size = size1;
+
+ scoped_guard(spinlock_irqsave, &client->lock) {
+ if (client->in_shutdown)
+ kfree(event);
+ else
+ list_add_tail(&event->link, &client->event_list);
+ }
+
+ wake_up_interruptible(&client->wait);
+}
+
+static int 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;
+
+ if (list_empty(&client->event_list) &&
+ fw_device_is_shutdown(client->device))
+ return -ENODEV;
+
+ scoped_guard(spinlock_irq, &client->lock) {
+ event = list_first_entry(&client->event_list, struct event, link);
+ list_del(&event->link);
+ }
+
+ 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)) {
+ ret = -EFAULT;
+ goto out;
+ }
+ total += size;
+ }
+ ret = total;
+
+ out:
+ kfree(event);
+
+ return ret;
+}
+
+static ssize_t fw_device_op_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *offset)
+{
+ struct client *client = file->private_data;
+
+ return dequeue_event(client, buffer, count);
+}
+
+static void fill_bus_reset_event(struct fw_cdev_event_bus_reset *event,
+ struct client *client)
+{
+ struct fw_card *card = client->device->card;
+
+ guard(spinlock_irq)(&card->lock);
+
+ event->closure = client->bus_reset_closure;
+ event->type = FW_CDEV_EVENT_BUS_RESET;
+ event->generation = client->device->generation;
+ 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;
+}
+
+static void for_each_client(struct fw_device *device,
+ void (*callback)(struct client *client))
+{
+ struct client *c;
+
+ guard(mutex)(&device->client_list_mutex);
+
+ list_for_each_entry(c, &device->client_list, link)
+ callback(c);
+}
+
+static void queue_bus_reset_event(struct client *client)
+{
+ struct bus_reset_event *e;
+ struct client_resource *resource;
+ unsigned long index;
+
+ e = kzalloc_obj(*e);
+ if (e == NULL)
+ return;
+
+ fill_bus_reset_event(&e->reset, 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);
+ }
+}
+
+void fw_device_cdev_update(struct fw_device *device)
+{
+ for_each_client(device, queue_bus_reset_event);
+}
+
+static void wake_up_client(struct client *client)
+{
+ wake_up_interruptible(&client->wait);
+}
+
+void fw_device_cdev_remove(struct fw_device *device)
+{
+ for_each_client(device, wake_up_client);
+}
+
+union ioctl_arg {
+ struct fw_cdev_get_info get_info;
+ struct fw_cdev_send_request send_request;
+ struct fw_cdev_allocate allocate;
+ struct fw_cdev_deallocate deallocate;
+ struct fw_cdev_send_response send_response;
+ struct fw_cdev_initiate_bus_reset initiate_bus_reset;
+ struct fw_cdev_add_descriptor add_descriptor;
+ struct fw_cdev_remove_descriptor remove_descriptor;
+ struct fw_cdev_create_iso_context create_iso_context;
+ struct fw_cdev_queue_iso queue_iso;
+ struct fw_cdev_start_iso start_iso;
+ struct fw_cdev_stop_iso stop_iso;
+ struct fw_cdev_get_cycle_timer get_cycle_timer;
+ struct fw_cdev_allocate_iso_resource allocate_iso_resource;
+ struct fw_cdev_send_stream_packet send_stream_packet;
+ struct fw_cdev_get_cycle_timer2 get_cycle_timer2;
+ struct fw_cdev_send_phy_packet send_phy_packet;
+ struct fw_cdev_receive_phy_packets receive_phy_packets;
+ struct fw_cdev_set_iso_channels set_iso_channels;
+ struct fw_cdev_flush_iso flush_iso;
+};
+
+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;
+ a->card = client->device->card->index;
+
+ 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;
+
+ ret = copy_to_user(u64_to_uptr(a->rom), client->device->config_rom,
+ min(want, have));
+ if (ret != 0)
+ return -EFAULT;
+ }
+ a->rom_length = client->device->config_rom_length * 4;
+ }
+
+ guard(mutex)(&client->device->client_list_mutex);
+
+ 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 (ret == 0 && list_empty(&client->link))
+ list_add_tail(&client->link, &client->device->client_list);
+
+ return ret ? -EFAULT : 0;
+}
+
+static int add_client_resource(struct client *client, struct client_resource *resource,
+ gfp_t gfp_mask)
+{
+ scoped_guard(spinlock_irqsave, &client->lock) {
+ 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);
+ }
+ if (ret < 0)
+ return ret;
+
+ resource->handle = index;
+ client_get(client);
+ }
+
+ return 0;
+}
+
+static int release_client_resource(struct client *client, u32 handle,
+ client_resource_release_fn_t release,
+ struct client_resource **return_resource)
+{
+ unsigned long index = handle;
+ struct client_resource *resource;
+
+ scoped_guard(spinlock_irq, &client->lock) {
+ if (client->in_shutdown)
+ return -EINVAL;
+
+ resource = xa_load(&client->resource_xa, index);
+ if (!resource || resource->release != release)
+ return -EINVAL;
+
+ xa_erase(&client->resource_xa, handle);
+ }
+
+ if (return_resource)
+ *return_resource = resource;
+ else
+ resource->release(client, resource);
+
+ client_put(client);
+
+ return 0;
+}
+
+static void release_transaction(struct client *client,
+ struct client_resource *resource)
+{
+}
+
+static void complete_transaction(struct fw_card *card, int rcode, u32 request_tstamp,
+ u32 response_tstamp, void *payload, size_t length, void *data)
+{
+ struct outbound_transaction_event *e = data;
+ struct client *client = e->client;
+ unsigned long index = e->r.resource.handle;
+
+ scoped_guard(spinlock_irqsave, &client->lock) {
+ xa_erase(&client->resource_xa, index);
+ if (client->in_shutdown)
+ wake_up(&client->tx_flush_wait);
+ }
+
+ switch (e->rsp.without_tstamp.type) {
+ case FW_CDEV_EVENT_RESPONSE:
+ {
+ struct fw_cdev_event_response *rsp = &e->rsp.without_tstamp;
+
+ if (length < rsp->length)
+ rsp->length = length;
+ if (rcode == RCODE_COMPLETE)
+ memcpy(rsp->data, payload, rsp->length);
+
+ rsp->rcode = rcode;
+
+ // In the case that sizeof(*rsp) doesn't align with the position of the
+ // data, and the read is short, preserve an extra copy of the data
+ // to stay compatible with a pre-2.6.27 bug. Since the bug is harmless
+ // for short reads and some apps depended on it, this is both safe
+ // and prudent for compatibility.
+ if (rsp->length <= sizeof(*rsp) - offsetof(typeof(*rsp), data))
+ queue_event(client, &e->event, rsp, sizeof(*rsp), rsp->data, rsp->length);
+ else
+ queue_event(client, &e->event, rsp, sizeof(*rsp) + rsp->length, NULL, 0);
+
+ break;
+ }
+ case FW_CDEV_EVENT_RESPONSE2:
+ {
+ struct fw_cdev_event_response2 *rsp = &e->rsp.with_tstamp;
+
+ if (length < rsp->length)
+ rsp->length = length;
+ if (rcode == RCODE_COMPLETE)
+ memcpy(rsp->data, payload, rsp->length);
+
+ rsp->rcode = rcode;
+ rsp->request_tstamp = request_tstamp;
+ rsp->response_tstamp = response_tstamp;
+
+ queue_event(client, &e->event, rsp, sizeof(*rsp) + rsp->length, NULL, 0);
+
+ break;
+ }
+ default:
+ WARN_ON(1);
+ break;
+ }
+
+ // Drop the xarray's reference.
+ client_put(client);
+}
+
+static int init_request(struct client *client,
+ struct fw_cdev_send_request *request,
+ int destination_id, int speed)
+{
+ struct outbound_transaction_event *e;
+ void *payload;
+ int ret;
+
+ if (request->tcode != TCODE_STREAM_DATA &&
+ (request->length > 4096 || request->length > 512 << speed))
+ return -EIO;
+
+ if (request->tcode == TCODE_WRITE_QUADLET_REQUEST &&
+ request->length < 4)
+ return -EINVAL;
+
+ e = kmalloc(sizeof(*e) + request->length, GFP_KERNEL);
+ if (e == NULL)
+ return -ENOMEM;
+ e->client = client;
+
+ if (client->version < FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP) {
+ struct fw_cdev_event_response *rsp = &e->rsp.without_tstamp;
+
+ rsp->type = FW_CDEV_EVENT_RESPONSE;
+ rsp->length = request->length;
+ rsp->closure = request->closure;
+ payload = rsp->data;
+ } else {
+ struct fw_cdev_event_response2 *rsp = &e->rsp.with_tstamp;
+
+ rsp->type = FW_CDEV_EVENT_RESPONSE2;
+ rsp->length = request->length;
+ rsp->closure = request->closure;
+ payload = rsp->data;
+ }
+
+ if (request->data && copy_from_user(payload, u64_to_uptr(request->data), request->length)) {
+ ret = -EFAULT;
+ goto failed;
+ }
+
+ e->r.resource.release = release_transaction;
+ ret = add_client_resource(client, &e->r.resource, GFP_KERNEL);
+ if (ret < 0)
+ goto failed;
+
+ fw_send_request_with_tstamp(client->device->card, &e->r.transaction, request->tcode,
+ destination_id, request->generation, speed, request->offset,
+ payload, request->length, complete_transaction, e);
+ return 0;
+
+ failed:
+ kfree(e);
+
+ return ret;
+}
+
+static int ioctl_send_request(struct client *client, union ioctl_arg *arg)
+{
+ switch (arg->send_request.tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ case TCODE_WRITE_BLOCK_REQUEST:
+ case TCODE_READ_QUADLET_REQUEST:
+ case TCODE_READ_BLOCK_REQUEST:
+ case TCODE_LOCK_MASK_SWAP:
+ case TCODE_LOCK_COMPARE_SWAP:
+ case TCODE_LOCK_FETCH_ADD:
+ case TCODE_LOCK_LITTLE_ADD:
+ case TCODE_LOCK_BOUNDED_ADD:
+ case TCODE_LOCK_WRAP_ADD:
+ case TCODE_LOCK_VENDOR_DEPENDENT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return init_request(client, &arg->send_request, client->device->node_id,
+ client->device->max_speed);
+}
+
+static void release_request(struct client *client,
+ struct client_resource *resource)
+{
+ struct inbound_transaction_resource *r = to_inbound_transaction_resource(resource);
+
+ if (r->is_fcp)
+ fw_request_put(r->request);
+ else
+ fw_send_response(r->card, r->request, RCODE_CONFLICT_ERROR);
+
+ fw_card_put(r->card);
+ kfree(r);
+}
+
+static void handle_request(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)
+{
+ struct address_handler_resource *handler = callback_data;
+ bool is_fcp = is_in_fcp_region(offset, length);
+ struct inbound_transaction_resource *r;
+ struct inbound_transaction_event *e;
+ size_t event_size0;
+ int ret;
+
+ /* card may be different from handler->client->device->card */
+ fw_card_get(card);
+
+ // Extend the lifetime of data for request so that its payload is safely accessible in
+ // the process context for the client.
+ if (is_fcp)
+ fw_request_get(request);
+
+ r = kmalloc_obj(*r, GFP_ATOMIC);
+ e = kmalloc_obj(*e, GFP_ATOMIC);
+ if (r == NULL || e == NULL)
+ goto failed;
+
+ r->card = card;
+ r->request = request;
+ r->is_fcp = is_fcp;
+ r->data = payload;
+ r->length = length;
+
+ r->resource.release = release_request;
+ ret = add_client_resource(handler->client, &r->resource, GFP_ATOMIC);
+ if (ret < 0)
+ goto failed;
+
+ if (handler->client->version < FW_CDEV_VERSION_EVENT_REQUEST2) {
+ struct fw_cdev_event_request *req = &e->req.request;
+
+ if (tcode & 0x10)
+ tcode = TCODE_LOCK_REQUEST;
+
+ req->type = FW_CDEV_EVENT_REQUEST;
+ req->tcode = tcode;
+ req->offset = offset;
+ req->length = length;
+ req->handle = r->resource.handle;
+ req->closure = handler->closure;
+ event_size0 = sizeof(*req);
+ } else if (handler->client->version < FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP) {
+ struct fw_cdev_event_request2 *req = &e->req.request2;
+
+ req->type = FW_CDEV_EVENT_REQUEST2;
+ req->tcode = tcode;
+ req->offset = offset;
+ req->source_node_id = source;
+ req->destination_node_id = destination;
+ req->card = card->index;
+ req->generation = generation;
+ req->length = length;
+ req->handle = r->resource.handle;
+ req->closure = handler->closure;
+ event_size0 = sizeof(*req);
+ } else {
+ struct fw_cdev_event_request3 *req = &e->req.with_tstamp;
+
+ req->type = FW_CDEV_EVENT_REQUEST3;
+ req->tcode = tcode;
+ req->offset = offset;
+ req->source_node_id = source;
+ req->destination_node_id = destination;
+ req->card = card->index;
+ req->generation = generation;
+ req->length = length;
+ req->handle = r->resource.handle;
+ req->closure = handler->closure;
+ req->tstamp = fw_request_get_timestamp(request);
+ event_size0 = sizeof(*req);
+ }
+
+ queue_event(handler->client, &e->event,
+ &e->req, event_size0, r->data, length);
+ return;
+
+ failed:
+ kfree(r);
+ kfree(e);
+
+ if (!is_fcp)
+ fw_send_response(card, request, RCODE_CONFLICT_ERROR);
+ else
+ fw_request_put(request);
+
+ fw_card_put(card);
+}
+
+static void release_address_handler(struct client *client,
+ struct client_resource *resource)
+{
+ struct address_handler_resource *r = to_address_handler_resource(resource);
+
+ fw_core_remove_address_handler(&r->handler);
+ kfree(r);
+}
+
+static int ioctl_allocate(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_allocate *a = &arg->allocate;
+ struct address_handler_resource *r;
+ struct fw_address_region region;
+ int ret;
+
+ r = kmalloc_obj(*r);
+ if (r == NULL)
+ return -ENOMEM;
+
+ region.start = a->offset;
+ if (client->version < FW_CDEV_VERSION_ALLOCATE_REGION_END)
+ region.end = a->offset + a->length;
+ else
+ region.end = a->region_end;
+
+ r->handler.length = a->length;
+ r->handler.address_callback = handle_request;
+ r->handler.callback_data = r;
+ r->closure = a->closure;
+ r->client = client;
+
+ ret = fw_core_add_address_handler(&r->handler, &region);
+ if (ret < 0) {
+ kfree(r);
+ return ret;
+ }
+ a->offset = r->handler.offset;
+
+ r->resource.release = release_address_handler;
+ ret = add_client_resource(client, &r->resource, GFP_KERNEL);
+ if (ret < 0) {
+ release_address_handler(client, &r->resource);
+ return ret;
+ }
+ a->handle = r->resource.handle;
+
+ return 0;
+}
+
+static int ioctl_deallocate(struct client *client, union ioctl_arg *arg)
+{
+ return release_client_resource(client, arg->deallocate.handle,
+ release_address_handler, NULL);
+}
+
+static int ioctl_send_response(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_send_response *a = &arg->send_response;
+ struct client_resource *resource;
+ struct inbound_transaction_resource *r;
+ int ret = 0;
+
+ if (release_client_resource(client, a->handle,
+ release_request, &resource) < 0)
+ return -EINVAL;
+
+ r = to_inbound_transaction_resource(resource);
+ if (r->is_fcp) {
+ fw_request_put(r->request);
+ goto out;
+ }
+
+ if (a->length != fw_get_response_length(r->request)) {
+ ret = -EINVAL;
+ fw_request_put(r->request);
+ goto out;
+ }
+ if (copy_from_user(r->data, u64_to_uptr(a->data), a->length)) {
+ ret = -EFAULT;
+ fw_request_put(r->request);
+ goto out;
+ }
+ fw_send_response(r->card, r->request, a->rcode);
+ out:
+ fw_card_put(r->card);
+ kfree(r);
+
+ return ret;
+}
+
+static int ioctl_initiate_bus_reset(struct client *client, union ioctl_arg *arg)
+{
+ fw_schedule_bus_reset(client->device->card, true,
+ arg->initiate_bus_reset.type == FW_CDEV_SHORT_RESET);
+ return 0;
+}
+
+static void release_descriptor(struct client *client,
+ struct client_resource *resource)
+{
+ struct descriptor_resource *r = to_descriptor_resource(resource);
+
+ fw_core_remove_descriptor(&r->descriptor);
+ kfree(r);
+}
+
+static int ioctl_add_descriptor(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_add_descriptor *a = &arg->add_descriptor;
+ struct descriptor_resource *r;
+ int ret;
+
+ /* Access policy: Allow this ioctl only on local nodes' device files. */
+ if (!client->device->is_local)
+ return -ENOSYS;
+
+ if (a->length > 256)
+ return -EINVAL;
+
+ r = kmalloc_flex(*r, data, a->length);
+ if (r == NULL)
+ return -ENOMEM;
+
+ if (copy_from_user(r->data, u64_to_uptr(a->data),
+ flex_array_size(r, data, a->length))) {
+ ret = -EFAULT;
+ goto failed;
+ }
+
+ r->descriptor.length = a->length;
+ r->descriptor.immediate = a->immediate;
+ r->descriptor.key = a->key;
+ r->descriptor.data = r->data;
+
+ ret = fw_core_add_descriptor(&r->descriptor);
+ if (ret < 0)
+ goto failed;
+
+ r->resource.release = release_descriptor;
+ ret = add_client_resource(client, &r->resource, GFP_KERNEL);
+ if (ret < 0) {
+ fw_core_remove_descriptor(&r->descriptor);
+ goto failed;
+ }
+ a->handle = r->resource.handle;
+
+ return 0;
+ failed:
+ kfree(r);
+
+ return ret;
+}
+
+static int ioctl_remove_descriptor(struct client *client, union ioctl_arg *arg)
+{
+ return release_client_resource(client, arg->remove_descriptor.handle,
+ release_descriptor, NULL);
+}
+
+static void iso_callback(struct fw_iso_context *context, u32 cycle,
+ size_t header_length, void *header, void *data)
+{
+ struct client *client = data;
+ struct iso_interrupt_event *e;
+
+ e = kmalloc(sizeof(*e) + header_length, GFP_KERNEL);
+ if (e == NULL)
+ return;
+
+ e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT;
+ e->interrupt.closure = client->iso_closure;
+ e->interrupt.cycle = cycle;
+ e->interrupt.header_length = header_length;
+ memcpy(e->interrupt.header, header, header_length);
+ queue_event(client, &e->event, &e->interrupt,
+ sizeof(e->interrupt) + header_length, NULL, 0);
+}
+
+static void iso_mc_callback(struct fw_iso_context *context,
+ dma_addr_t completed, void *data)
+{
+ struct client *client = data;
+ struct iso_interrupt_mc_event *e;
+
+ e = kmalloc_obj(*e);
+ if (e == NULL)
+ return;
+
+ e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL;
+ e->interrupt.closure = client->iso_closure;
+ e->interrupt.completed = fw_iso_buffer_lookup(&client->buffer,
+ completed);
+ queue_event(client, &e->event, &e->interrupt,
+ sizeof(e->interrupt), NULL, 0);
+}
+
+static enum dma_data_direction iso_dma_direction(struct fw_iso_context *context)
+{
+ if (context->type == FW_ISO_CONTEXT_TRANSMIT)
+ return DMA_TO_DEVICE;
+ else
+ return DMA_FROM_DEVICE;
+}
+
+static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_create_iso_context *a = &arg->create_iso_context;
+ struct fw_iso_context *context;
+ int ret;
+
+ BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT ||
+ FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE ||
+ FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL !=
+ FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL);
+
+ switch (a->type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ if (a->speed > SCODE_3200 || a->channel > 63)
+ return -EINVAL;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE:
+ if (a->header_size < 4 || (a->header_size & 3) ||
+ a->channel > 63)
+ return -EINVAL;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (a->type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL)
+ context = fw_iso_mc_context_create(client->device->card, iso_mc_callback, client);
+ else
+ context = fw_iso_context_create(client->device->card, a->type, a->channel, a->speed,
+ a->header_size, iso_callback, client);
+ if (IS_ERR(context))
+ return PTR_ERR(context);
+ if (client->version < FW_CDEV_VERSION_AUTO_FLUSH_ISO_OVERFLOW)
+ context->flags |= FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS;
+
+ // We only support one context at this time.
+ scoped_guard(mutex, &client->iso_context_mutex) {
+ if (client->iso_context != NULL) {
+ fw_iso_context_destroy(context);
+
+ return -EBUSY;
+ }
+ // The DMA mapping operation is available if the buffer is already allocated by
+ // mmap(2) system call. If not, it is delegated to the system call.
+ if (client->buffer.pages && !client->buffer.dma_addrs) {
+ ret = fw_iso_buffer_map_dma(&client->buffer, client->device->card,
+ iso_dma_direction(context));
+ if (ret < 0) {
+ fw_iso_context_destroy(context);
+
+ return ret;
+ }
+ }
+ client->iso_closure = a->closure;
+ client->iso_context = context;
+ }
+
+ a->handle = 0;
+
+ return 0;
+}
+
+static int ioctl_set_iso_channels(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_set_iso_channels *a = &arg->set_iso_channels;
+ struct fw_iso_context *ctx = client->iso_context;
+
+ if (ctx == NULL || a->handle != 0)
+ return -EINVAL;
+
+ return fw_iso_context_set_channels(ctx, &a->channels);
+}
+
+/* Macros for decoding the iso packet control header. */
+#define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff)
+#define GET_INTERRUPT(v) (((v) >> 16) & 0x01)
+#define GET_SKIP(v) (((v) >> 17) & 0x01)
+#define GET_TAG(v) (((v) >> 18) & 0x03)
+#define GET_SY(v) (((v) >> 20) & 0x0f)
+#define GET_HEADER_LENGTH(v) (((v) >> 24) & 0xff)
+
+static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_queue_iso *a = &arg->queue_iso;
+ struct fw_cdev_iso_packet __user *p, *end, *next;
+ struct fw_iso_context *ctx = client->iso_context;
+ unsigned long payload, buffer_end, transmit_header_bytes = 0;
+ u32 control;
+ int count;
+ DEFINE_RAW_FLEX(struct fw_iso_packet, u, header, 64);
+
+ if (ctx == NULL || a->handle != 0)
+ return -EINVAL;
+
+ /*
+ * If the user passes a non-NULL data pointer, has mmap()'ed
+ * the iso buffer, and the pointer points inside the buffer,
+ * we setup the payload pointers accordingly. Otherwise we
+ * set them both to 0, which will still let packets with
+ * payload_length == 0 through. In other words, if no packets
+ * use the indirect payload, the iso buffer need not be mapped
+ * and the a->data pointer is ignored.
+ */
+ payload = (unsigned long)a->data - client->vm_start;
+ buffer_end = client->buffer.page_count << PAGE_SHIFT;
+ if (a->data == 0 || client->buffer.pages == NULL ||
+ payload >= buffer_end) {
+ payload = 0;
+ buffer_end = 0;
+ }
+
+ if (ctx->type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL && payload & 3)
+ return -EINVAL;
+
+ p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets);
+
+ end = (void __user *)p + a->size;
+ count = 0;
+ while (p < end) {
+ if (get_user(control, &p->control))
+ return -EFAULT;
+ u->payload_length = GET_PAYLOAD_LENGTH(control);
+ u->interrupt = GET_INTERRUPT(control);
+ u->skip = GET_SKIP(control);
+ u->tag = GET_TAG(control);
+ u->sy = GET_SY(control);
+ u->header_length = GET_HEADER_LENGTH(control);
+
+ switch (ctx->type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ if (u->header_length & 3)
+ return -EINVAL;
+ transmit_header_bytes = u->header_length;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE:
+ if (u->header_length == 0 ||
+ u->header_length % ctx->header_size != 0)
+ return -EINVAL;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ if (u->payload_length == 0 ||
+ u->payload_length & 3)
+ return -EINVAL;
+ break;
+ }
+
+ next = (struct fw_cdev_iso_packet __user *)
+ &p->header[transmit_header_bytes / 4];
+ if (next > end)
+ return -EINVAL;
+ if (copy_from_user
+ (u->header, p->header, transmit_header_bytes))
+ return -EFAULT;
+ if (u->skip && ctx->type == FW_ISO_CONTEXT_TRANSMIT &&
+ u->header_length + u->payload_length > 0)
+ return -EINVAL;
+ if (payload + u->payload_length > buffer_end)
+ return -EINVAL;
+
+ if (fw_iso_context_queue(ctx, u, &client->buffer, payload))
+ break;
+
+ p = next;
+ payload += u->payload_length;
+ count++;
+ }
+ fw_iso_context_queue_flush(ctx);
+
+ a->size -= uptr_to_u64(p) - a->packets;
+ a->packets = uptr_to_u64(p);
+ a->data = client->vm_start + payload;
+
+ return count;
+}
+
+static int ioctl_start_iso(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_start_iso *a = &arg->start_iso;
+
+ BUILD_BUG_ON(
+ FW_CDEV_ISO_CONTEXT_MATCH_TAG0 != FW_ISO_CONTEXT_MATCH_TAG0 ||
+ FW_CDEV_ISO_CONTEXT_MATCH_TAG1 != FW_ISO_CONTEXT_MATCH_TAG1 ||
+ FW_CDEV_ISO_CONTEXT_MATCH_TAG2 != FW_ISO_CONTEXT_MATCH_TAG2 ||
+ FW_CDEV_ISO_CONTEXT_MATCH_TAG3 != FW_ISO_CONTEXT_MATCH_TAG3 ||
+ FW_CDEV_ISO_CONTEXT_MATCH_ALL_TAGS != FW_ISO_CONTEXT_MATCH_ALL_TAGS);
+
+ if (client->iso_context == NULL || a->handle != 0)
+ return -EINVAL;
+
+ if (client->iso_context->type == FW_ISO_CONTEXT_RECEIVE &&
+ (a->tags == 0 || a->tags > 15 || a->sync > 15))
+ return -EINVAL;
+
+ return fw_iso_context_start(client->iso_context,
+ a->cycle, a->sync, a->tags);
+}
+
+static int ioctl_stop_iso(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_stop_iso *a = &arg->stop_iso;
+
+ if (client->iso_context == NULL || a->handle != 0)
+ return -EINVAL;
+
+ return fw_iso_context_stop(client->iso_context);
+}
+
+static int ioctl_flush_iso(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_flush_iso *a = &arg->flush_iso;
+
+ if (client->iso_context == NULL || a->handle != 0)
+ return -EINVAL;
+
+ return fw_iso_context_flush_completions(client->iso_context);
+}
+
+static int ioctl_get_cycle_timer2(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_get_cycle_timer2 *a = &arg->get_cycle_timer2;
+ struct fw_card *card = client->device->card;
+ struct timespec64 ts = {0, 0};
+ u32 cycle_time = 0;
+ int ret;
+
+ guard(irq)();
+
+ ret = fw_card_read_cycle_time(card, &cycle_time);
+ if (ret < 0)
+ return ret;
+
+ switch (a->clk_id) {
+ case CLOCK_REALTIME: ktime_get_real_ts64(&ts); break;
+ case CLOCK_MONOTONIC: ktime_get_ts64(&ts); break;
+ case CLOCK_MONOTONIC_RAW: ktime_get_raw_ts64(&ts); break;
+ default:
+ return -EINVAL;
+ }
+
+ a->tv_sec = ts.tv_sec;
+ a->tv_nsec = ts.tv_nsec;
+ a->cycle_timer = cycle_time;
+
+ return 0;
+}
+
+static int ioctl_get_cycle_timer(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_get_cycle_timer *a = &arg->get_cycle_timer;
+ struct fw_cdev_get_cycle_timer2 ct2;
+
+ ct2.clk_id = CLOCK_REALTIME;
+ ioctl_get_cycle_timer2(client, (union ioctl_arg *)&ct2);
+
+ a->local_time = ct2.tv_sec * USEC_PER_SEC + ct2.tv_nsec / NSEC_PER_USEC;
+ a->cycle_timer = ct2.cycle_timer;
+
+ return 0;
+}
+
+static int fill_iso_resource_params(struct iso_resource_params *params,
+ struct fw_cdev_allocate_iso_resource *request)
+{
+ if ((request->channels == 0 && request->bandwidth == 0) ||
+ request->bandwidth > BANDWIDTH_AVAILABLE_INITIAL)
+ return -EINVAL;
+
+ params->channels_mask = request->channels;
+ params->bandwidth = request->bandwidth;
+
+ return 0;
+}
+
+static void iso_resource_auto_work(struct work_struct *work)
+{
+ struct iso_resource_event *e;
+ 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;
+ 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;
+ }
+
+ 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;
+ }
+ break;
+ case ISO_RES_AUTO_REALLOC:
+ // We could be called twice within the same generation.
+ if (resource_generation == current_generation)
+ goto out;
+ break;
+ case ISO_RES_AUTO_DEALLOC:
+ default:
+ break;
+ }
+
+ bandwidth = r->params.bandwidth;
+
+ fw_iso_resource_manage(client->device->card, current_generation, r->params.channels_mask,
+ &channel, &bandwidth, todo != ISO_RES_AUTO_DEALLOC);
+
+ if (todo == ISO_RES_AUTO_DEALLOC) {
+ free = true;
+ e = r->e_dealloc;
+ r->e_dealloc = NULL;
+ } 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;
+
+ 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) {
+ if (!client->in_shutdown && xa_erase(&client->resource_xa, index)) {
+ client_put(client);
+ free = true;
+ }
+ }
+ }
+
+ if (todo == ISO_RES_AUTO_REALLOC) {
+ if (success)
+ goto out;
+
+ // Notify the userspace client of the failure through a deallocation event.
+ e = r->e_dealloc;
+ r->e_dealloc = NULL;
+ } else {
+ // Transit from allocation to reallocation, except if the client requested
+ // deallocation in the meantime.
+ scoped_guard(spinlock_irq, &client->lock)
+ r->todo = ISO_RES_AUTO_REALLOC;
+
+ if (channel >= 0)
+ r->params.channels_mask = BIT_ULL(channel);
+
+ e = r->e_alloc;
+ r->e_alloc = NULL;
+ }
+ }
+
+ e->iso_resource.handle = r->resource.handle;
+ e->iso_resource.channel = channel;
+ e->iso_resource.bandwidth = bandwidth;
+
+ queue_event(client, &e->event,
+ &e->iso_resource, sizeof(e->iso_resource), NULL, 0);
+
+ if (free) {
+ cancel_delayed_work(&r->work);
+ kfree(r->e_alloc);
+ kfree(r->e_dealloc);
+ kfree(r);
+ }
+ 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;
+ schedule_iso_resource_auto(r, 0);
+}
+
+static int ioctl_allocate_iso_resource(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_allocate_iso_resource *request = &arg->allocate_iso_resource;
+ struct iso_resource_event *e1 __free(kfree) = kmalloc_obj(*e1);
+ struct iso_resource_event *e2 __free(kfree) = kmalloc_obj(*e2);
+ struct iso_resource_auto *r __free(kfree) = kmalloc_obj(*r);
+ int err;
+
+ if (!r || !e1 || !e2)
+ return -ENOMEM;
+
+ err = fill_iso_resource_params(&r->params, request);
+ if (err < 0)
+ return err;
+
+ INIT_DELAYED_WORK(&r->work, iso_resource_auto_work);
+ r->client = client;
+ r->todo = ISO_RES_AUTO_ALLOC;
+ r->e_alloc = e1;
+ r->e_dealloc = e2;
+
+ e1->iso_resource.closure = request->closure;
+ e1->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED;
+ 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);
+ if (err < 0)
+ return err;
+ request->handle = r->resource.handle;
+
+ retain_and_null_ptr(e1);
+ retain_and_null_ptr(e2);
+ schedule_iso_resource_auto(no_free_ptr(r), 0);
+
+ return 0;
+}
+
+static int ioctl_deallocate_iso_resource(struct client *client,
+ union ioctl_arg *arg)
+{
+ return release_client_resource(client,
+ arg->deallocate.handle, release_iso_resource_auto, NULL);
+}
+
+#define UNAVAILABLE_HANDLE -1
+
+static void iso_resource_once_work(struct work_struct *work)
+{
+ struct iso_resource_once *r = from_work(r, work, work);
+ struct client *client = r->client;
+ struct iso_resource_event *e = r->event;
+ int generation, channel, bandwidth;
+
+ scoped_guard(spinlock_irq, &client->lock)
+ generation = client->device->generation;
+
+ bandwidth = r->params.bandwidth;
+
+ fw_iso_resource_manage(client->device->card, generation, r->params.channels_mask, &channel,
+ &bandwidth, r->todo == ISO_RES_ONCE_ALLOC);
+
+ e->iso_resource.handle = UNAVAILABLE_HANDLE;
+ e->iso_resource.channel = channel;
+ e->iso_resource.bandwidth = bandwidth;
+
+ queue_event(client, &e->event, &e->iso_resource, sizeof(e->iso_resource), NULL, 0);
+
+ cancel_work(&r->work);
+ kfree(r);
+
+ client_put(client);
+}
+
+static int init_iso_resource_once(struct client *client,
+ struct fw_cdev_allocate_iso_resource *request, int todo)
+{
+ struct iso_resource_event *e __free(kfree) = kmalloc_obj(*e);
+ struct iso_resource_once *r __free(kfree) = kmalloc_obj(*r);
+ int err;
+
+ if (!r || !e)
+ return -ENOMEM;
+
+ err = fill_iso_resource_params(&r->params, request);
+ if (err < 0)
+ return err;
+
+ INIT_WORK(&r->work, iso_resource_once_work);
+ r->client = client;
+ r->todo = todo;
+
+ if (todo == ISO_RES_ONCE_ALLOC)
+ e->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED;
+ else
+ e->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED;
+ e->iso_resource.closure = request->closure;
+ r->event = no_free_ptr(e);
+
+ // Keep the client until work item finishing.
+ client_get(r->client);
+
+ queue_work(fw_workqueue, &no_free_ptr(r)->work);
+
+ request->handle = UNAVAILABLE_HANDLE;
+
+ return 0;
+}
+
+static int ioctl_allocate_iso_resource_once(struct client *client,
+ union ioctl_arg *arg)
+{
+ return init_iso_resource_once(client, &arg->allocate_iso_resource, ISO_RES_ONCE_ALLOC);
+}
+
+static int ioctl_deallocate_iso_resource_once(struct client *client,
+ union ioctl_arg *arg)
+{
+ return init_iso_resource_once(client, &arg->allocate_iso_resource, ISO_RES_ONCE_DEALLOC);
+}
+
+/*
+ * Returns a speed code: Maximum speed to or from this device,
+ * limited by the device's link speed, the local node's link speed,
+ * and all PHY port speeds between the two links.
+ */
+static int ioctl_get_speed(struct client *client, union ioctl_arg *arg)
+{
+ return client->device->max_speed;
+}
+
+static int ioctl_send_broadcast_request(struct client *client,
+ union ioctl_arg *arg)
+{
+ struct fw_cdev_send_request *a = &arg->send_request;
+
+ switch (a->tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ case TCODE_WRITE_BLOCK_REQUEST:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Security policy: Only allow accesses to Units Space. */
+ if (a->offset < CSR_REGISTER_BASE + CSR_CONFIG_ROM_END)
+ return -EACCES;
+
+ return init_request(client, a, LOCAL_BUS | 0x3f, SCODE_100);
+}
+
+static int ioctl_send_stream_packet(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_send_stream_packet *a = &arg->send_stream_packet;
+ struct fw_cdev_send_request request;
+ int dest;
+
+ if (a->speed > client->device->card->link_speed ||
+ a->length > 1024 << a->speed)
+ return -EIO;
+
+ if (a->tag > 3 || a->channel > 63 || a->sy > 15)
+ return -EINVAL;
+
+ dest = fw_stream_packet_destination_id(a->tag, a->channel, a->sy);
+ request.tcode = TCODE_STREAM_DATA;
+ request.length = a->length;
+ request.closure = a->closure;
+ request.data = a->data;
+ request.generation = a->generation;
+
+ return init_request(client, &request, dest, a->speed);
+}
+
+static void outbound_phy_packet_callback(struct fw_packet *packet,
+ struct fw_card *card, int status)
+{
+ struct outbound_phy_packet_event *e =
+ container_of(packet, struct outbound_phy_packet_event, p);
+ struct client *e_client = e->client;
+ u32 rcode;
+
+ trace_async_phy_outbound_complete((uintptr_t)packet, card->index, status, packet->generation,
+ packet->timestamp);
+
+ switch (status) {
+ // expected:
+ case ACK_COMPLETE:
+ rcode = RCODE_COMPLETE;
+ break;
+ // should never happen with PHY packets:
+ case ACK_PENDING:
+ rcode = RCODE_COMPLETE;
+ break;
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ rcode = RCODE_BUSY;
+ break;
+ case ACK_DATA_ERROR:
+ rcode = RCODE_DATA_ERROR;
+ break;
+ case ACK_TYPE_ERROR:
+ rcode = RCODE_TYPE_ERROR;
+ break;
+ // stale generation; cancelled; on certain controllers: no ack
+ default:
+ rcode = status;
+ break;
+ }
+
+ switch (e->phy_packet.without_tstamp.type) {
+ case FW_CDEV_EVENT_PHY_PACKET_SENT:
+ {
+ struct fw_cdev_event_phy_packet *pp = &e->phy_packet.without_tstamp;
+
+ pp->rcode = rcode;
+ pp->data[0] = packet->timestamp;
+ queue_event(e->client, &e->event, &e->phy_packet, sizeof(*pp) + pp->length,
+ NULL, 0);
+ break;
+ }
+ case FW_CDEV_EVENT_PHY_PACKET_SENT2:
+ {
+ struct fw_cdev_event_phy_packet2 *pp = &e->phy_packet.with_tstamp;
+
+ pp->rcode = rcode;
+ pp->tstamp = packet->timestamp;
+ queue_event(e->client, &e->event, &e->phy_packet, sizeof(*pp) + pp->length,
+ NULL, 0);
+ break;
+ }
+ default:
+ WARN_ON(1);
+ break;
+ }
+
+ client_put(e_client);
+}
+
+static int ioctl_send_phy_packet(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_send_phy_packet *a = &arg->send_phy_packet;
+ struct fw_card *card = client->device->card;
+ struct outbound_phy_packet_event *e;
+
+ /* Access policy: Allow this ioctl only on local nodes' device files. */
+ if (!client->device->is_local)
+ return -ENOSYS;
+
+ e = kzalloc(sizeof(*e) + sizeof(a->data), GFP_KERNEL);
+ if (e == NULL)
+ return -ENOMEM;
+
+ client_get(client);
+ e->client = client;
+ e->p.speed = SCODE_100;
+ e->p.generation = a->generation;
+ async_header_set_tcode(e->p.header, TCODE_LINK_INTERNAL);
+ e->p.header[1] = a->data[0];
+ e->p.header[2] = a->data[1];
+ e->p.header_length = 12;
+ e->p.callback = outbound_phy_packet_callback;
+
+ if (client->version < FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP) {
+ struct fw_cdev_event_phy_packet *pp = &e->phy_packet.without_tstamp;
+
+ pp->closure = a->closure;
+ pp->type = FW_CDEV_EVENT_PHY_PACKET_SENT;
+ if (is_ping_packet(a->data))
+ pp->length = 4;
+ } else {
+ struct fw_cdev_event_phy_packet2 *pp = &e->phy_packet.with_tstamp;
+
+ pp->closure = a->closure;
+ pp->type = FW_CDEV_EVENT_PHY_PACKET_SENT2;
+ // Keep the data field so that application can match the response event to the
+ // request.
+ pp->length = sizeof(a->data);
+ memcpy(pp->data, a->data, sizeof(a->data));
+ }
+
+ 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);
+
+ return 0;
+}
+
+static int ioctl_receive_phy_packets(struct client *client, union ioctl_arg *arg)
+{
+ struct fw_cdev_receive_phy_packets *a = &arg->receive_phy_packets;
+
+ /* Access policy: Allow this ioctl only on local nodes' device files. */
+ 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)
+ list_move_tail(&client->phy_receiver_link, &phy_receiver_list);
+
+ client->phy_receiver_closure = a->closure;
+
+ return 0;
+}
+
+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);
+
+ 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)
+ break;
+
+ if (client->version < FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP) {
+ struct fw_cdev_event_phy_packet *pp = &e->phy_packet.without_tstamp;
+
+ pp->closure = client->phy_receiver_closure;
+ pp->type = FW_CDEV_EVENT_PHY_PACKET_RECEIVED;
+ pp->rcode = RCODE_COMPLETE;
+ pp->length = 8;
+ pp->data[0] = p->header[1];
+ pp->data[1] = p->header[2];
+ queue_event(client, &e->event, &e->phy_packet, sizeof(*pp) + 8, NULL, 0);
+ } else {
+ struct fw_cdev_event_phy_packet2 *pp = &e->phy_packet.with_tstamp;
+
+ pp = &e->phy_packet.with_tstamp;
+ pp->closure = client->phy_receiver_closure;
+ pp->type = FW_CDEV_EVENT_PHY_PACKET_RECEIVED2;
+ pp->rcode = RCODE_COMPLETE;
+ pp->length = 8;
+ pp->tstamp = p->timestamp;
+ pp->data[0] = p->header[1];
+ pp->data[1] = p->header[2];
+ queue_event(client, &e->event, &e->phy_packet, sizeof(*pp) + 8, NULL, 0);
+ }
+ }
+}
+
+static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
+ [0x00] = ioctl_get_info,
+ [0x01] = ioctl_send_request,
+ [0x02] = ioctl_allocate,
+ [0x03] = ioctl_deallocate,
+ [0x04] = ioctl_send_response,
+ [0x05] = ioctl_initiate_bus_reset,
+ [0x06] = ioctl_add_descriptor,
+ [0x07] = ioctl_remove_descriptor,
+ [0x08] = ioctl_create_iso_context,
+ [0x09] = ioctl_queue_iso,
+ [0x0a] = ioctl_start_iso,
+ [0x0b] = ioctl_stop_iso,
+ [0x0c] = ioctl_get_cycle_timer,
+ [0x0d] = ioctl_allocate_iso_resource,
+ [0x0e] = ioctl_deallocate_iso_resource,
+ [0x0f] = ioctl_allocate_iso_resource_once,
+ [0x10] = ioctl_deallocate_iso_resource_once,
+ [0x11] = ioctl_get_speed,
+ [0x12] = ioctl_send_broadcast_request,
+ [0x13] = ioctl_send_stream_packet,
+ [0x14] = ioctl_get_cycle_timer2,
+ [0x15] = ioctl_send_phy_packet,
+ [0x16] = ioctl_receive_phy_packets,
+ [0x17] = ioctl_set_iso_channels,
+ [0x18] = ioctl_flush_iso,
+};
+
+static int dispatch_ioctl(struct client *client,
+ unsigned int cmd, void __user *arg)
+{
+ union ioctl_arg buffer;
+ int ret;
+
+ if (fw_device_is_shutdown(client->device))
+ return -ENODEV;
+
+ if (_IOC_TYPE(cmd) != '#' ||
+ _IOC_NR(cmd) >= ARRAY_SIZE(ioctl_handlers) ||
+ _IOC_SIZE(cmd) > sizeof(buffer))
+ return -ENOTTY;
+
+ memset(&buffer, 0, sizeof(buffer));
+
+ if (_IOC_DIR(cmd) & _IOC_WRITE)
+ if (copy_from_user(&buffer, arg, _IOC_SIZE(cmd)))
+ return -EFAULT;
+
+ ret = ioctl_handlers[_IOC_NR(cmd)](client, &buffer);
+ if (ret < 0)
+ return ret;
+
+ if (_IOC_DIR(cmd) & _IOC_READ)
+ if (copy_to_user(arg, &buffer, _IOC_SIZE(cmd)))
+ return -EFAULT;
+
+ return ret;
+}
+
+static long fw_device_op_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return dispatch_ioctl(file->private_data, cmd, (void __user *)arg);
+}
+
+static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct client *client = file->private_data;
+ unsigned long size;
+ int page_count, ret;
+
+ if (fw_device_is_shutdown(client->device))
+ return -ENODEV;
+
+ /* FIXME: We could support multiple buffers, but we don't. */
+ if (client->buffer.pages != NULL)
+ return -EBUSY;
+
+ if (!(vma->vm_flags & VM_SHARED))
+ return -EINVAL;
+
+ if (vma->vm_start & ~PAGE_MASK)
+ return -EINVAL;
+
+ client->vm_start = vma->vm_start;
+ size = vma->vm_end - vma->vm_start;
+ page_count = size >> PAGE_SHIFT;
+ if (size & ~PAGE_MASK)
+ return -EINVAL;
+
+ ret = fw_iso_buffer_alloc(&client->buffer, page_count);
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(mutex, &client->iso_context_mutex) {
+ // The direction of DMA can be determined if the isochronous context is already
+ // allocated. If not, the DMA mapping operation is postponed after the allocation.
+ if (client->iso_context) {
+ ret = fw_iso_buffer_map_dma(&client->buffer, client->device->card,
+ iso_dma_direction(client->iso_context));
+ if (ret < 0)
+ goto fail;
+ }
+ }
+
+ ret = vm_map_pages_zero(vma, client->buffer.pages,
+ client->buffer.page_count);
+ if (ret < 0)
+ goto fail;
+
+ return 0;
+ fail:
+ fw_iso_buffer_destroy(&client->buffer, client->device->card);
+ return ret;
+}
+
+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;
+ }
+
+ return false;
+}
+
+static int fw_device_op_release(struct inode *inode, struct file *file)
+{
+ struct client *client = file->private_data;
+ struct event *event, *next_event;
+ 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)
+ list_del(&client->phy_receiver_link);
+
+ scoped_guard(mutex, &client->device->client_list_mutex)
+ list_del(&client->link);
+
+ if (client->iso_context)
+ fw_iso_context_destroy(client->iso_context);
+ mutex_destroy(&client->iso_context_mutex);
+
+ if (client->buffer.pages)
+ fw_iso_buffer_destroy(&client->buffer, client->device->card);
+
+ // Freeze client->resource_xa and client->event_list.
+ scoped_guard(spinlock_irq, &client->lock)
+ client->in_shutdown = true;
+
+ wait_event(client->tx_flush_wait, !has_outbound_transactions(client));
+
+ xa_for_each(&client->resource_xa, index, resource) {
+ resource->release(client, resource);
+ client_put(client);
+ }
+ xa_destroy(&client->resource_xa);
+
+ list_for_each_entry_safe(event, next_event, &client->event_list, link)
+ kfree(event);
+
+ client_put(client);
+
+ return 0;
+}
+
+static __poll_t fw_device_op_poll(struct file *file, poll_table * pt)
+{
+ struct client *client = file->private_data;
+ __poll_t mask = 0;
+
+ poll_wait(file, &client->wait, pt);
+
+ if (fw_device_is_shutdown(client->device))
+ mask |= EPOLLHUP | EPOLLERR;
+ if (!list_empty(&client->event_list))
+ mask |= EPOLLIN | EPOLLRDNORM;
+
+ return mask;
+}
+
+const struct file_operations fw_device_ops = {
+ .owner = THIS_MODULE,
+ .open = fw_device_op_open,
+ .read = fw_device_op_read,
+ .unlocked_ioctl = fw_device_op_ioctl,
+ .mmap = fw_device_op_mmap,
+ .release = fw_device_op_release,
+ .poll = fw_device_op_poll,
+ .compat_ioctl = compat_ptr_ioctl,
+};
diff --git a/drivers/firewire/core-device.c b/drivers/firewire/core-device.c
new file mode 100644
index 000000000..cbac66916
--- /dev/null
+++ b/drivers/firewire/core-device.c
@@ -0,0 +1,1444 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Device probing and sysfs code.
+ *
+ * Copyright (C) 2005-2006 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/bug.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/jiffies.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/random.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+
+#include <linux/atomic.h>
+#include <asm/byteorder.h>
+
+#include "core.h"
+
+#define ROOT_DIR_OFFSET 5
+
+void fw_csr_iterator_init(struct fw_csr_iterator *ci, const u32 *p)
+{
+ ci->p = p + 1;
+ ci->end = ci->p + (p[0] >> 16);
+}
+EXPORT_SYMBOL(fw_csr_iterator_init);
+
+int fw_csr_iterator_next(struct fw_csr_iterator *ci, int *key, int *value)
+{
+ *key = *ci->p >> 24;
+ *value = *ci->p & 0xffffff;
+
+ return ci->p++ < ci->end;
+}
+EXPORT_SYMBOL(fw_csr_iterator_next);
+
+static const u32 *search_directory(const u32 *directory, int search_key)
+{
+ struct fw_csr_iterator ci;
+ int key, value;
+
+ search_key |= CSR_DIRECTORY;
+
+ fw_csr_iterator_init(&ci, directory);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ if (key == search_key)
+ return ci.p - 1 + value;
+ }
+
+ return NULL;
+}
+
+static const u32 *search_leaf(const u32 *directory, int search_key)
+{
+ struct fw_csr_iterator ci;
+ int last_key = 0, key, value;
+
+ fw_csr_iterator_init(&ci, directory);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ if (last_key == search_key &&
+ key == (CSR_DESCRIPTOR | CSR_LEAF))
+ return ci.p - 1 + value;
+
+ last_key = key;
+ }
+
+ return NULL;
+}
+
+static int textual_leaf_to_string(const u32 *block, char *buf, size_t size)
+{
+ unsigned int quadlets, i;
+ char c;
+
+ if (!size || !buf)
+ return -EINVAL;
+
+ quadlets = min(block[0] >> 16, 256U);
+ if (quadlets < 2)
+ return -ENODATA;
+
+ if (block[1] != 0 || block[2] != 0)
+ /* unknown language/character set */
+ return -ENODATA;
+
+ block += 3;
+ quadlets -= 2;
+ for (i = 0; i < quadlets * 4 && i < size - 1; i++) {
+ c = block[i / 4] >> (24 - 8 * (i % 4));
+ if (c == '\0')
+ break;
+ buf[i] = c;
+ }
+ buf[i] = '\0';
+
+ return i;
+}
+
+/**
+ * fw_csr_string() - reads a string from the configuration ROM
+ * @directory: e.g. root directory or unit directory
+ * @key: the key of the preceding directory entry
+ * @buf: where to put the string
+ * @size: size of @buf, in bytes
+ *
+ * The string is taken from a minimal ASCII text descriptor leaf just after the entry with the
+ * @key. The string is zero-terminated. An overlong string is silently truncated such that it
+ * and the zero byte fit into @size.
+ *
+ * Returns strlen(buf) or a negative error code.
+ */
+int fw_csr_string(const u32 *directory, int key, char *buf, size_t size)
+{
+ const u32 *leaf = search_leaf(directory, key);
+ if (!leaf)
+ return -ENOENT;
+
+ return textual_leaf_to_string(leaf, buf, size);
+}
+EXPORT_SYMBOL(fw_csr_string);
+
+static void get_ids(const u32 *directory, int *id)
+{
+ struct fw_csr_iterator ci;
+ int key, value;
+
+ fw_csr_iterator_init(&ci, directory);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ switch (key) {
+ case CSR_VENDOR: id[0] = value; break;
+ case CSR_MODEL: id[1] = value; break;
+ case CSR_SPECIFIER_ID: id[2] = value; break;
+ case CSR_VERSION: id[3] = value; break;
+ }
+ }
+}
+
+static void get_modalias_ids(const struct fw_unit *unit, int *id)
+{
+ const u32 *root_directory = &fw_parent_device(unit)->config_rom[ROOT_DIR_OFFSET];
+ const u32 *directories[] = {NULL, NULL, NULL};
+ const u32 *vendor_directory;
+ int i;
+
+ directories[0] = root_directory;
+
+ // Legacy layout of configuration ROM described in Annex 1 of 'Configuration ROM for AV/C
+ // Devices 1.0 (December 12, 2000, 1394 Trading Association, TA Document 1999027)'.
+ vendor_directory = search_directory(root_directory, CSR_VENDOR);
+ if (!vendor_directory) {
+ directories[1] = unit->directory;
+ } else {
+ directories[1] = vendor_directory;
+ directories[2] = unit->directory;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(directories) && !!directories[i]; ++i)
+ get_ids(directories[i], id);
+}
+
+static bool match_ids(const struct ieee1394_device_id *id_table, int *id)
+{
+ int match = 0;
+
+ if (id[0] == id_table->vendor_id)
+ match |= IEEE1394_MATCH_VENDOR_ID;
+ if (id[1] == id_table->model_id)
+ match |= IEEE1394_MATCH_MODEL_ID;
+ if (id[2] == id_table->specifier_id)
+ match |= IEEE1394_MATCH_SPECIFIER_ID;
+ if (id[3] == id_table->version)
+ match |= IEEE1394_MATCH_VERSION;
+
+ return (match & id_table->match_flags) == id_table->match_flags;
+}
+
+static const struct ieee1394_device_id *unit_match(struct device *dev,
+ const struct device_driver *drv)
+{
+ const struct ieee1394_device_id *id_table =
+ container_of_const(drv, struct fw_driver, driver)->id_table;
+ int id[] = {0, 0, 0, 0};
+
+ get_modalias_ids(fw_unit(dev), id);
+
+ for (; id_table->match_flags != 0; id_table++)
+ if (match_ids(id_table, id))
+ return id_table;
+
+ return NULL;
+}
+
+static bool is_fw_unit(const struct device *dev);
+
+static int fw_unit_match(struct device *dev, const struct device_driver *drv)
+{
+ /* We only allow binding to fw_units. */
+ return is_fw_unit(dev) && unit_match(dev, drv) != NULL;
+}
+
+static int fw_unit_probe(struct device *dev)
+{
+ struct fw_driver *driver =
+ container_of(dev->driver, struct fw_driver, driver);
+
+ return driver->probe(fw_unit(dev), unit_match(dev, dev->driver));
+}
+
+static void fw_unit_remove(struct device *dev)
+{
+ struct fw_driver *driver =
+ container_of(dev->driver, struct fw_driver, driver);
+
+ driver->remove(fw_unit(dev));
+}
+
+static int get_modalias(const struct fw_unit *unit, char *buffer, size_t buffer_size)
+{
+ int id[] = {0, 0, 0, 0};
+
+ get_modalias_ids(unit, id);
+
+ return snprintf(buffer, buffer_size,
+ "ieee1394:ven%08Xmo%08Xsp%08Xver%08X",
+ id[0], id[1], id[2], id[3]);
+}
+
+static int fw_unit_uevent(const struct device *dev, struct kobj_uevent_env *env)
+{
+ const struct fw_unit *unit = fw_unit(dev);
+ char modalias[64];
+
+ get_modalias(unit, modalias, sizeof(modalias));
+
+ if (add_uevent_var(env, "MODALIAS=%s", modalias))
+ return -ENOMEM;
+
+ return 0;
+}
+
+const struct bus_type fw_bus_type = {
+ .name = "firewire",
+ .match = fw_unit_match,
+ .probe = fw_unit_probe,
+ .remove = fw_unit_remove,
+};
+EXPORT_SYMBOL(fw_bus_type);
+
+int fw_device_enable_phys_dma(struct fw_device *device)
+{
+ int generation = device->generation;
+
+ /* device->node_id, accessed below, must not be older than generation */
+ smp_rmb();
+
+ return device->card->driver->enable_phys_dma(device->card,
+ device->node_id,
+ generation);
+}
+EXPORT_SYMBOL(fw_device_enable_phys_dma);
+
+struct config_rom_attribute {
+ struct device_attribute attr;
+ u32 key;
+};
+
+static ssize_t show_immediate(struct device *dev,
+ struct device_attribute *dattr, char *buf)
+{
+ struct config_rom_attribute *attr =
+ container_of(dattr, struct config_rom_attribute, attr);
+ struct fw_csr_iterator ci;
+ const u32 *directories[] = {NULL, NULL};
+ int i, value = -1;
+
+ guard(rwsem_read)(&fw_device_rwsem);
+
+ if (is_fw_unit(dev)) {
+ directories[0] = fw_unit(dev)->directory;
+ } else {
+ const u32 *root_directory = fw_device(dev)->config_rom + ROOT_DIR_OFFSET;
+ const u32 *vendor_directory = search_directory(root_directory, CSR_VENDOR);
+
+ if (!vendor_directory) {
+ directories[0] = root_directory;
+ } else {
+ // Legacy layout of configuration ROM described in Annex 1 of
+ // 'Configuration ROM for AV/C Devices 1.0 (December 12, 2000, 1394 Trading
+ // Association, TA Document 1999027)'.
+ directories[0] = vendor_directory;
+ directories[1] = root_directory;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(directories) && !!directories[i]; ++i) {
+ int key, val;
+
+ fw_csr_iterator_init(&ci, directories[i]);
+ while (fw_csr_iterator_next(&ci, &key, &val)) {
+ if (attr->key == key)
+ value = val;
+ }
+ }
+
+ if (value < 0)
+ return -ENOENT;
+
+ // Note that this function is also called by init_fw_attribute_group() with NULL pointer.
+ return buf ? sysfs_emit(buf, "0x%06x\n", value) : 0;
+}
+
+#define IMMEDIATE_ATTR(name, key) \
+ { __ATTR(name, S_IRUGO, show_immediate, NULL), key }
+
+static ssize_t show_text_leaf(struct device *dev,
+ struct device_attribute *dattr, char *buf)
+{
+ struct config_rom_attribute *attr =
+ container_of(dattr, struct config_rom_attribute, attr);
+ const u32 *directories[] = {NULL, NULL};
+ size_t bufsize;
+ char dummy_buf[2];
+ int i, ret = -ENOENT;
+
+ guard(rwsem_read)(&fw_device_rwsem);
+
+ if (is_fw_unit(dev)) {
+ directories[0] = fw_unit(dev)->directory;
+ } else {
+ const u32 *root_directory = fw_device(dev)->config_rom + ROOT_DIR_OFFSET;
+ const u32 *vendor_directory = search_directory(root_directory, CSR_VENDOR);
+
+ if (!vendor_directory) {
+ directories[0] = root_directory;
+ } else {
+ // Legacy layout of configuration ROM described in Annex 1 of
+ // 'Configuration ROM for AV/C Devices 1.0 (December 12, 2000, 1394
+ // Trading Association, TA Document 1999027)'.
+ directories[0] = root_directory;
+ directories[1] = vendor_directory;
+ }
+ }
+
+ // Note that this function is also called by init_fw_attribute_group() with NULL pointer.
+ if (buf) {
+ bufsize = PAGE_SIZE - 1;
+ } else {
+ buf = dummy_buf;
+ bufsize = 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(directories) && !!directories[i]; ++i) {
+ int result = fw_csr_string(directories[i], attr->key, buf, bufsize);
+ // Detected.
+ if (result >= 0) {
+ ret = result;
+ } else if (i == 0 && attr->key == CSR_VENDOR) {
+ // Sony DVMC-DA1 has configuration ROM such that the descriptor leaf entry
+ // in the root directory follows to the directory entry for vendor ID
+ // instead of the immediate value for vendor ID.
+ result = fw_csr_string(directories[i], CSR_DIRECTORY | attr->key, buf,
+ bufsize);
+ if (result >= 0)
+ ret = result;
+ }
+ }
+
+ if (ret < 0)
+ return ret;
+
+ // Strip trailing whitespace and add newline.
+ while (ret > 0 && isspace(buf[ret - 1]))
+ ret--;
+ strcpy(buf + ret, "\n");
+ ret++;
+
+ return ret;
+}
+
+#define TEXT_LEAF_ATTR(name, key) \
+ { __ATTR(name, S_IRUGO, show_text_leaf, NULL), key }
+
+static struct config_rom_attribute config_rom_attributes[] = {
+ IMMEDIATE_ATTR(vendor, CSR_VENDOR),
+ IMMEDIATE_ATTR(hardware_version, CSR_HARDWARE_VERSION),
+ IMMEDIATE_ATTR(specifier_id, CSR_SPECIFIER_ID),
+ IMMEDIATE_ATTR(version, CSR_VERSION),
+ IMMEDIATE_ATTR(model, CSR_MODEL),
+ TEXT_LEAF_ATTR(vendor_name, CSR_VENDOR),
+ TEXT_LEAF_ATTR(model_name, CSR_MODEL),
+ TEXT_LEAF_ATTR(hardware_version_name, CSR_HARDWARE_VERSION),
+};
+
+static void init_fw_attribute_group(struct device *dev,
+ struct device_attribute *attrs,
+ struct fw_attribute_group *group)
+{
+ struct device_attribute *attr;
+ int i, j;
+
+ for (j = 0; attrs[j].attr.name != NULL; j++)
+ group->attrs[j] = &attrs[j].attr;
+
+ for (i = 0; i < ARRAY_SIZE(config_rom_attributes); i++) {
+ attr = &config_rom_attributes[i].attr;
+ if (attr->show(dev, attr, NULL) < 0)
+ continue;
+ group->attrs[j++] = &attr->attr;
+ }
+
+ group->attrs[j] = NULL;
+ group->groups[0] = &group->group;
+ group->groups[1] = NULL;
+ group->group.attrs = group->attrs;
+ dev->groups = (const struct attribute_group **) group->groups;
+}
+
+static ssize_t modalias_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fw_unit *unit = fw_unit(dev);
+ int length;
+
+ length = get_modalias(unit, buf, PAGE_SIZE);
+ strcpy(buf + length, "\n");
+
+ return length + 1;
+}
+
+static ssize_t rom_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fw_device *device = fw_device(dev->parent);
+ struct fw_unit *unit = fw_unit(dev);
+
+ return sysfs_emit(buf, "%td\n", unit->directory - device->config_rom);
+}
+
+static struct device_attribute fw_unit_attributes[] = {
+ __ATTR_RO(modalias),
+ __ATTR_RO(rom_index),
+ __ATTR_NULL,
+};
+
+static ssize_t config_rom_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fw_device *device = fw_device(dev);
+ size_t length;
+
+ guard(rwsem_read)(&fw_device_rwsem);
+
+ length = device->config_rom_length * 4;
+ memcpy(buf, device->config_rom, length);
+
+ return length;
+}
+
+static ssize_t guid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fw_device *device = fw_device(dev);
+
+ guard(rwsem_read)(&fw_device_rwsem);
+
+ return sysfs_emit(buf, "0x%08x%08x\n", device->config_rom[3], device->config_rom[4]);
+}
+
+static ssize_t is_local_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fw_device *device = fw_device(dev);
+
+ return sysfs_emit(buf, "%u\n", device->is_local);
+}
+
+static int units_sprintf(char *buf, const u32 *directory)
+{
+ struct fw_csr_iterator ci;
+ int key, value;
+ int specifier_id = 0;
+ int version = 0;
+
+ fw_csr_iterator_init(&ci, directory);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ switch (key) {
+ case CSR_SPECIFIER_ID:
+ specifier_id = value;
+ break;
+ case CSR_VERSION:
+ version = value;
+ break;
+ }
+ }
+
+ return sprintf(buf, "0x%06x:0x%06x ", specifier_id, version);
+}
+
+static ssize_t units_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fw_device *device = fw_device(dev);
+ struct fw_csr_iterator ci;
+ int key, value, i = 0;
+
+ guard(rwsem_read)(&fw_device_rwsem);
+
+ fw_csr_iterator_init(&ci, &device->config_rom[ROOT_DIR_OFFSET]);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ if (key != (CSR_UNIT | CSR_DIRECTORY))
+ continue;
+ i += units_sprintf(&buf[i], ci.p + value - 1);
+ if (i >= PAGE_SIZE - (8 + 1 + 8 + 1))
+ break;
+ }
+
+ if (i)
+ buf[i - 1] = '\n';
+
+ return i;
+}
+
+static struct device_attribute fw_device_attributes[] = {
+ __ATTR_RO(config_rom),
+ __ATTR_RO(guid),
+ __ATTR_RO(is_local),
+ __ATTR_RO(units),
+ __ATTR_NULL,
+};
+
+#define CANON_OUI 0x000085
+
+static int detect_quirks_by_bus_information_block(const u32 *bus_information_block)
+{
+ int quirks = 0;
+
+ if ((bus_information_block[2] & 0x000000f0) == 0)
+ quirks |= FW_DEVICE_QUIRK_IRM_IS_1394_1995_ONLY;
+
+ if ((bus_information_block[3] >> 8) == CANON_OUI)
+ quirks |= FW_DEVICE_QUIRK_IRM_IGNORES_BUS_MANAGER;
+
+ return quirks;
+}
+
+struct entry_match {
+ unsigned int index;
+ u32 value;
+};
+
+static const struct entry_match motu_audio_express_matches[] = {
+ { 1, 0x030001f2 },
+ { 3, 0xd1000002 },
+ { 4, 0x8d000005 },
+ { 6, 0x120001f2 },
+ { 7, 0x13000033 },
+ { 8, 0x17104800 },
+};
+
+static const struct entry_match tascam_fw_series_matches[] = {
+ { 1, 0x0300022e },
+ { 3, 0x8d000006 },
+ { 4, 0xd1000001 },
+ { 6, 0x1200022e },
+ { 8, 0xd4000004 },
+};
+
+static int detect_quirks_by_root_directory(const u32 *root_directory, unsigned int length)
+{
+ static const struct {
+ enum fw_device_quirk quirk;
+ const struct entry_match *matches;
+ unsigned int match_count;
+ } *entry, entries[] = {
+ {
+ .quirk = FW_DEVICE_QUIRK_ACK_PACKET_WITH_INVALID_PENDING_CODE,
+ .matches = motu_audio_express_matches,
+ .match_count = ARRAY_SIZE(motu_audio_express_matches),
+ },
+ {
+ .quirk = FW_DEVICE_QUIRK_UNSTABLE_AT_S400,
+ .matches = tascam_fw_series_matches,
+ .match_count = ARRAY_SIZE(tascam_fw_series_matches),
+ },
+ };
+ int quirks = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+ int j;
+
+ entry = entries + i;
+ for (j = 0; j < entry->match_count; ++j) {
+ unsigned int index = entry->matches[j].index;
+ unsigned int value = entry->matches[j].value;
+
+ if ((length < index) || (root_directory[index] != value))
+ break;
+ }
+ if (j == entry->match_count)
+ quirks |= entry->quirk;
+ }
+
+ return quirks;
+}
+
+static int read_rom(struct fw_device *device, int generation, int speed, int index, u32 *data)
+{
+ u64 offset = (CSR_REGISTER_BASE | CSR_CONFIG_ROM) + index * 4;
+ int i, rcode;
+
+ /* device->node_id, accessed below, must not be older than generation */
+ smp_rmb();
+
+ for (i = 10; i < 100; i += 10) {
+ rcode = fw_run_transaction(device->card,
+ TCODE_READ_QUADLET_REQUEST, device->node_id,
+ generation, speed, offset, data, 4);
+ if (rcode != RCODE_BUSY)
+ break;
+ msleep(i);
+ }
+ be32_to_cpus(data);
+
+ return rcode;
+}
+
+// By quadlet unit.
+#define MAX_CONFIG_ROM_SIZE ((CSR_CONFIG_ROM_END - CSR_CONFIG_ROM) / sizeof(u32))
+
+/*
+ * Read the bus info block, perform a speed probe, and read all of the rest of
+ * the config ROM. We do all this with a cached bus generation. If the bus
+ * generation changes under us, read_config_rom will fail and get retried.
+ * It's better to start all over in this case because the node from which we
+ * are reading the ROM may have changed the ROM during the reset.
+ * Returns either a result code or a negative error code.
+ */
+static int read_config_rom(struct fw_device *device, int generation)
+{
+ struct fw_card *card = device->card;
+ const u32 *new_rom, *old_rom __free(kfree) = NULL;
+ u32 *stack, *rom __free(kfree) = NULL;
+ u32 sp, key;
+ int i, end, length, ret, speed;
+ int quirks;
+
+ rom = kmalloc(sizeof(*rom) * MAX_CONFIG_ROM_SIZE +
+ sizeof(*stack) * MAX_CONFIG_ROM_SIZE, GFP_KERNEL);
+ if (rom == NULL)
+ return -ENOMEM;
+
+ stack = &rom[MAX_CONFIG_ROM_SIZE];
+ memset(rom, 0, sizeof(*rom) * MAX_CONFIG_ROM_SIZE);
+
+ speed = SCODE_100;
+
+ /* First read the bus info block. */
+ for (i = 0; i < 5; i++) {
+ ret = read_rom(device, generation, speed, i, &rom[i]);
+ if (ret != RCODE_COMPLETE)
+ return ret;
+ /*
+ * As per IEEE1212 7.2, during initialization, devices can
+ * reply with a 0 for the first quadlet of the config
+ * rom to indicate that they are booting (for example,
+ * if the firmware is on the disk of a external
+ * harddisk). In that case we just fail, and the
+ * retry mechanism will try again later.
+ */
+ if (i == 0 && rom[i] == 0)
+ return RCODE_BUSY;
+ }
+
+ quirks = detect_quirks_by_bus_information_block(rom);
+
+ // Just prevent from torn writing/reading.
+ WRITE_ONCE(device->quirks, quirks);
+
+ /*
+ * Now parse the config rom. The config rom is a recursive
+ * directory structure so we parse it using a stack of
+ * references to the blocks that make up the structure. We
+ * push a reference to the root directory on the stack to
+ * start things off.
+ */
+ length = i;
+ sp = 0;
+ stack[sp++] = 0xc0000005;
+ while (sp > 0) {
+ /*
+ * Pop the next block reference of the stack. The
+ * lower 24 bits is the offset into the config rom,
+ * the upper 8 bits are the type of the reference the
+ * block.
+ */
+ key = stack[--sp];
+ i = key & 0xffffff;
+ if (WARN_ON(i >= MAX_CONFIG_ROM_SIZE))
+ return -ENXIO;
+
+ /* Read header quadlet for the block to get the length. */
+ ret = read_rom(device, generation, speed, i, &rom[i]);
+ if (ret != RCODE_COMPLETE)
+ return ret;
+ end = i + (rom[i] >> 16) + 1;
+ if (end > MAX_CONFIG_ROM_SIZE) {
+ /*
+ * This block extends outside the config ROM which is
+ * a firmware bug. Ignore this whole block, i.e.
+ * simply set a fake block length of 0.
+ */
+ fw_err(card, "skipped invalid ROM block %x at %llx\n",
+ rom[i],
+ i * 4 | CSR_REGISTER_BASE | CSR_CONFIG_ROM);
+ rom[i] = 0;
+ end = i;
+ }
+ i++;
+
+ /*
+ * Now read in the block. If this is a directory
+ * block, check the entries as we read them to see if
+ * it references another block, and push it in that case.
+ */
+ for (; i < end; i++) {
+ ret = read_rom(device, generation, speed, i, &rom[i]);
+ if (ret != RCODE_COMPLETE)
+ return ret;
+
+ if ((key >> 30) != 3 || (rom[i] >> 30) < 2)
+ continue;
+ /*
+ * Offset points outside the ROM. May be a firmware
+ * bug or an Extended ROM entry (IEEE 1212-2001 clause
+ * 7.7.18). Simply overwrite this pointer here by a
+ * fake immediate entry so that later iterators over
+ * the ROM don't have to check offsets all the time.
+ */
+ if (i + (rom[i] & 0xffffff) >= MAX_CONFIG_ROM_SIZE) {
+ fw_err(card,
+ "skipped unsupported ROM entry %x at %llx\n",
+ rom[i],
+ i * 4 | CSR_REGISTER_BASE | CSR_CONFIG_ROM);
+ rom[i] = 0;
+ continue;
+ }
+ stack[sp++] = i + rom[i];
+ }
+ if (length < i)
+ length = i;
+ }
+
+ quirks |= detect_quirks_by_root_directory(rom + ROOT_DIR_OFFSET, length - ROOT_DIR_OFFSET);
+
+ // Just prevent from torn writing/reading.
+ WRITE_ONCE(device->quirks, quirks);
+
+ if (unlikely(quirks & FW_DEVICE_QUIRK_UNSTABLE_AT_S400))
+ speed = SCODE_200;
+ else
+ speed = device->node->max_speed;
+
+ // Determine the speed of
+ // - devices with link speed less than PHY speed,
+ // - devices with 1394b PHY (unless only connected to 1394a PHYs),
+ // - all devices if there are 1394b repeaters.
+ // Note, we cannot use the bus info block's link_spd as starting point because some buggy
+ // firmwares set it lower than necessary and because 1394-1995 nodes do not have the field.
+ if ((rom[2] & 0x7) < speed || speed == SCODE_BETA || card->beta_repeaters_present) {
+ u32 dummy;
+
+ // for S1600 and S3200.
+ if (speed == SCODE_BETA)
+ speed = card->link_speed;
+
+ while (speed > SCODE_100) {
+ if (read_rom(device, generation, speed, 0, &dummy) ==
+ RCODE_COMPLETE)
+ break;
+ --speed;
+ }
+ }
+
+ device->max_speed = speed;
+
+ old_rom = device->config_rom;
+ new_rom = kmemdup(rom, length * 4, GFP_KERNEL);
+ if (new_rom == NULL)
+ return -ENOMEM;
+
+ scoped_guard(rwsem_write, &fw_device_rwsem) {
+ device->config_rom = new_rom;
+ device->config_rom_length = length;
+ }
+
+ device->max_rec = rom[2] >> 12 & 0xf;
+ device->cmc = rom[2] >> 30 & 1;
+ device->irmc = rom[2] >> 31 & 1;
+
+ return RCODE_COMPLETE;
+}
+
+static void fw_unit_release(struct device *dev)
+{
+ struct fw_unit *unit = fw_unit(dev);
+
+ fw_device_put(fw_parent_device(unit));
+ kfree(unit);
+}
+
+static struct device_type fw_unit_type = {
+ .uevent = fw_unit_uevent,
+ .release = fw_unit_release,
+};
+
+static bool is_fw_unit(const struct device *dev)
+{
+ return dev->type == &fw_unit_type;
+}
+
+static void create_units(struct fw_device *device)
+{
+ struct fw_csr_iterator ci;
+ struct fw_unit *unit;
+ int key, value, i;
+
+ i = 0;
+ fw_csr_iterator_init(&ci, &device->config_rom[ROOT_DIR_OFFSET]);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ if (key != (CSR_UNIT | CSR_DIRECTORY))
+ continue;
+
+ /*
+ * Get the address of the unit directory and try to
+ * match the drivers id_tables against it.
+ */
+ unit = kzalloc_obj(*unit);
+ if (unit == NULL)
+ continue;
+
+ unit->directory = ci.p + value - 1;
+ unit->device.bus = &fw_bus_type;
+ unit->device.type = &fw_unit_type;
+ unit->device.parent = &device->device;
+ dev_set_name(&unit->device, "%s.%d", dev_name(&device->device), i++);
+
+ BUILD_BUG_ON(ARRAY_SIZE(unit->attribute_group.attrs) <
+ ARRAY_SIZE(fw_unit_attributes) +
+ ARRAY_SIZE(config_rom_attributes));
+ init_fw_attribute_group(&unit->device,
+ fw_unit_attributes,
+ &unit->attribute_group);
+
+ fw_device_get(device);
+ if (device_register(&unit->device) < 0) {
+ put_device(&unit->device);
+ continue;
+ }
+ }
+}
+
+static int shutdown_unit(struct device *device, void *data)
+{
+ device_unregister(device);
+
+ return 0;
+}
+
+/*
+ * fw_device_rwsem acts as dual purpose mutex:
+ * - serializes accesses to fw_device.config_rom/.config_rom_length and
+ * fw_unit.directory, unless those accesses happen at safe occasions
+ */
+DECLARE_RWSEM(fw_device_rwsem);
+
+DEFINE_XARRAY_ALLOC(fw_device_xa);
+int fw_cdev_major;
+
+struct fw_device *fw_device_get_by_devt(dev_t devt)
+{
+ struct fw_device *device;
+
+ device = xa_load(&fw_device_xa, MINOR(devt));
+ if (device)
+ fw_device_get(device);
+
+ return device;
+}
+
+struct workqueue_struct *fw_workqueue;
+EXPORT_SYMBOL(fw_workqueue);
+
+static void fw_schedule_device_work(struct fw_device *device,
+ unsigned long delay)
+{
+ queue_delayed_work(fw_workqueue, &device->work, delay);
+}
+
+/*
+ * These defines control the retry behavior for reading the config
+ * rom. It shouldn't be necessary to tweak these; if the device
+ * doesn't respond to a config rom read within 10 seconds, it's not
+ * going to respond at all. As for the initial delay, a lot of
+ * devices will be able to respond within half a second after bus
+ * reset. On the other hand, it's not really worth being more
+ * aggressive than that, since it scales pretty well; if 10 devices
+ * are plugged in, they're all getting read within one second.
+ */
+
+#define MAX_RETRIES 10
+#define RETRY_DELAY secs_to_jiffies(3)
+#define INITIAL_DELAY msecs_to_jiffies(500)
+#define SHUTDOWN_DELAY secs_to_jiffies(2)
+
+static void fw_device_shutdown(struct work_struct *work)
+{
+ struct fw_device *device = from_work(device, work, work.work);
+
+ if (time_is_after_jiffies64(device->card->reset_jiffies + SHUTDOWN_DELAY)
+ && !list_empty(&device->card->link)) {
+ fw_schedule_device_work(device, SHUTDOWN_DELAY);
+ return;
+ }
+
+ if (atomic_cmpxchg(&device->state,
+ FW_DEVICE_GONE,
+ FW_DEVICE_SHUTDOWN) != FW_DEVICE_GONE)
+ return;
+
+ fw_device_cdev_remove(device);
+ device_for_each_child(&device->device, NULL, shutdown_unit);
+ device_unregister(&device->device);
+
+ xa_erase(&fw_device_xa, MINOR(device->device.devt));
+
+ fw_device_put(device);
+}
+
+static void fw_device_release(struct device *dev)
+{
+ struct fw_device *device = fw_device(dev);
+ struct fw_card *card = device->card;
+
+ /*
+ * Take the card lock so we don't set this to NULL while a
+ * FW_NODE_UPDATED callback is being handled or while the
+ * bus manager work looks at this node.
+ */
+ scoped_guard(spinlock_irqsave, &card->lock)
+ fw_node_set_device(device->node, NULL);
+
+ fw_node_put(device->node);
+ kfree(device->config_rom);
+ kfree(device);
+ fw_card_put(card);
+}
+
+static struct device_type fw_device_type = {
+ .release = fw_device_release,
+};
+
+static bool is_fw_device(const struct device *dev)
+{
+ return dev->type == &fw_device_type;
+}
+
+static int update_unit(struct device *dev, void *data)
+{
+ struct fw_unit *unit = fw_unit(dev);
+ struct fw_driver *driver = (struct fw_driver *)dev->driver;
+
+ if (is_fw_unit(dev) && driver != NULL && driver->update != NULL) {
+ device_lock(dev);
+ driver->update(unit);
+ device_unlock(dev);
+ }
+
+ return 0;
+}
+
+static void fw_device_update(struct work_struct *work)
+{
+ struct fw_device *device = from_work(device, work, work.work);
+
+ fw_device_cdev_update(device);
+ device_for_each_child(&device->device, NULL, update_unit);
+}
+
+enum { BC_UNKNOWN = 0, BC_UNIMPLEMENTED, BC_IMPLEMENTED, };
+
+static void set_broadcast_channel(struct fw_device *device, int generation)
+{
+ struct fw_card *card = device->card;
+ __be32 data;
+ int rcode;
+
+ if (!card->broadcast_channel_allocated)
+ return;
+
+ /*
+ * The Broadcast_Channel Valid bit is required by nodes which want to
+ * transmit on this channel. Such transmissions are practically
+ * exclusive to IP over 1394 (RFC 2734). IP capable nodes are required
+ * to be IRM capable and have a max_rec of 8 or more. We use this fact
+ * to narrow down to which nodes we send Broadcast_Channel updates.
+ */
+ if (!device->irmc || device->max_rec < 8)
+ return;
+
+ /*
+ * Some 1394-1995 nodes crash if this 1394a-2000 register is written.
+ * Perform a read test first.
+ */
+ if (device->bc_implemented == BC_UNKNOWN) {
+ rcode = fw_run_transaction(card, TCODE_READ_QUADLET_REQUEST,
+ device->node_id, generation, device->max_speed,
+ CSR_REGISTER_BASE + CSR_BROADCAST_CHANNEL,
+ &data, 4);
+ switch (rcode) {
+ case RCODE_COMPLETE:
+ if (data & cpu_to_be32(1 << 31)) {
+ device->bc_implemented = BC_IMPLEMENTED;
+ break;
+ }
+ fallthrough; /* to case address error */
+ case RCODE_ADDRESS_ERROR:
+ device->bc_implemented = BC_UNIMPLEMENTED;
+ }
+ }
+
+ if (device->bc_implemented == BC_IMPLEMENTED) {
+ data = cpu_to_be32(BROADCAST_CHANNEL_INITIAL |
+ BROADCAST_CHANNEL_VALID);
+ fw_run_transaction(card, TCODE_WRITE_QUADLET_REQUEST,
+ device->node_id, generation, device->max_speed,
+ CSR_REGISTER_BASE + CSR_BROADCAST_CHANNEL,
+ &data, 4);
+ }
+}
+
+int fw_device_set_broadcast_channel(struct device *dev, void *gen)
+{
+ if (is_fw_device(dev))
+ set_broadcast_channel(fw_device(dev), (long)gen);
+
+ return 0;
+}
+
+static int compare_configuration_rom(struct device *dev, const void *data)
+{
+ const struct fw_device *old = fw_device(dev);
+ const u32 *config_rom = data;
+
+ if (!is_fw_device(dev))
+ return 0;
+
+ // Compare the bus information block and root_length/root_crc.
+ return !memcmp(old->config_rom, config_rom, 6 * 4);
+}
+
+static void fw_device_init(struct work_struct *work)
+{
+ struct fw_device *device = from_work(device, work, work.work);
+ struct fw_card *card = device->card;
+ struct device *found;
+ u32 minor;
+ int ret;
+
+ /*
+ * All failure paths here call fw_node_set_device(node, NULL), so that we
+ * don't try to do device_for_each_child() on a kfree()'d
+ * device.
+ */
+
+ ret = read_config_rom(device, device->generation);
+ if (ret != RCODE_COMPLETE) {
+ if (device->config_rom_retries < MAX_RETRIES &&
+ atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
+ device->config_rom_retries++;
+ fw_schedule_device_work(device, RETRY_DELAY);
+ } else {
+ if (device->node->link_on)
+ fw_notice(card, "giving up on node %x: reading config rom failed: %s\n",
+ device->node_id,
+ fw_rcode_string(ret));
+ if (device->node == card->root_node)
+ fw_schedule_bm_work(card, 0);
+ fw_device_release(&device->device);
+ }
+ return;
+ }
+
+ // If a device was pending for deletion because its node went away but its bus info block
+ // and root directory header matches that of a newly discovered device, revive the
+ // existing fw_device. The newly allocated fw_device becomes obsolete instead.
+ //
+ // serialize config_rom access.
+ scoped_guard(rwsem_read, &fw_device_rwsem) {
+ found = device_find_child(card->device, device->config_rom,
+ compare_configuration_rom);
+ }
+ if (found) {
+ struct fw_device *reused = fw_device(found);
+
+ if (atomic_cmpxchg(&reused->state,
+ FW_DEVICE_GONE,
+ FW_DEVICE_RUNNING) == FW_DEVICE_GONE) {
+ // serialize node access
+ scoped_guard(spinlock_irq, &card->lock) {
+ struct fw_node *current_node = device->node;
+ struct fw_node *obsolete_node = reused->node;
+
+ device->node = obsolete_node;
+ fw_node_set_device(device->node, device);
+ reused->node = current_node;
+ fw_node_set_device(reused->node, reused);
+
+ reused->max_speed = device->max_speed;
+ reused->node_id = current_node->node_id;
+ smp_wmb(); /* update node_id before generation */
+ reused->generation = card->generation;
+ reused->config_rom_retries = 0;
+ fw_notice(card, "rediscovered device %s\n",
+ dev_name(found));
+
+ reused->workfn = fw_device_update;
+ fw_schedule_device_work(reused, 0);
+
+ if (current_node == card->root_node)
+ fw_schedule_bm_work(card, 0);
+ }
+
+ put_device(found);
+ fw_device_release(&device->device);
+
+ return;
+ }
+
+ put_device(found);
+ }
+
+ device_initialize(&device->device);
+
+ fw_device_get(device);
+
+ // The index of allocated entry is used for minor identifier of device node.
+ ret = xa_alloc(&fw_device_xa, &minor, device, XA_LIMIT(0, MINORMASK), GFP_KERNEL);
+ if (ret < 0)
+ goto error;
+
+ device->device.bus = &fw_bus_type;
+ device->device.type = &fw_device_type;
+ device->device.parent = card->device;
+ device->device.devt = MKDEV(fw_cdev_major, minor);
+ dev_set_name(&device->device, "fw%d", minor);
+
+ BUILD_BUG_ON(ARRAY_SIZE(device->attribute_group.attrs) <
+ ARRAY_SIZE(fw_device_attributes) +
+ ARRAY_SIZE(config_rom_attributes));
+ init_fw_attribute_group(&device->device,
+ fw_device_attributes,
+ &device->attribute_group);
+
+ if (device_add(&device->device)) {
+ fw_err(card, "failed to add device\n");
+ goto error_with_cdev;
+ }
+
+ create_units(device);
+
+ /*
+ * Transition the device to running state. If it got pulled
+ * out from under us while we did the initialization work, we
+ * have to shut down the device again here. Normally, though,
+ * fw_node_event will be responsible for shutting it down when
+ * necessary. We have to use the atomic cmpxchg here to avoid
+ * racing with the FW_NODE_DESTROYED case in
+ * fw_node_event().
+ */
+ if (atomic_cmpxchg(&device->state,
+ FW_DEVICE_INITIALIZING,
+ FW_DEVICE_RUNNING) == FW_DEVICE_GONE) {
+ device->workfn = fw_device_shutdown;
+ fw_schedule_device_work(device, SHUTDOWN_DELAY);
+ } else {
+ fw_notice(card, "created device %s: GUID %08x%08x, S%d00, quirks %08x\n",
+ dev_name(&device->device),
+ device->config_rom[3], device->config_rom[4],
+ 1 << device->max_speed, device->quirks);
+ device->config_rom_retries = 0;
+
+ set_broadcast_channel(device, device->generation);
+
+ add_device_randomness(&device->config_rom[3], 8);
+ }
+
+ /*
+ * Reschedule the IRM work if we just finished reading the
+ * root node config rom. If this races with a bus reset we
+ * just end up running the IRM work a couple of extra times -
+ * pretty harmless.
+ */
+ if (device->node == card->root_node)
+ fw_schedule_bm_work(card, 0);
+
+ return;
+
+ error_with_cdev:
+ xa_erase(&fw_device_xa, minor);
+ error:
+ fw_device_put(device); // fw_device_xa's reference.
+
+ put_device(&device->device); /* our reference */
+}
+
+/* Reread and compare bus info block and header of root directory */
+static int reread_config_rom(struct fw_device *device, int generation,
+ bool *changed)
+{
+ u32 q;
+ int i, rcode;
+
+ for (i = 0; i < 6; i++) {
+ rcode = read_rom(device, generation, device->max_speed, i, &q);
+ if (rcode != RCODE_COMPLETE)
+ return rcode;
+
+ if (i == 0 && q == 0)
+ /* inaccessible (see read_config_rom); retry later */
+ return RCODE_BUSY;
+
+ if (q != device->config_rom[i]) {
+ *changed = true;
+ return RCODE_COMPLETE;
+ }
+ }
+
+ *changed = false;
+ return RCODE_COMPLETE;
+}
+
+static void fw_device_refresh(struct work_struct *work)
+{
+ struct fw_device *device = from_work(device, work, work.work);
+ struct fw_card *card = device->card;
+ int ret, node_id = device->node_id;
+ bool changed;
+
+ ret = reread_config_rom(device, device->generation, &changed);
+ if (ret != RCODE_COMPLETE)
+ goto failed_config_rom;
+
+ if (!changed) {
+ if (atomic_cmpxchg(&device->state,
+ FW_DEVICE_INITIALIZING,
+ FW_DEVICE_RUNNING) == FW_DEVICE_GONE)
+ goto gone;
+
+ fw_device_update(work);
+ device->config_rom_retries = 0;
+ goto out;
+ }
+
+ /*
+ * Something changed. We keep things simple and don't investigate
+ * further. We just destroy all previous units and create new ones.
+ */
+ device_for_each_child(&device->device, NULL, shutdown_unit);
+
+ ret = read_config_rom(device, device->generation);
+ if (ret != RCODE_COMPLETE)
+ goto failed_config_rom;
+
+ fw_device_cdev_update(device);
+ create_units(device);
+
+ /* Userspace may want to re-read attributes. */
+ kobject_uevent(&device->device.kobj, KOBJ_CHANGE);
+
+ if (atomic_cmpxchg(&device->state,
+ FW_DEVICE_INITIALIZING,
+ FW_DEVICE_RUNNING) == FW_DEVICE_GONE)
+ goto gone;
+
+ fw_notice(card, "refreshed device %s\n", dev_name(&device->device));
+ device->config_rom_retries = 0;
+ goto out;
+
+ failed_config_rom:
+ if (device->config_rom_retries < MAX_RETRIES &&
+ atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
+ device->config_rom_retries++;
+ fw_schedule_device_work(device, RETRY_DELAY);
+ return;
+ }
+
+ fw_notice(card, "giving up on refresh of device %s: %s\n",
+ dev_name(&device->device), fw_rcode_string(ret));
+ gone:
+ atomic_set(&device->state, FW_DEVICE_GONE);
+ device->workfn = fw_device_shutdown;
+ fw_schedule_device_work(device, SHUTDOWN_DELAY);
+ out:
+ if (node_id == card->root_node->node_id)
+ fw_schedule_bm_work(card, 0);
+}
+
+static void fw_device_workfn(struct work_struct *work)
+{
+ struct fw_device *device = from_work(device, to_delayed_work(work), work);
+ device->workfn(work);
+}
+
+void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
+{
+ struct fw_device *device;
+
+ switch (event) {
+ case FW_NODE_CREATED:
+ /*
+ * Attempt to scan the node, regardless whether its self ID has
+ * the L (link active) flag set or not. Some broken devices
+ * send L=0 but have an up-and-running link; others send L=1
+ * without actually having a link.
+ */
+ create:
+ device = kzalloc_obj(*device, GFP_ATOMIC);
+ if (device == NULL)
+ break;
+
+ /*
+ * Do minimal initialization of the device here, the
+ * rest will happen in fw_device_init().
+ *
+ * Attention: A lot of things, even fw_device_get(),
+ * cannot be done before fw_device_init() finished!
+ * You can basically just check device->state and
+ * schedule work until then, but only while holding
+ * card->lock.
+ */
+ atomic_set(&device->state, FW_DEVICE_INITIALIZING);
+ device->card = fw_card_get(card);
+ device->node = fw_node_get(node);
+ device->node_id = node->node_id;
+ device->generation = card->generation;
+ device->is_local = node == card->local_node;
+ mutex_init(&device->client_list_mutex);
+ INIT_LIST_HEAD(&device->client_list);
+
+ /*
+ * Set the node data to point back to this device so
+ * FW_NODE_UPDATED callbacks can update the node_id
+ * and generation for the device.
+ */
+ fw_node_set_device(node, device);
+
+ /*
+ * Many devices are slow to respond after bus resets,
+ * especially if they are bus powered and go through
+ * power-up after getting plugged in. We schedule the
+ * first config rom scan half a second after bus reset.
+ */
+ device->workfn = fw_device_init;
+ INIT_DELAYED_WORK(&device->work, fw_device_workfn);
+ fw_schedule_device_work(device, INITIAL_DELAY);
+ break;
+
+ case FW_NODE_INITIATED_RESET:
+ case FW_NODE_LINK_ON:
+ device = fw_node_get_device(node);
+ if (device == NULL)
+ goto create;
+
+ device->node_id = node->node_id;
+ smp_wmb(); /* update node_id before generation */
+ device->generation = card->generation;
+ if (atomic_cmpxchg(&device->state,
+ FW_DEVICE_RUNNING,
+ FW_DEVICE_INITIALIZING) == FW_DEVICE_RUNNING) {
+ device->workfn = fw_device_refresh;
+ fw_schedule_device_work(device,
+ device->is_local ? 0 : INITIAL_DELAY);
+ }
+ break;
+
+ case FW_NODE_UPDATED:
+ device = fw_node_get_device(node);
+ if (device == NULL)
+ break;
+
+ device->node_id = node->node_id;
+ smp_wmb(); /* update node_id before generation */
+ device->generation = card->generation;
+ if (atomic_read(&device->state) == FW_DEVICE_RUNNING) {
+ device->workfn = fw_device_update;
+ fw_schedule_device_work(device, 0);
+ }
+ break;
+
+ case FW_NODE_DESTROYED:
+ case FW_NODE_LINK_OFF:
+ if (!fw_node_get_device(node))
+ break;
+
+ /*
+ * Destroy the device associated with the node. There
+ * are two cases here: either the device is fully
+ * initialized (FW_DEVICE_RUNNING) or we're in the
+ * process of reading its config rom
+ * (FW_DEVICE_INITIALIZING). If it is fully
+ * initialized we can reuse device->work to schedule a
+ * full fw_device_shutdown(). If not, there's work
+ * scheduled to read it's config rom, and we just put
+ * the device in shutdown state to have that code fail
+ * to create the device.
+ */
+ device = fw_node_get_device(node);
+ if (atomic_xchg(&device->state,
+ FW_DEVICE_GONE) == FW_DEVICE_RUNNING) {
+ device->workfn = fw_device_shutdown;
+ fw_schedule_device_work(device,
+ list_empty(&card->link) ? 0 : SHUTDOWN_DELAY);
+ }
+ break;
+ }
+}
+
+#ifdef CONFIG_FIREWIRE_KUNIT_DEVICE_ATTRIBUTE_TEST
+#include "device-attribute-test.c"
+#endif
diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c
new file mode 100644
index 000000000..d2b40a1a5
--- /dev/null
+++ b/drivers/firewire/core-iso.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Isochronous I/O functionality:
+ * - Isochronous DMA context management
+ * - Isochronous bus resource management (channels, bandwidth), client side
+ *
+ * Copyright (C) 2006 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/vmalloc.h>
+#include <linux/export.h>
+
+#include <asm/byteorder.h>
+
+#include "core.h"
+
+#include <trace/events/firewire.h>
+
+/*
+ * Isochronous DMA context management
+ */
+
+int fw_iso_buffer_alloc(struct fw_iso_buffer *buffer, int page_count)
+{
+ struct page **page_array __free(kfree) = kzalloc_objs(page_array[0],
+ page_count);
+
+ if (!page_array)
+ return -ENOMEM;
+
+ // Retrieve noncontiguous pages. The descriptors for 1394 OHCI isochronous DMA contexts
+ // have a set of address and length per each, while the reason to use pages is the
+ // convenience to map them into virtual address space of user process.
+ unsigned long nr_populated = alloc_pages_bulk(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO,
+ page_count, page_array);
+ if (nr_populated != page_count) {
+ // Assuming the above call fills page_array sequentially from the beginning.
+ release_pages(page_array, nr_populated);
+ return -ENOMEM;
+ }
+
+ buffer->page_count = page_count;
+ buffer->pages = no_free_ptr(page_array);
+
+ return 0;
+}
+
+int fw_iso_buffer_map_dma(struct fw_iso_buffer *buffer, struct fw_card *card,
+ enum dma_data_direction direction)
+{
+ dma_addr_t *dma_addrs __free(kfree) = kzalloc_objs(dma_addrs[0],
+ buffer->page_count);
+ int i;
+
+ if (!dma_addrs)
+ return -ENOMEM;
+
+ // Retrieve DMA mapping addresses for the pages. They are not contiguous. Maintain the cache
+ // coherency for the pages by hand.
+ for (i = 0; i < buffer->page_count; i++) {
+ // The dma_map_phys() with a physical address per page is available here, instead.
+ dma_addr_t dma_addr = dma_map_page(card->device, buffer->pages[i], 0, PAGE_SIZE,
+ direction);
+ if (dma_mapping_error(card->device, dma_addr))
+ break;
+
+ dma_addrs[i] = dma_addr;
+ }
+ if (i < buffer->page_count) {
+ while (i-- > 0)
+ dma_unmap_page(card->device, dma_addrs[i], PAGE_SIZE, buffer->direction);
+ return -ENOMEM;
+ }
+
+ buffer->direction = direction;
+ buffer->dma_addrs = no_free_ptr(dma_addrs);
+
+ return 0;
+}
+
+int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card,
+ int page_count, enum dma_data_direction direction)
+{
+ int ret;
+
+ ret = fw_iso_buffer_alloc(buffer, page_count);
+ if (ret < 0)
+ return ret;
+
+ ret = fw_iso_buffer_map_dma(buffer, card, direction);
+ if (ret < 0)
+ fw_iso_buffer_destroy(buffer, card);
+
+ return ret;
+}
+EXPORT_SYMBOL(fw_iso_buffer_init);
+
+void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer,
+ struct fw_card *card)
+{
+ if (buffer->dma_addrs) {
+ for (int i = 0; i < buffer->page_count; ++i) {
+ dma_addr_t dma_addr = buffer->dma_addrs[i];
+ dma_unmap_page(card->device, dma_addr, PAGE_SIZE, buffer->direction);
+ }
+ kfree(buffer->dma_addrs);
+ buffer->dma_addrs = NULL;
+ }
+
+ if (buffer->pages) {
+ release_pages(buffer->pages, buffer->page_count);
+ kfree(buffer->pages);
+ buffer->pages = NULL;
+ }
+
+ buffer->page_count = 0;
+}
+EXPORT_SYMBOL(fw_iso_buffer_destroy);
+
+/* Convert DMA address to offset into virtually contiguous buffer. */
+size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed)
+{
+ for (int i = 0; i < buffer->page_count; i++) {
+ dma_addr_t dma_addr = buffer->dma_addrs[i];
+ ssize_t offset = (ssize_t)completed - (ssize_t)dma_addr;
+ if (offset > 0 && offset <= PAGE_SIZE)
+ return (i << PAGE_SHIFT) + offset;
+ }
+
+ return 0;
+}
+
+struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, int channel,
+ int speed, size_t header_size, size_t header_storage_size,
+ union fw_iso_callback callback, void *callback_data)
+{
+ struct fw_iso_context *ctx;
+
+ ctx = card->driver->allocate_iso_context(card, type, channel, header_size,
+ header_storage_size);
+ if (IS_ERR(ctx))
+ return ctx;
+
+ ctx->card = card;
+ ctx->type = type;
+ ctx->channel = channel;
+ ctx->speed = speed;
+ ctx->flags = 0;
+ ctx->header_size = header_size;
+ ctx->header_storage_size = header_storage_size;
+ ctx->callback = callback;
+ ctx->callback_data = callback_data;
+
+ trace_isoc_outbound_allocate(ctx, channel, speed);
+ trace_isoc_inbound_single_allocate(ctx, channel, header_size);
+ trace_isoc_inbound_multiple_allocate(ctx);
+
+ return ctx;
+}
+EXPORT_SYMBOL(__fw_iso_context_create);
+
+void fw_iso_context_destroy(struct fw_iso_context *ctx)
+{
+ trace_isoc_outbound_destroy(ctx);
+ trace_isoc_inbound_single_destroy(ctx);
+ trace_isoc_inbound_multiple_destroy(ctx);
+
+ ctx->card->driver->free_iso_context(ctx);
+}
+EXPORT_SYMBOL(fw_iso_context_destroy);
+
+int fw_iso_context_start(struct fw_iso_context *ctx,
+ int cycle, int sync, int tags)
+{
+ trace_isoc_outbound_start(ctx, cycle);
+ 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);
+}
+EXPORT_SYMBOL(fw_iso_context_start);
+
+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);
+}
+
+int fw_iso_context_queue(struct fw_iso_context *ctx,
+ struct fw_iso_packet *packet,
+ struct fw_iso_buffer *buffer,
+ unsigned long payload)
+{
+ trace_isoc_outbound_queue(ctx, payload, packet);
+ 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);
+}
+EXPORT_SYMBOL(fw_iso_context_queue);
+
+void fw_iso_context_queue_flush(struct fw_iso_context *ctx)
+{
+ trace_isoc_outbound_flush(ctx);
+ trace_isoc_inbound_single_flush(ctx);
+ trace_isoc_inbound_multiple_flush(ctx);
+
+ ctx->card->driver->flush_queue_iso(ctx);
+}
+EXPORT_SYMBOL(fw_iso_context_queue_flush);
+
+/**
+ * fw_iso_context_flush_completions() - process isochronous context in current process context.
+ * @ctx: the isochronous context
+ *
+ * Process the isochronous context in the current process context. The registered callback function
+ * is called when a queued packet buffer with the interrupt flag is completed, either after
+ * transmission in the IT context or after being filled in the IR context. Additionally, the
+ * callback function is also called for the packet buffer completed at last. Furthermore, the
+ * callback function is called as well when the header buffer in the context becomes full. If it is
+ * required to process the context asynchronously, fw_iso_context_schedule_flush_completions() is
+ * available instead.
+ *
+ * Context: Process context. May sleep due to disable_work_sync().
+ */
+int fw_iso_context_flush_completions(struct fw_iso_context *ctx)
+{
+ int err;
+
+ trace_isoc_outbound_flush_completions(ctx);
+ trace_isoc_inbound_single_flush_completions(ctx);
+ trace_isoc_inbound_multiple_flush_completions(ctx);
+
+ might_sleep();
+
+ // Avoid dead lock due to programming mistake.
+ if (WARN_ON_ONCE(current_work() == &ctx->work))
+ return 0;
+
+ disable_work_sync(&ctx->work);
+
+ err = ctx->card->driver->flush_iso_completions(ctx);
+
+ enable_work(&ctx->work);
+
+ return err;
+}
+EXPORT_SYMBOL(fw_iso_context_flush_completions);
+
+int fw_iso_context_stop(struct fw_iso_context *ctx)
+{
+ int err;
+
+ trace_isoc_outbound_stop(ctx);
+ trace_isoc_inbound_single_stop(ctx);
+ trace_isoc_inbound_multiple_stop(ctx);
+
+ might_sleep();
+
+ // Avoid dead lock due to programming mistake.
+ if (WARN_ON_ONCE(current_work() == &ctx->work))
+ return 0;
+
+ err = ctx->card->driver->stop_iso(ctx);
+
+ cancel_work_sync(&ctx->work);
+
+ return err;
+}
+EXPORT_SYMBOL(fw_iso_context_stop);
+
+/*
+ * Isochronous bus resource management (channels, bandwidth), client side
+ */
+
+static int manage_bandwidth(struct fw_card *card, int irm_id, int generation,
+ int bandwidth, bool allocate)
+{
+ int try, new, old = allocate ? BANDWIDTH_AVAILABLE_INITIAL : 0;
+ __be32 data[2];
+
+ /*
+ * On a 1394a IRM with low contention, try < 1 is enough.
+ * On a 1394-1995 IRM, we need at least try < 2.
+ * Let's just do try < 5.
+ */
+ for (try = 0; try < 5; try++) {
+ new = allocate ? old - bandwidth : old + bandwidth;
+ if (new < 0 || new > BANDWIDTH_AVAILABLE_INITIAL)
+ return -EBUSY;
+
+ data[0] = cpu_to_be32(old);
+ data[1] = cpu_to_be32(new);
+ switch (fw_run_transaction(card, TCODE_LOCK_COMPARE_SWAP,
+ irm_id, generation, SCODE_100,
+ CSR_REGISTER_BASE + CSR_BANDWIDTH_AVAILABLE,
+ data, 8)) {
+ case RCODE_GENERATION:
+ /* A generation change frees all bandwidth. */
+ return allocate ? -EAGAIN : bandwidth;
+
+ case RCODE_COMPLETE:
+ if (be32_to_cpup(data) == old)
+ return bandwidth;
+
+ old = be32_to_cpup(data);
+ /* Fall through. */
+ }
+ }
+
+ return -EIO;
+}
+
+static int manage_channel(struct fw_card *card, int irm_id, int generation,
+ u32 channels_mask, u64 offset, bool allocate)
+{
+ __be32 bit, all, old;
+ __be32 data[2];
+ int channel, ret = -EIO, retry = 5;
+
+ old = all = allocate ? cpu_to_be32(~0) : 0;
+
+ for (channel = 0; channel < 32; channel++) {
+ if (!(channels_mask & 1 << channel))
+ continue;
+
+ ret = -EBUSY;
+
+ bit = cpu_to_be32(1 << (31 - channel));
+ if ((old & bit) != (all & bit))
+ continue;
+
+ data[0] = old;
+ data[1] = old ^ bit;
+ switch (fw_run_transaction(card, TCODE_LOCK_COMPARE_SWAP,
+ irm_id, generation, SCODE_100,
+ offset, data, 8)) {
+ case RCODE_GENERATION:
+ /* A generation change frees all channels. */
+ return allocate ? -EAGAIN : channel;
+
+ case RCODE_COMPLETE:
+ if (data[0] == old)
+ return channel;
+
+ old = data[0];
+
+ /* Is the IRM 1394a-2000 compliant? */
+ if ((data[0] & bit) == (data[1] & bit))
+ continue;
+
+ fallthrough; /* It's a 1394-1995 IRM, retry */
+ default:
+ if (retry) {
+ retry--;
+ channel--;
+ } else {
+ ret = -EIO;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static void deallocate_channel(struct fw_card *card, int irm_id,
+ int generation, int channel)
+{
+ u32 mask;
+ u64 offset;
+
+ mask = channel < 32 ? 1 << channel : 1 << (channel - 32);
+ offset = channel < 32 ? CSR_REGISTER_BASE + CSR_CHANNELS_AVAILABLE_HI :
+ CSR_REGISTER_BASE + CSR_CHANNELS_AVAILABLE_LO;
+
+ manage_channel(card, irm_id, generation, mask, offset, false);
+}
+
+/**
+ * fw_iso_resource_manage() - Allocate or deallocate a channel and/or bandwidth
+ * @card: card interface for this action
+ * @generation: bus generation
+ * @channels_mask: bitmask for channel allocation
+ * @channel: pointer for returning channel allocation result
+ * @bandwidth: pointer for returning bandwidth allocation result
+ * @allocate: whether to allocate (true) or deallocate (false)
+ *
+ * In parameters: card, generation, channels_mask, bandwidth, allocate
+ * Out parameters: channel, bandwidth
+ *
+ * This function blocks (sleeps) during communication with the IRM.
+ *
+ * Allocates or deallocates at most one channel out of channels_mask.
+ * channels_mask is a bitfield with MSB for channel 63 and LSB for channel 0.
+ * (Note, the IRM's CHANNELS_AVAILABLE is a big-endian bitfield with MSB for
+ * channel 0 and LSB for channel 63.)
+ * Allocates or deallocates as many bandwidth allocation units as specified.
+ *
+ * Returns channel < 0 if no channel was allocated or deallocated.
+ * Returns bandwidth = 0 if no bandwidth was allocated or deallocated.
+ *
+ * If generation is stale, deallocations succeed but allocations fail with
+ * channel = -EAGAIN.
+ *
+ * If channel allocation fails, no bandwidth will be allocated either.
+ * If bandwidth allocation fails, no channel will be allocated either.
+ * But deallocations of channel and bandwidth are tried independently
+ * of each other's success.
+ */
+void fw_iso_resource_manage(struct fw_card *card, int generation,
+ u64 channels_mask, int *channel, int *bandwidth,
+ bool allocate)
+{
+ u32 channels_hi = channels_mask; /* channels 31...0 */
+ u32 channels_lo = channels_mask >> 32; /* channels 63...32 */
+ int irm_id, ret, c = -EINVAL;
+
+ scoped_guard(spinlock_irq, &card->lock)
+ irm_id = card->irm_node->node_id;
+
+ if (channels_hi)
+ c = manage_channel(card, irm_id, generation, channels_hi,
+ CSR_REGISTER_BASE + CSR_CHANNELS_AVAILABLE_HI,
+ allocate);
+ if (channels_lo && c < 0) {
+ c = manage_channel(card, irm_id, generation, channels_lo,
+ CSR_REGISTER_BASE + CSR_CHANNELS_AVAILABLE_LO,
+ allocate);
+ if (c >= 0)
+ c += 32;
+ }
+ *channel = c;
+
+ if (allocate && channels_mask != 0 && c < 0)
+ *bandwidth = 0;
+
+ if (*bandwidth == 0)
+ return;
+
+ ret = manage_bandwidth(card, irm_id, generation, *bandwidth, allocate);
+ if (ret < 0)
+ *bandwidth = 0;
+
+ if (allocate && ret < 0) {
+ if (c >= 0)
+ deallocate_channel(card, irm_id, generation, c);
+ *channel = ret;
+ }
+}
+EXPORT_SYMBOL(fw_iso_resource_manage);
diff --git a/drivers/firewire/core-topology.c b/drivers/firewire/core-topology.c
new file mode 100644
index 000000000..df2ac0dab
--- /dev/null
+++ b/drivers/firewire/core-topology.c
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Incremental bus scan, based on bus topology
+ *
+ * Copyright (C) 2004-2006 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/atomic.h>
+#include <asm/byteorder.h>
+
+#include "core.h"
+#include "phy-packet-definitions.h"
+#include <trace/events/firewire.h>
+
+static struct fw_node *fw_node_create(u32 sid, int port_count, int color)
+{
+ struct fw_node *node;
+
+ node = kzalloc_flex(*node, ports, port_count, GFP_ATOMIC);
+ if (node == NULL)
+ return NULL;
+
+ node->color = color;
+ node->node_id = LOCAL_BUS | phy_packet_self_id_get_phy_id(sid);
+ node->link_on = phy_packet_self_id_zero_get_link_active(sid);
+ // NOTE: Only two bits, thus only for SCODE_100, SCODE_200, SCODE_400, and SCODE_BETA.
+ node->phy_speed = phy_packet_self_id_zero_get_scode(sid);
+ node->initiated_reset = phy_packet_self_id_zero_get_initiated_reset(sid);
+ node->port_count = port_count;
+
+ kref_init(&node->kref);
+ INIT_LIST_HEAD(&node->link);
+
+ return node;
+}
+
+/*
+ * Compute the maximum hop count for this node and it's children. The
+ * maximum hop count is the maximum number of connections between any
+ * two nodes in the subtree rooted at this node. We need this for
+ * setting the gap count. As we build the tree bottom up in
+ * build_tree() below, this is fairly easy to do: for each node we
+ * maintain the max hop count and the max depth, ie the number of hops
+ * to the furthest leaf. Computing the max hop count breaks down into
+ * two cases: either the path goes through this node, in which case
+ * the hop count is the sum of the two biggest child depths plus 2.
+ * Or it could be the case that the max hop path is entirely
+ * contained in a child tree, in which case the max hop count is just
+ * the max hop count of this child.
+ */
+static void update_hop_count(struct fw_node *node)
+{
+ int depths[2] = { -1, -1 };
+ int max_child_hops = 0;
+ int i;
+
+ for (i = 0; i < node->port_count; i++) {
+ if (node->ports[i] == NULL)
+ continue;
+
+ if (node->ports[i]->max_hops > max_child_hops)
+ max_child_hops = node->ports[i]->max_hops;
+
+ if (node->ports[i]->max_depth > depths[0]) {
+ depths[1] = depths[0];
+ depths[0] = node->ports[i]->max_depth;
+ } else if (node->ports[i]->max_depth > depths[1])
+ depths[1] = node->ports[i]->max_depth;
+ }
+
+ node->max_depth = depths[0] + 1;
+ node->max_hops = max(max_child_hops, depths[0] + depths[1] + 2);
+}
+
+static inline struct fw_node *fw_node(struct list_head *l)
+{
+ return list_entry(l, struct fw_node, link);
+}
+
+/*
+ * This function builds the tree representation of the topology given
+ * by the self IDs from the latest bus reset. During the construction
+ * of the tree, the function checks that the self IDs are valid and
+ * internally consistent. On success this function returns the
+ * fw_node corresponding to the local card otherwise NULL.
+ */
+static struct fw_node *build_tree(struct fw_card *card, const u32 *sid, int self_id_count,
+ unsigned int generation)
+{
+ struct self_id_sequence_enumerator enumerator = {
+ .cursor = sid,
+ .quadlet_count = self_id_count,
+ };
+ struct fw_node *node, *child, *local_node, *irm_node;
+ struct list_head stack;
+ int phy_id, stack_depth;
+ int gap_count;
+ bool beta_repeaters_present;
+
+ local_node = NULL;
+ node = NULL;
+ INIT_LIST_HEAD(&stack);
+ stack_depth = 0;
+ phy_id = 0;
+ irm_node = NULL;
+ gap_count = phy_packet_self_id_zero_get_gap_count(*sid);
+ beta_repeaters_present = false;
+
+ while (enumerator.quadlet_count > 0) {
+ unsigned int child_port_count = 0;
+ unsigned int total_port_count = 0;
+ unsigned int parent_count = 0;
+ unsigned int quadlet_count;
+ const u32 *self_id_sequence;
+ unsigned int port_capacity;
+ enum phy_packet_self_id_port_status port_status;
+ unsigned int port_index;
+ struct list_head *h;
+ int i;
+
+ self_id_sequence = self_id_sequence_enumerator_next(&enumerator, &quadlet_count);
+ if (IS_ERR(self_id_sequence)) {
+ if (PTR_ERR(self_id_sequence) != -ENODATA) {
+ fw_err(card, "inconsistent extended self IDs: %ld\n",
+ PTR_ERR(self_id_sequence));
+ return NULL;
+ }
+ break;
+ }
+
+ port_capacity = self_id_sequence_get_port_capacity(quadlet_count);
+ trace_self_id_sequence(card->index, self_id_sequence, quadlet_count, generation);
+
+ for (port_index = 0; port_index < port_capacity; ++port_index) {
+ port_status = self_id_sequence_get_port_status(self_id_sequence, quadlet_count,
+ port_index);
+ switch (port_status) {
+ case PHY_PACKET_SELF_ID_PORT_STATUS_CHILD:
+ ++child_port_count;
+ fallthrough;
+ case PHY_PACKET_SELF_ID_PORT_STATUS_PARENT:
+ case PHY_PACKET_SELF_ID_PORT_STATUS_NCONN:
+ ++total_port_count;
+ fallthrough;
+ case PHY_PACKET_SELF_ID_PORT_STATUS_NONE:
+ default:
+ break;
+ }
+ }
+
+ if (phy_id != phy_packet_self_id_get_phy_id(self_id_sequence[0])) {
+ fw_err(card, "PHY ID mismatch in self ID: %d != %d\n",
+ phy_id, phy_packet_self_id_get_phy_id(self_id_sequence[0]));
+ return NULL;
+ }
+
+ if (child_port_count > stack_depth) {
+ fw_err(card, "topology stack underflow\n");
+ return NULL;
+ }
+
+ /*
+ * Seek back from the top of our stack to find the
+ * start of the child nodes for this node.
+ */
+ for (i = 0, h = &stack; i < child_port_count; i++)
+ h = h->prev;
+ /*
+ * When the stack is empty, this yields an invalid value,
+ * but that pointer will never be dereferenced.
+ */
+ child = fw_node(h);
+
+ node = fw_node_create(self_id_sequence[0], total_port_count, card->color);
+ if (node == NULL) {
+ fw_err(card, "out of memory while building topology\n");
+ return NULL;
+ }
+
+ if (phy_id == (card->node_id & 0x3f))
+ local_node = node;
+
+ if (phy_packet_self_id_zero_get_contender(self_id_sequence[0]))
+ irm_node = node;
+
+ for (port_index = 0; port_index < total_port_count; ++port_index) {
+ port_status = self_id_sequence_get_port_status(self_id_sequence, quadlet_count,
+ port_index);
+ switch (port_status) {
+ case PHY_PACKET_SELF_ID_PORT_STATUS_PARENT:
+ // Who's your daddy? We dont know the parent node at this time, so
+ // we temporarily abuse node->color for remembering the entry in
+ // the node->ports array where the parent node should be. Later,
+ // when we handle the parent node, we fix up the reference.
+ ++parent_count;
+ node->color = port_index;
+ break;
+
+ case PHY_PACKET_SELF_ID_PORT_STATUS_CHILD:
+ node->ports[port_index] = child;
+ // Fix up parent reference for this child node.
+ child->ports[child->color] = node;
+ child->color = card->color;
+ child = fw_node(child->link.next);
+ break;
+ case PHY_PACKET_SELF_ID_PORT_STATUS_NCONN:
+ case PHY_PACKET_SELF_ID_PORT_STATUS_NONE:
+ default:
+ break;
+ }
+ }
+
+ // Check that the node reports exactly one parent port, except for the root, which
+ // of course should have no parents.
+ if ((enumerator.quadlet_count == 0 && parent_count != 0) ||
+ (enumerator.quadlet_count > 0 && parent_count != 1)) {
+ fw_err(card, "parent port inconsistency for node %d: "
+ "parent_count=%d\n", phy_id, parent_count);
+ return NULL;
+ }
+
+ /* Pop the child nodes off the stack and push the new node. */
+ __list_del(h->prev, &stack);
+ list_add_tail(&node->link, &stack);
+ stack_depth += 1 - child_port_count;
+
+ if (node->phy_speed == SCODE_BETA && parent_count + child_port_count > 1)
+ beta_repeaters_present = true;
+
+ // If PHYs report different gap counts, set an invalid count which will force a gap
+ // count reconfiguration and a reset.
+ if (phy_packet_self_id_zero_get_gap_count(self_id_sequence[0]) != gap_count)
+ gap_count = GAP_COUNT_MISMATCHED;
+
+ update_hop_count(node);
+
+ phy_id++;
+ }
+
+ card->root_node = node;
+ card->irm_node = irm_node;
+ card->gap_count = gap_count;
+ card->beta_repeaters_present = beta_repeaters_present;
+
+ return local_node;
+}
+
+typedef void (*fw_node_callback_t)(struct fw_card * card,
+ struct fw_node * node,
+ struct fw_node * parent);
+
+static void for_each_fw_node(struct fw_card *card, struct fw_node *root,
+ fw_node_callback_t callback)
+{
+ struct list_head list;
+ struct fw_node *node, *next, *child, *parent;
+ int i;
+
+ INIT_LIST_HEAD(&list);
+
+ fw_node_get(root);
+ list_add_tail(&root->link, &list);
+ parent = NULL;
+ for (node = list_first_entry(&list, typeof(*node), link);
+ !list_entry_is_head(node, &list, link);
+ node = list_next_entry(node, link)) {
+ node->color = card->color;
+
+ for (i = 0; i < node->port_count; i++) {
+ child = node->ports[i];
+ if (!child)
+ continue;
+ if (child->color == card->color)
+ parent = child;
+ else {
+ fw_node_get(child);
+ list_add_tail(&child->link, &list);
+ }
+ }
+
+ callback(card, node, parent);
+ }
+
+ list_for_each_entry_safe(node, next, &list, link)
+ fw_node_put(node);
+}
+
+static void report_lost_node(struct fw_card *card,
+ struct fw_node *node, struct fw_node *parent)
+{
+ fw_node_event(card, node, FW_NODE_DESTROYED);
+ fw_node_put(node);
+
+ /* Topology has changed - reset bus manager retry counter */
+ card->bm_retries = 0;
+}
+
+static void report_found_node(struct fw_card *card,
+ struct fw_node *node, struct fw_node *parent)
+{
+ int b_path = (node->phy_speed == SCODE_BETA);
+
+ if (parent != NULL) {
+ /* min() macro doesn't work here with gcc 3.4 */
+ node->max_speed = parent->max_speed < node->phy_speed ?
+ parent->max_speed : node->phy_speed;
+ node->b_path = parent->b_path && b_path;
+ } else {
+ node->max_speed = node->phy_speed;
+ node->b_path = b_path;
+ }
+
+ fw_node_event(card, node, FW_NODE_CREATED);
+
+ /* Topology has changed - reset bus manager retry counter */
+ card->bm_retries = 0;
+}
+
+void fw_destroy_nodes(struct fw_card *card)
+__must_hold(&card->lock)
+{
+ lockdep_assert_held(&card->lock);
+
+ card->color++;
+ if (card->local_node != NULL)
+ for_each_fw_node(card, card->local_node, report_lost_node);
+ card->local_node = NULL;
+}
+
+static void move_tree(struct fw_node *node0, struct fw_node *node1, int port)
+{
+ struct fw_node *tree;
+ int i;
+
+ tree = node1->ports[port];
+ node0->ports[port] = tree;
+ for (i = 0; i < tree->port_count; i++) {
+ if (tree->ports[i] == node1) {
+ tree->ports[i] = node0;
+ break;
+ }
+ }
+}
+
+/*
+ * Compare the old topology tree for card with the new one specified by root.
+ * Queue the nodes and mark them as either found, lost or updated.
+ * Update the nodes in the card topology tree as we go.
+ */
+static void update_tree(struct fw_card *card, struct fw_node *root)
+{
+ struct list_head list0, list1;
+ struct fw_node *node0, *node1, *next1;
+ int i, event;
+
+ INIT_LIST_HEAD(&list0);
+ list_add_tail(&card->local_node->link, &list0);
+ INIT_LIST_HEAD(&list1);
+ list_add_tail(&root->link, &list1);
+
+ node0 = fw_node(list0.next);
+ node1 = fw_node(list1.next);
+
+ while (&node0->link != &list0) {
+ WARN_ON(node0->port_count != node1->port_count);
+
+ if (node0->link_on && !node1->link_on)
+ event = FW_NODE_LINK_OFF;
+ else if (!node0->link_on && node1->link_on)
+ event = FW_NODE_LINK_ON;
+ else if (node1->initiated_reset && node1->link_on)
+ event = FW_NODE_INITIATED_RESET;
+ else
+ event = FW_NODE_UPDATED;
+
+ node0->node_id = node1->node_id;
+ node0->color = card->color;
+ node0->link_on = node1->link_on;
+ node0->initiated_reset = node1->initiated_reset;
+ node0->max_hops = node1->max_hops;
+ node1->color = card->color;
+ fw_node_event(card, node0, event);
+
+ if (card->root_node == node1)
+ card->root_node = node0;
+ if (card->irm_node == node1)
+ card->irm_node = node0;
+
+ for (i = 0; i < node0->port_count; i++) {
+ if (node0->ports[i] && node1->ports[i]) {
+ /*
+ * This port didn't change, queue the
+ * connected node for further
+ * investigation.
+ */
+ if (node0->ports[i]->color == card->color)
+ continue;
+ list_add_tail(&node0->ports[i]->link, &list0);
+ list_add_tail(&node1->ports[i]->link, &list1);
+ } else if (node0->ports[i]) {
+ /*
+ * The nodes connected here were
+ * unplugged; unref the lost nodes and
+ * queue FW_NODE_LOST callbacks for
+ * them.
+ */
+
+ for_each_fw_node(card, node0->ports[i],
+ report_lost_node);
+ node0->ports[i] = NULL;
+ } else if (node1->ports[i]) {
+ /*
+ * One or more node were connected to
+ * this port. Move the new nodes into
+ * the tree and queue FW_NODE_CREATED
+ * callbacks for them.
+ */
+ move_tree(node0, node1, i);
+ for_each_fw_node(card, node0->ports[i],
+ report_found_node);
+ }
+ }
+
+ node0 = fw_node(node0->link.next);
+ next1 = fw_node(node1->link.next);
+ fw_node_put(node1);
+ node1 = next1;
+ }
+}
+
+static void update_topology_map(__be32 *buffer, size_t buffer_size, int root_node_id,
+ const u32 *self_ids, int self_id_count)
+{
+ __be32 *map = buffer;
+ u32 next_generation = be32_to_cpu(buffer[1]) + 1;
+ int node_count = (root_node_id & 0x3f) + 1;
+
+ memset(map, 0, buffer_size);
+
+ *map++ = cpu_to_be32((self_id_count + 2) << 16);
+ *map++ = cpu_to_be32(next_generation);
+ *map++ = cpu_to_be32((node_count << 16) | self_id_count);
+
+ while (self_id_count--)
+ *map++ = cpu_to_be32p(self_ids++);
+
+ fw_compute_block_crc(buffer);
+}
+
+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)
+{
+ struct fw_node *local_node;
+
+ trace_bus_reset_handle(card->index, generation, node_id, bm_abdicate, self_ids, self_id_count);
+
+ scoped_guard(spinlock, &card->lock) {
+ // If the selfID buffer is not the immediate successor of the
+ // previously processed one, we cannot reliably compare the
+ // old and new topologies.
+ if (!is_next_generation(generation, card->generation) && card->local_node != NULL) {
+ fw_destroy_nodes(card);
+ card->bm_retries = 0;
+ }
+ card->broadcast_channel_allocated = card->broadcast_channel_auto_allocated;
+ card->node_id = node_id;
+ // Update node_id before generation to prevent anybody from using
+ // a stale node_id together with a current generation.
+ smp_wmb();
+ card->generation = generation;
+ card->reset_jiffies = get_jiffies_64();
+ card->bm_node_id = 0xffff;
+ card->bm_abdicate = bm_abdicate;
+
+ local_node = build_tree(card, self_ids, self_id_count, generation);
+
+ card->color++;
+
+ if (local_node == NULL) {
+ fw_err(card, "topology build failed\n");
+ // FIXME: We need to issue a bus reset in this case.
+ } else if (card->local_node == NULL) {
+ card->local_node = local_node;
+ for_each_fw_node(card, local_node, report_found_node);
+ } else {
+ update_tree(card, local_node);
+ }
+ }
+
+ fw_schedule_bm_work(card, 0);
+
+ // Just used by transaction layer.
+ scoped_guard(spinlock, &card->topology_map.lock) {
+ update_topology_map(card->topology_map.buffer, sizeof(card->topology_map.buffer),
+ card->root_node->node_id, self_ids, self_id_count);
+ }
+}
+EXPORT_SYMBOL(fw_core_handle_bus_reset);
diff --git a/drivers/firewire/core-trace.c b/drivers/firewire/core-trace.c
new file mode 100644
index 000000000..b70947fc7
--- /dev/null
+++ b/drivers/firewire/core-trace.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright (c) 2024 Takashi Sakamoto
+
+#include <linux/types.h>
+#include <linux/err.h>
+#include "packet-header-definitions.h"
+#include "phy-packet-definitions.h"
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/firewire.h>
+
+#ifdef TRACEPOINTS_ENABLED
+EXPORT_TRACEPOINT_SYMBOL_GPL(isoc_inbound_single_completions);
+EXPORT_TRACEPOINT_SYMBOL_GPL(isoc_inbound_multiple_completions);
+EXPORT_TRACEPOINT_SYMBOL_GPL(isoc_outbound_completions);
+#endif
diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c
new file mode 100644
index 000000000..22ae387ae
--- /dev/null
+++ b/drivers/firewire/core-transaction.c
@@ -0,0 +1,1508 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Core IEEE1394 transaction logic
+ *
+ * Copyright (C) 2004-2006 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/bug.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <asm/byteorder.h>
+
+#include "core.h"
+#include "packet-header-definitions.h"
+#include "phy-packet-definitions.h"
+#include <trace/events/firewire.h>
+
+#define HEADER_DESTINATION_IS_BROADCAST(header) \
+ ((async_header_get_destination(header) & 0x3f) == 0x3f)
+
+/* returns 0 if the split timeout handler is already running */
+static int try_cancel_split_timeout(struct fw_transaction *t)
+{
+ 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)
+{
+ 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)
+{
+ 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);
+ }
+ }
+}
+
+// card->transactions.lock must be acquired in advance.
+#define find_and_pop_transaction_entry(card, condition) \
+({ \
+ struct fw_transaction *iter, *t = NULL; \
+ list_for_each_entry(iter, &card->transactions.list, link) { \
+ if (condition) { \
+ t = iter; \
+ break; \
+ } \
+ } \
+ if (t && try_cancel_split_timeout(t)) \
+ remove_transaction_entry(card, t); \
+ t; \
+})
+
+static int close_transaction(struct fw_transaction *transaction, struct fw_card *card, int rcode,
+ u32 response_tstamp)
+{
+ struct fw_transaction *t;
+
+ // 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;
+ }
+
+ 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);
+ }
+
+ 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 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) {
+ if (list_empty(&t->link))
+ return;
+ remove_transaction_entry(card, t);
+ }
+
+ 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)
+{
+ if (list_empty(&t->link) || WARN_ON(t->is_split_transaction))
+ return;
+
+ t->is_split_transaction = true;
+
+ mod_timer(&t->split_timeout_timer, jiffies + delta);
+}
+
+static u32 compute_split_timeout_timestamp(struct fw_card *card, u32 request_timestamp);
+
+static void transmit_complete_callback(struct fw_packet *packet,
+ struct fw_card *card, int status)
+{
+ struct fw_transaction *t =
+ container_of(packet, struct fw_transaction, packet);
+
+ 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;
+ }
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ close_transaction(t, card, RCODE_BUSY, packet->timestamp);
+ break;
+ case ACK_DATA_ERROR:
+ close_transaction(t, card, RCODE_DATA_ERROR, packet->timestamp);
+ break;
+ case ACK_TYPE_ERROR:
+ close_transaction(t, card, RCODE_TYPE_ERROR, packet->timestamp);
+ 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);
+ break;
+ }
+}
+
+static void fw_fill_request(struct fw_packet *packet, int tcode, int tlabel,
+ int destination_id, int source_id, int generation, int speed,
+ unsigned long long offset, void *payload, size_t length)
+{
+ int ext_tcode;
+
+ if (tcode == TCODE_STREAM_DATA) {
+ // The value of destination_id argument should include tag, channel, and sy fields
+ // as isochronous packet header has.
+ packet->header[0] = destination_id;
+ isoc_header_set_data_length(packet->header, length);
+ isoc_header_set_tcode(packet->header, TCODE_STREAM_DATA);
+ packet->header_length = 4;
+ packet->payload = payload;
+ packet->payload_length = length;
+
+ goto common;
+ }
+
+ if (tcode > 0x10) {
+ ext_tcode = tcode & ~0x10;
+ tcode = TCODE_LOCK_REQUEST;
+ } else
+ ext_tcode = 0;
+
+ async_header_set_retry(packet->header, RETRY_X);
+ async_header_set_tlabel(packet->header, tlabel);
+ async_header_set_tcode(packet->header, tcode);
+ async_header_set_destination(packet->header, destination_id);
+ async_header_set_source(packet->header, source_id);
+ async_header_set_offset(packet->header, offset);
+
+ switch (tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ async_header_set_quadlet_data(packet->header, *(u32 *)payload);
+ packet->header_length = 16;
+ packet->payload_length = 0;
+ break;
+
+ case TCODE_LOCK_REQUEST:
+ case TCODE_WRITE_BLOCK_REQUEST:
+ async_header_set_data_length(packet->header, length);
+ async_header_set_extended_tcode(packet->header, ext_tcode);
+ packet->header_length = 16;
+ packet->payload = payload;
+ packet->payload_length = length;
+ break;
+
+ case TCODE_READ_QUADLET_REQUEST:
+ packet->header_length = 12;
+ packet->payload_length = 0;
+ break;
+
+ case TCODE_READ_BLOCK_REQUEST:
+ async_header_set_data_length(packet->header, length);
+ async_header_set_extended_tcode(packet->header, ext_tcode);
+ packet->header_length = 16;
+ packet->payload_length = 0;
+ break;
+
+ default:
+ WARN(1, "wrong tcode %d\n", tcode);
+ }
+ common:
+ packet->speed = speed;
+ packet->generation = generation;
+ packet->ack = 0;
+ packet->payload_mapped = false;
+}
+
+static int allocate_tlabel(struct fw_card *card)
+__must_hold(&card->transactions.lock)
+{
+ int tlabel;
+
+ lockdep_assert_held(&card->transactions.lock);
+
+ tlabel = card->transactions.current_tlabel;
+ while (card->transactions.tlabel_mask & (1ULL << tlabel)) {
+ tlabel = (tlabel + 1) & 0x3f;
+ if (tlabel == card->transactions.current_tlabel)
+ return -EBUSY;
+ }
+
+ card->transactions.current_tlabel = (tlabel + 1) & 0x3f;
+ card->transactions.tlabel_mask |= 1ULL << tlabel;
+
+ return tlabel;
+}
+
+/**
+ * __fw_send_request() - submit a request packet for transmission to generate callback for response
+ * subaction with or without time stamp.
+ * @card: interface to send the request at
+ * @t: transaction instance to which the request belongs
+ * @tcode: transaction code
+ * @destination_id: destination node ID, consisting of bus_ID and phy_ID
+ * @generation: bus generation in which request and response are valid
+ * @speed: transmission speed
+ * @offset: 48bit wide offset into destination's address space
+ * @payload: data payload for the request subaction
+ * @length: length of the payload, in bytes
+ * @callback: union of two functions whether to receive time stamp or not for response
+ * subaction.
+ * @with_tstamp: Whether to receive time stamp or not for response subaction.
+ * @callback_data: data to be passed to the transaction completion callback
+ *
+ * Submit a request packet into the asynchronous request transmission queue.
+ * Can be called from atomic context. If you prefer a blocking API, use
+ * fw_run_transaction() in a context that can sleep.
+ *
+ * In case of lock requests, specify one of the firewire-core specific %TCODE_
+ * constants instead of %TCODE_LOCK_REQUEST in @tcode.
+ *
+ * Make sure that the value in @destination_id is not older than the one in
+ * @generation. Otherwise the request is in danger to be sent to a wrong node.
+ *
+ * In case of asynchronous stream packets i.e. %TCODE_STREAM_DATA, the caller
+ * needs to synthesize @destination_id with fw_stream_packet_destination_id().
+ * It will contain tag, channel, and sy data instead of a node ID then.
+ *
+ * The payload buffer at @data is going to be DMA-mapped except in case of
+ * @length <= 8 or of local (loopback) requests. Hence make sure that the
+ * buffer complies with the restrictions of the streaming DMA mapping API.
+ * @payload must not be freed before the @callback is called.
+ *
+ * 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
+ * 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.
+ *
+ * Note some timing corner cases: fw_send_request() may complete much earlier
+ * 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.
+ */
+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)
+{
+ int tlabel;
+
+ /*
+ * 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;
+
+ (void)fw_card_read_cycle_time(card, &curr_cycle_time);
+ tstamp = cycle_time_to_ohci_tstamp(curr_cycle_time);
+
+ 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);
+ }
+
+ // 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);
+
+ // Safe with no lock, since the index field of fw_card is immutable once assigned.
+ trace_async_request_outbound_initiate((uintptr_t)t, card->index, generation, speed,
+ t->packet.header, payload,
+ tcode_is_read_request(tcode) ? 0 : length / 4);
+
+ card->driver->send_request(card, &t->packet);
+}
+EXPORT_SYMBOL_GPL(__fw_send_request);
+
+struct transaction_callback_data {
+ struct completion done;
+ void *payload;
+ int rcode;
+};
+
+static void transaction_callback(struct fw_card *card, int rcode,
+ void *payload, size_t length, void *data)
+{
+ struct transaction_callback_data *d = data;
+
+ if (rcode == RCODE_COMPLETE)
+ memcpy(d->payload, payload, length);
+ d->rcode = rcode;
+ complete(&d->done);
+}
+
+/**
+ * fw_run_transaction() - send request and sleep until transaction is completed
+ * @card: card interface for this request
+ * @tcode: transaction code
+ * @destination_id: destination node ID, consisting of bus_ID and phy_ID
+ * @generation: bus generation in which request and response are valid
+ * @speed: transmission speed
+ * @offset: 48bit wide offset into destination's address space
+ * @payload: data payload for the request subaction
+ * @length: length of the payload, in bytes
+ *
+ * Returns the RCODE. See fw_send_request() for parameter documentation.
+ * Unlike fw_send_request(), @data points to the payload of the request or/and
+ * to the payload of the response. DMA mapping restrictions apply to outbound
+ * request payloads of >= 8 bytes but not to inbound response payloads.
+ */
+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)
+{
+ struct transaction_callback_data d;
+ struct fw_transaction t;
+
+ timer_setup_on_stack(&t.split_timeout_timer, NULL, 0);
+ init_completion(&d.done);
+ d.payload = payload;
+ fw_send_request(card, &t, tcode, destination_id, generation, speed,
+ offset, payload, length, transaction_callback, &d);
+ wait_for_completion(&d.done);
+ timer_destroy_on_stack(&t.split_timeout_timer);
+
+ return d.rcode;
+}
+EXPORT_SYMBOL(fw_run_transaction);
+
+static DEFINE_MUTEX(phy_config_mutex);
+static DECLARE_COMPLETION(phy_config_done);
+
+static void transmit_phy_packet_callback(struct fw_packet *packet,
+ struct fw_card *card, int status)
+{
+ trace_async_phy_outbound_complete((uintptr_t)packet, card->index, packet->generation, status,
+ packet->timestamp);
+ complete(&phy_config_done);
+}
+
+static struct fw_packet phy_config_packet = {
+ .header_length = 12,
+ .payload_length = 0,
+ .speed = SCODE_100,
+ .callback = transmit_phy_packet_callback,
+};
+
+void fw_send_phy_config(struct fw_card *card,
+ int node_id, int generation, int gap_count)
+{
+ long timeout = msecs_to_jiffies(100);
+ u32 data = 0;
+
+ phy_packet_set_packet_identifier(&data, PHY_PACKET_PACKET_IDENTIFIER_PHY_CONFIG);
+
+ if (node_id != FW_PHY_CONFIG_NO_NODE_ID) {
+ phy_packet_phy_config_set_root_id(&data, node_id);
+ phy_packet_phy_config_set_force_root_node(&data, true);
+ }
+
+ if (gap_count == FW_PHY_CONFIG_CURRENT_GAP_COUNT) {
+ gap_count = card->driver->read_phy_reg(card, 1);
+ if (gap_count < 0)
+ return;
+
+ gap_count &= 63;
+ if (gap_count == 63)
+ return;
+ }
+ phy_packet_phy_config_set_gap_count(&data, gap_count);
+ phy_packet_phy_config_set_gap_count_optimization(&data, true);
+
+ guard(mutex)(&phy_config_mutex);
+
+ async_header_set_tcode(phy_config_packet.header, TCODE_LINK_INTERNAL);
+ phy_config_packet.header[1] = data;
+ phy_config_packet.header[2] = ~data;
+ phy_config_packet.generation = generation;
+ reinit_completion(&phy_config_done);
+
+ trace_async_phy_outbound_initiate((uintptr_t)&phy_config_packet, card->index,
+ phy_config_packet.generation, phy_config_packet.header[1],
+ phy_config_packet.header[2]);
+
+ card->driver->send_request(card, &phy_config_packet);
+ wait_for_completion_timeout(&phy_config_done, timeout);
+}
+
+static struct fw_address_handler *lookup_overlapping_address_handler(
+ struct list_head *list, unsigned long long offset, size_t length)
+{
+ struct fw_address_handler *handler;
+
+ list_for_each_entry_rcu(handler, list, link) {
+ if (handler->offset < offset + length &&
+ offset < handler->offset + handler->length)
+ return handler;
+ }
+
+ return NULL;
+}
+
+static bool is_enclosing_handler(struct fw_address_handler *handler,
+ unsigned long long offset, size_t length)
+{
+ return handler->offset <= offset &&
+ offset + length <= handler->offset + handler->length;
+}
+
+static struct fw_address_handler *lookup_enclosing_address_handler(
+ struct list_head *list, unsigned long long offset, size_t length)
+{
+ struct fw_address_handler *handler;
+
+ list_for_each_entry_rcu(handler, list, link) {
+ if (is_enclosing_handler(handler, offset, length))
+ return handler;
+ }
+
+ return NULL;
+}
+
+static DEFINE_SPINLOCK(address_handler_list_lock);
+static LIST_HEAD(address_handler_list);
+
+const struct fw_address_region fw_high_memory_region =
+ { .start = FW_MAX_PHYSICAL_RANGE, .end = 0xffffe0000000ULL, };
+EXPORT_SYMBOL(fw_high_memory_region);
+
+static const struct fw_address_region low_memory_region =
+ { .start = 0x000000000000ULL, .end = FW_MAX_PHYSICAL_RANGE, };
+
+#if 0
+const struct fw_address_region fw_private_region =
+ { .start = 0xffffe0000000ULL, .end = 0xfffff0000000ULL, };
+const struct fw_address_region fw_csr_region =
+ { .start = CSR_REGISTER_BASE,
+ .end = CSR_REGISTER_BASE | CSR_CONFIG_ROM_END, };
+const struct fw_address_region fw_unit_space_region =
+ { .start = 0xfffff0000900ULL, .end = 0x1000000000000ULL, };
+#endif /* 0 */
+
+static void complete_address_handler(struct kref *kref)
+{
+ struct fw_address_handler *handler = container_of(kref, struct fw_address_handler, kref);
+
+ complete(&handler->done);
+}
+
+static void get_address_handler(struct fw_address_handler *handler)
+{
+ kref_get(&handler->kref);
+}
+
+static int put_address_handler(struct fw_address_handler *handler)
+{
+ return kref_put(&handler->kref, complete_address_handler);
+}
+
+/**
+ * fw_core_add_address_handler() - register for incoming requests
+ * @handler: callback
+ * @region: region in the IEEE 1212 node space address range
+ *
+ * region->start, ->end, and handler->length have to be quadlet-aligned.
+ *
+ * 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.
+ *
+ * Address allocations are exclusive, except for the FCP registers.
+ */
+int fw_core_add_address_handler(struct fw_address_handler *handler,
+ const struct fw_address_region *region)
+{
+ struct fw_address_handler *other;
+ int ret = -EBUSY;
+
+ if (region->start & 0xffff000000000003ULL ||
+ region->start >= region->end ||
+ region->end > 0x0001000000000000ULL ||
+ handler->length & 3 ||
+ handler->length == 0)
+ return -EINVAL;
+
+ guard(spinlock)(&address_handler_list_lock);
+
+ handler->offset = region->start;
+ while (handler->offset + handler->length <= region->end) {
+ if (is_in_fcp_region(handler->offset, handler->length))
+ other = NULL;
+ else
+ other = lookup_overlapping_address_handler
+ (&address_handler_list,
+ handler->offset, handler->length);
+ if (other != NULL) {
+ handler->offset += other->length;
+ } else {
+ init_completion(&handler->done);
+ kref_init(&handler->kref);
+ list_add_tail_rcu(&handler->link, &address_handler_list);
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(fw_core_add_address_handler);
+
+/**
+ * fw_core_remove_address_handler() - unregister an address handler
+ * @handler: callback
+ *
+ * To be called in process context.
+ *
+ * When fw_core_remove_address_handler() returns, @handler->callback() is
+ * guaranteed to not run on any CPU anymore.
+ */
+void fw_core_remove_address_handler(struct fw_address_handler *handler)
+{
+ scoped_guard(spinlock, &address_handler_list_lock)
+ list_del_rcu(&handler->link);
+
+ synchronize_rcu();
+
+ if (!put_address_handler(handler))
+ wait_for_completion(&handler->done);
+}
+EXPORT_SYMBOL(fw_core_remove_address_handler);
+
+struct fw_request {
+ struct kref kref;
+ struct fw_packet response;
+ u32 request_header[ASYNC_HEADER_QUADLET_COUNT];
+ int ack;
+ u32 timestamp;
+ u32 length;
+ u32 data[];
+};
+
+void fw_request_get(struct fw_request *request)
+{
+ kref_get(&request->kref);
+}
+
+static void release_request(struct kref *kref)
+{
+ struct fw_request *request = container_of(kref, struct fw_request, kref);
+
+ kfree(request);
+}
+
+void fw_request_put(struct fw_request *request)
+{
+ kref_put(&request->kref, release_request);
+}
+
+static void free_response_callback(struct fw_packet *packet,
+ struct fw_card *card, int status)
+{
+ struct fw_request *request = container_of(packet, struct fw_request, response);
+
+ trace_async_response_outbound_complete((uintptr_t)request, card->index, packet->generation,
+ packet->speed, status, packet->timestamp);
+
+ // Decrease the reference count since not at in-flight.
+ fw_request_put(request);
+
+ // Decrease the reference count to release the object.
+ fw_request_put(request);
+}
+
+int fw_get_response_length(struct fw_request *r)
+{
+ int tcode, ext_tcode, data_length;
+
+ tcode = async_header_get_tcode(r->request_header);
+
+ switch (tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ case TCODE_WRITE_BLOCK_REQUEST:
+ return 0;
+
+ case TCODE_READ_QUADLET_REQUEST:
+ return 4;
+
+ case TCODE_READ_BLOCK_REQUEST:
+ data_length = async_header_get_data_length(r->request_header);
+ return data_length;
+
+ case TCODE_LOCK_REQUEST:
+ ext_tcode = async_header_get_extended_tcode(r->request_header);
+ data_length = async_header_get_data_length(r->request_header);
+ switch (ext_tcode) {
+ case EXTCODE_FETCH_ADD:
+ case EXTCODE_LITTLE_ADD:
+ return data_length;
+ default:
+ return data_length / 2;
+ }
+
+ default:
+ WARN(1, "wrong tcode %d\n", tcode);
+ return 0;
+ }
+}
+
+void fw_fill_response(struct fw_packet *response, u32 *request_header,
+ int rcode, void *payload, size_t length)
+{
+ int tcode, tlabel, extended_tcode, source, destination;
+
+ tcode = async_header_get_tcode(request_header);
+ tlabel = async_header_get_tlabel(request_header);
+ source = async_header_get_destination(request_header); // Exchange.
+ destination = async_header_get_source(request_header); // Exchange.
+ extended_tcode = async_header_get_extended_tcode(request_header);
+
+ async_header_set_retry(response->header, RETRY_1);
+ async_header_set_tlabel(response->header, tlabel);
+ async_header_set_destination(response->header, destination);
+ async_header_set_source(response->header, source);
+ async_header_set_rcode(response->header, rcode);
+ response->header[2] = 0; // The field is reserved.
+
+ switch (tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ case TCODE_WRITE_BLOCK_REQUEST:
+ async_header_set_tcode(response->header, TCODE_WRITE_RESPONSE);
+ response->header_length = 12;
+ response->payload_length = 0;
+ break;
+
+ case TCODE_READ_QUADLET_REQUEST:
+ async_header_set_tcode(response->header, TCODE_READ_QUADLET_RESPONSE);
+ if (payload != NULL)
+ async_header_set_quadlet_data(response->header, *(u32 *)payload);
+ else
+ async_header_set_quadlet_data(response->header, 0);
+ response->header_length = 16;
+ response->payload_length = 0;
+ break;
+
+ case TCODE_READ_BLOCK_REQUEST:
+ case TCODE_LOCK_REQUEST:
+ async_header_set_tcode(response->header, tcode + 2);
+ async_header_set_data_length(response->header, length);
+ async_header_set_extended_tcode(response->header, extended_tcode);
+ response->header_length = 16;
+ response->payload = payload;
+ response->payload_length = length;
+ break;
+
+ default:
+ WARN(1, "wrong tcode %d\n", tcode);
+ }
+
+ response->payload_mapped = false;
+}
+EXPORT_SYMBOL(fw_fill_response);
+
+static u32 compute_split_timeout_timestamp(struct fw_card *card,
+ u32 request_timestamp)
+__must_hold(&card->split_timeout.lock)
+{
+ unsigned int cycles;
+ u32 timestamp;
+
+ lockdep_assert_held(&card->split_timeout.lock);
+
+ cycles = card->split_timeout.cycles;
+ cycles += request_timestamp & 0x1fff;
+
+ timestamp = request_timestamp & ~0x1fff;
+ timestamp += (cycles / 8000) << 13;
+ timestamp |= cycles % 8000;
+
+ return timestamp;
+}
+
+static struct fw_request *allocate_request(struct fw_card *card,
+ struct fw_packet *p)
+{
+ struct fw_request *request;
+ u32 *data, length;
+ int request_tcode;
+
+ request_tcode = async_header_get_tcode(p->header);
+ switch (request_tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ data = &p->header[3];
+ length = 4;
+ break;
+
+ case TCODE_WRITE_BLOCK_REQUEST:
+ case TCODE_LOCK_REQUEST:
+ data = p->payload;
+ length = async_header_get_data_length(p->header);
+ break;
+
+ case TCODE_READ_QUADLET_REQUEST:
+ data = NULL;
+ length = 4;
+ break;
+
+ case TCODE_READ_BLOCK_REQUEST:
+ data = NULL;
+ length = async_header_get_data_length(p->header);
+ break;
+
+ default:
+ fw_notice(card, "ERROR - corrupt request received - %08x %08x %08x\n",
+ p->header[0], p->header[1], p->header[2]);
+ return NULL;
+ }
+
+ request = kmalloc(sizeof(*request) + length, GFP_ATOMIC);
+ if (request == NULL)
+ 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)
+ request->response.timestamp = compute_split_timeout_timestamp(card, p->timestamp);
+
+ request->response.speed = p->speed;
+ request->response.generation = p->generation;
+ request->response.ack = 0;
+ request->response.callback = free_response_callback;
+ request->ack = p->ack;
+ request->timestamp = p->timestamp;
+ request->length = length;
+ if (data)
+ memcpy(request->data, data, length);
+
+ memcpy(request->request_header, p->header, sizeof(p->header));
+
+ return request;
+}
+
+/**
+ * fw_send_response: - send response packet for asynchronous transaction.
+ * @card: interface to send the response at.
+ * @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.
+ */
+void fw_send_response(struct fw_card *card,
+ struct fw_request *request, int rcode)
+{
+ u32 *data = NULL;
+ unsigned int data_length = 0;
+
+ /* unified transaction or broadcast transaction: don't respond */
+ if (request->ack != ACK_PENDING ||
+ HEADER_DESTINATION_IS_BROADCAST(request->request_header)) {
+ fw_request_put(request);
+ return;
+ }
+
+ if (rcode == RCODE_COMPLETE) {
+ data = request->data;
+ data_length = fw_get_response_length(request);
+ }
+
+ fw_fill_response(&request->response, request->request_header, rcode, data, data_length);
+
+ // Increase the reference count so that the object is kept during in-flight.
+ fw_request_get(request);
+
+ trace_async_response_outbound_initiate((uintptr_t)request, card->index,
+ request->response.generation, request->response.speed,
+ request->response.header, data,
+ data ? data_length / 4 : 0);
+
+ card->driver->send_response(card, &request->response);
+}
+EXPORT_SYMBOL(fw_send_response);
+
+/**
+ * fw_get_request_speed() - returns speed at which the @request was received
+ * @request: firewire request data
+ */
+int fw_get_request_speed(struct fw_request *request)
+{
+ return request->response.speed;
+}
+EXPORT_SYMBOL(fw_get_request_speed);
+
+/**
+ * fw_request_get_timestamp: Get timestamp of the request.
+ * @request: The opaque pointer to request structure.
+ *
+ * Get timestamp when 1394 OHCI controller receives the asynchronous request subaction. 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.
+ *
+ * Returns: timestamp of the request.
+ */
+u32 fw_request_get_timestamp(const struct fw_request *request)
+{
+ return request->timestamp;
+}
+EXPORT_SYMBOL_GPL(fw_request_get_timestamp);
+
+static void handle_exclusive_region_request(struct fw_card *card,
+ struct fw_packet *p,
+ struct fw_request *request,
+ unsigned long long offset)
+{
+ struct fw_address_handler *handler;
+ int tcode, destination, source;
+
+ destination = async_header_get_destination(p->header);
+ source = async_header_get_source(p->header);
+ tcode = async_header_get_tcode(p->header);
+ if (tcode == TCODE_LOCK_REQUEST)
+ tcode = 0x10 + async_header_get_extended_tcode(p->header);
+
+ scoped_guard(rcu) {
+ handler = lookup_enclosing_address_handler(&address_handler_list, offset,
+ request->length);
+ if (handler)
+ get_address_handler(handler);
+ }
+
+ if (!handler) {
+ fw_send_response(card, request, RCODE_ADDRESS_ERROR);
+ return;
+ }
+
+ // Outside the RCU read-side critical section. Without spinlock. With reference count.
+ handler->address_callback(card, request, tcode, destination, source, p->generation, offset,
+ request->data, request->length, handler->callback_data);
+ put_address_handler(handler);
+}
+
+// To use kmalloc allocator efficiently, this should be power of two.
+#define BUFFER_ON_KERNEL_STACK_SIZE 4
+
+static void handle_fcp_region_request(struct fw_card *card,
+ struct fw_packet *p,
+ struct fw_request *request,
+ unsigned long long offset)
+{
+ struct fw_address_handler *buffer_on_kernel_stack[BUFFER_ON_KERNEL_STACK_SIZE];
+ struct fw_address_handler *handler, **handlers;
+ int tcode, destination, source, i, count, buffer_size;
+
+ if ((offset != (CSR_REGISTER_BASE | CSR_FCP_COMMAND) &&
+ offset != (CSR_REGISTER_BASE | CSR_FCP_RESPONSE)) ||
+ request->length > 0x200) {
+ fw_send_response(card, request, RCODE_ADDRESS_ERROR);
+
+ return;
+ }
+
+ tcode = async_header_get_tcode(p->header);
+ destination = async_header_get_destination(p->header);
+ source = async_header_get_source(p->header);
+
+ if (tcode != TCODE_WRITE_QUADLET_REQUEST &&
+ tcode != TCODE_WRITE_BLOCK_REQUEST) {
+ fw_send_response(card, request, RCODE_TYPE_ERROR);
+
+ return;
+ }
+
+ count = 0;
+ handlers = buffer_on_kernel_stack;
+ buffer_size = ARRAY_SIZE(buffer_on_kernel_stack);
+ scoped_guard(rcu) {
+ list_for_each_entry_rcu(handler, &address_handler_list, link) {
+ if (is_enclosing_handler(handler, offset, request->length)) {
+ if (count >= buffer_size) {
+ int next_size = buffer_size * 2;
+ struct fw_address_handler **buffer_on_kernel_heap;
+
+ if (handlers == buffer_on_kernel_stack)
+ buffer_on_kernel_heap = NULL;
+ else
+ buffer_on_kernel_heap = handlers;
+
+ buffer_on_kernel_heap =
+ krealloc_array(buffer_on_kernel_heap, next_size,
+ sizeof(*buffer_on_kernel_heap), GFP_ATOMIC);
+ // FCP is used for purposes unrelated to significant system
+ // resources (e.g. storage or networking), so allocation
+ // failures are not considered so critical.
+ if (!buffer_on_kernel_heap)
+ break;
+
+ if (handlers == buffer_on_kernel_stack) {
+ memcpy(buffer_on_kernel_heap, buffer_on_kernel_stack,
+ sizeof(buffer_on_kernel_stack));
+ }
+
+ handlers = buffer_on_kernel_heap;
+ buffer_size = next_size;
+ }
+ get_address_handler(handler);
+ handlers[count++] = handler;
+ }
+ }
+ }
+
+ for (i = 0; i < count; ++i) {
+ handler = handlers[i];
+ handler->address_callback(card, request, tcode, destination, source,
+ p->generation, offset, request->data,
+ request->length, handler->callback_data);
+ put_address_handler(handler);
+ }
+
+ if (handlers != buffer_on_kernel_stack)
+ kfree(handlers);
+
+ fw_send_response(card, request, RCODE_COMPLETE);
+}
+
+void fw_core_handle_request(struct fw_card *card, struct fw_packet *p)
+{
+ struct fw_request *request;
+ unsigned long long offset;
+ unsigned int tcode;
+
+ if (p->ack != ACK_PENDING && p->ack != ACK_COMPLETE)
+ return;
+
+ tcode = async_header_get_tcode(p->header);
+ if (tcode_is_link_internal(tcode)) {
+ trace_async_phy_inbound((uintptr_t)p, card->index, p->generation, p->ack, p->timestamp,
+ p->header[1], p->header[2]);
+ fw_cdev_handle_phy_packet(card, p);
+ return;
+ }
+
+ request = allocate_request(card, p);
+ if (request == NULL) {
+ /* FIXME: send statically allocated busy packet. */
+ return;
+ }
+
+ trace_async_request_inbound((uintptr_t)request, card->index, p->generation, p->speed,
+ p->ack, p->timestamp, p->header, request->data,
+ tcode_is_read_request(tcode) ? 0 : request->length / 4);
+
+ offset = async_header_get_offset(p->header);
+
+ if (!is_in_fcp_region(offset, request->length))
+ handle_exclusive_region_request(card, p, request, offset);
+ else
+ handle_fcp_region_request(card, p, request, offset);
+
+}
+EXPORT_SYMBOL(fw_core_handle_request);
+
+void fw_core_handle_response(struct fw_card *card, struct fw_packet *p)
+{
+ struct fw_transaction *t = NULL;
+ u32 *data;
+ size_t data_length;
+ int tcode, tlabel, source, rcode;
+
+ tcode = async_header_get_tcode(p->header);
+ tlabel = async_header_get_tlabel(p->header);
+ source = async_header_get_source(p->header);
+ rcode = async_header_get_rcode(p->header);
+
+ // FIXME: sanity check packet, is length correct, does tcodes
+ // and addresses match to the transaction request queried later.
+ //
+ // For the tracepoints event, let us decode the header here against the concern.
+
+ switch (tcode) {
+ case TCODE_READ_QUADLET_RESPONSE:
+ data = (u32 *) &p->header[3];
+ data_length = 4;
+ break;
+
+ case TCODE_WRITE_RESPONSE:
+ data = NULL;
+ data_length = 0;
+ break;
+
+ case TCODE_READ_BLOCK_RESPONSE:
+ case TCODE_LOCK_RESPONSE:
+ data = p->payload;
+ data_length = async_header_get_data_length(p->header);
+ break;
+
+ default:
+ /* Should never happen, this is just to shut up gcc. */
+ data = NULL;
+ data_length = 0;
+ 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);
+ }
+
+ trace_async_response_inbound((uintptr_t)t, card->index, p->generation, p->speed, p->ack,
+ p->timestamp, p->header, data, data_length / 4);
+
+ if (!t) {
+ fw_notice(card, "unsolicited response (source %x, tlabel %x)\n",
+ source, tlabel);
+ return;
+ }
+
+ /*
+ * The response handler may be executed while the request handler
+ * is still pending. Cancel the request handler.
+ */
+ 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);
+ }
+}
+EXPORT_SYMBOL(fw_core_handle_response);
+
+/**
+ * fw_rcode_string - convert a firewire result code to an error description
+ * @rcode: the result code
+ */
+const char *fw_rcode_string(int rcode)
+{
+ static const char *const names[] = {
+ [RCODE_COMPLETE] = "no error",
+ [RCODE_CONFLICT_ERROR] = "conflict error",
+ [RCODE_DATA_ERROR] = "data error",
+ [RCODE_TYPE_ERROR] = "type error",
+ [RCODE_ADDRESS_ERROR] = "address error",
+ [RCODE_SEND_ERROR] = "send error",
+ [RCODE_CANCELLED] = "timeout",
+ [RCODE_BUSY] = "busy",
+ [RCODE_GENERATION] = "bus reset",
+ [RCODE_NO_ACK] = "no ack",
+ };
+
+ if ((unsigned int)rcode < ARRAY_SIZE(names) && names[rcode])
+ return names[rcode];
+ else
+ return "unknown";
+}
+EXPORT_SYMBOL(fw_rcode_string);
+
+static const struct fw_address_region topology_map_region =
+ { .start = CSR_REGISTER_BASE | CSR_TOPOLOGY_MAP,
+ .end = CSR_REGISTER_BASE | CSR_TOPOLOGY_MAP_END, };
+
+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)
+{
+ int start;
+
+ if (!tcode_is_read_request(tcode)) {
+ fw_send_response(card, request, RCODE_TYPE_ERROR);
+ return;
+ }
+
+ if ((offset & 3) > 0 || (length & 3) > 0) {
+ fw_send_response(card, request, RCODE_ADDRESS_ERROR);
+ return;
+ }
+
+ 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)
+ memcpy(payload, &card->topology_map.buffer[start], length);
+
+ fw_send_response(card, request, RCODE_COMPLETE);
+}
+
+static struct fw_address_handler topology_map = {
+ .length = 0x400,
+ .address_callback = handle_topology_map,
+};
+
+static const struct fw_address_region registers_region =
+ { .start = CSR_REGISTER_BASE,
+ .end = CSR_REGISTER_BASE | CSR_CONFIG_ROM, };
+
+static void update_split_timeout(struct fw_card *card)
+__must_hold(&card->split_timeout.lock)
+{
+ unsigned int cycles;
+
+ cycles = card->split_timeout.hi * 8000 + (card->split_timeout.lo >> 19);
+
+ /* minimum per IEEE 1394, maximum which doesn't overflow OHCI */
+ cycles = clamp(cycles, 800u, 3u * 8000u);
+
+ card->split_timeout.cycles = cycles;
+ card->split_timeout.jiffies = isoc_cycles_to_jiffies(cycles);
+}
+
+static void handle_registers(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)
+{
+ int reg = offset & ~CSR_REGISTER_BASE;
+ __be32 *data = payload;
+ int rcode = RCODE_COMPLETE;
+
+ switch (reg) {
+ case CSR_PRIORITY_BUDGET:
+ if (!card->priority_budget_implemented) {
+ rcode = RCODE_ADDRESS_ERROR;
+ break;
+ }
+ fallthrough;
+
+ case CSR_NODE_IDS:
+ /*
+ * per IEEE 1394-2008 8.3.22.3, not IEEE 1394.1-2004 3.2.8
+ * and 9.6, but interoperable with IEEE 1394.1-2004 bridges
+ */
+ fallthrough;
+
+ case CSR_STATE_CLEAR:
+ case CSR_STATE_SET:
+ case CSR_CYCLE_TIME:
+ case CSR_BUS_TIME:
+ case CSR_BUSY_TIMEOUT:
+ if (tcode == TCODE_READ_QUADLET_REQUEST)
+ *data = cpu_to_be32(card->driver->read_csr(card, reg));
+ else if (tcode == TCODE_WRITE_QUADLET_REQUEST)
+ 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);
+ 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) {
+ card->split_timeout.hi = be32_to_cpu(*data) & 7;
+ update_split_timeout(card);
+ }
+ } 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) {
+ card->split_timeout.lo = be32_to_cpu(*data) & 0xfff80000;
+ update_split_timeout(card);
+ }
+ } else {
+ rcode = RCODE_TYPE_ERROR;
+ }
+ break;
+
+ case CSR_MAINT_UTILITY:
+ if (tcode == TCODE_READ_QUADLET_REQUEST)
+ *data = card->maint_utility_register;
+ else if (tcode == TCODE_WRITE_QUADLET_REQUEST)
+ card->maint_utility_register = *data;
+ else
+ rcode = RCODE_TYPE_ERROR;
+ break;
+
+ case CSR_BROADCAST_CHANNEL:
+ if (tcode == TCODE_READ_QUADLET_REQUEST)
+ *data = cpu_to_be32(card->broadcast_channel);
+ else if (tcode == TCODE_WRITE_QUADLET_REQUEST)
+ card->broadcast_channel =
+ (be32_to_cpu(*data) & BROADCAST_CHANNEL_VALID) |
+ BROADCAST_CHANNEL_INITIAL;
+ else
+ rcode = RCODE_TYPE_ERROR;
+ break;
+
+ case CSR_BUS_MANAGER_ID:
+ case CSR_BANDWIDTH_AVAILABLE:
+ case CSR_CHANNELS_AVAILABLE_HI:
+ case CSR_CHANNELS_AVAILABLE_LO:
+ /*
+ * FIXME: these are handled by the OHCI hardware and
+ * the stack never sees these request. If we add
+ * support for a new type of controller that doesn't
+ * handle this in hardware we need to deal with these
+ * transactions.
+ */
+ BUG();
+ break;
+
+ default:
+ rcode = RCODE_ADDRESS_ERROR;
+ break;
+ }
+
+ fw_send_response(card, request, rcode);
+}
+
+static struct fw_address_handler registers = {
+ .length = 0x400,
+ .address_callback = handle_registers,
+};
+
+static void handle_low_memory(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)
+{
+ /*
+ * This catches requests not handled by the physical DMA unit,
+ * i.e., wrong transaction types or unauthorized source nodes.
+ */
+ fw_send_response(card, request, RCODE_TYPE_ERROR);
+}
+
+static struct fw_address_handler low_memory = {
+ .length = FW_MAX_PHYSICAL_RANGE,
+ .address_callback = handle_low_memory,
+};
+
+MODULE_AUTHOR("Kristian Hoegsberg <krh@bitplanet.net>");
+MODULE_DESCRIPTION("Core IEEE1394 transaction logic");
+MODULE_LICENSE("GPL");
+
+static const u32 vendor_textual_descriptor[] = {
+ /* textual descriptor leaf () */
+ 0x00060000,
+ 0x00000000,
+ 0x00000000,
+ 0x4c696e75, /* L i n u */
+ 0x78204669, /* x F i */
+ 0x72657769, /* r e w i */
+ 0x72650000, /* r e */
+};
+
+static const u32 model_textual_descriptor[] = {
+ /* model descriptor leaf () */
+ 0x00030000,
+ 0x00000000,
+ 0x00000000,
+ 0x4a756a75, /* J u j u */
+};
+
+static struct fw_descriptor vendor_id_descriptor = {
+ .length = ARRAY_SIZE(vendor_textual_descriptor),
+ .immediate = 0x03001f11,
+ .key = 0x81000000,
+ .data = vendor_textual_descriptor,
+};
+
+static struct fw_descriptor model_id_descriptor = {
+ .length = ARRAY_SIZE(model_textual_descriptor),
+ .immediate = 0x17023901,
+ .key = 0x81000000,
+ .data = model_textual_descriptor,
+};
+
+static int __init fw_core_init(void)
+{
+ int ret;
+
+ fw_workqueue = alloc_workqueue("firewire", WQ_MEM_RECLAIM | WQ_UNBOUND,
+ 0);
+ if (!fw_workqueue)
+ return -ENOMEM;
+
+ ret = bus_register(&fw_bus_type);
+ if (ret < 0) {
+ destroy_workqueue(fw_workqueue);
+ return ret;
+ }
+
+ fw_cdev_major = register_chrdev(0, "firewire", &fw_device_ops);
+ if (fw_cdev_major < 0) {
+ bus_unregister(&fw_bus_type);
+ destroy_workqueue(fw_workqueue);
+ return fw_cdev_major;
+ }
+
+ fw_core_add_address_handler(&topology_map, &topology_map_region);
+ fw_core_add_address_handler(&registers, &registers_region);
+ fw_core_add_address_handler(&low_memory, &low_memory_region);
+ fw_core_add_descriptor(&vendor_id_descriptor);
+ fw_core_add_descriptor(&model_id_descriptor);
+
+ return 0;
+}
+
+static void __exit fw_core_cleanup(void)
+{
+ unregister_chrdev(fw_cdev_major, "firewire");
+ bus_unregister(&fw_bus_type);
+ destroy_workqueue(fw_workqueue);
+ xa_destroy(&fw_device_xa);
+}
+
+module_init(fw_core_init);
+module_exit(fw_core_cleanup);
diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h
new file mode 100644
index 000000000..8b49d7480
--- /dev/null
+++ b/drivers/firewire/core.h
@@ -0,0 +1,325 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _FIREWIRE_CORE_H
+#define _FIREWIRE_CORE_H
+
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/xarray.h>
+#include <linux/mm_types.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <linux/refcount.h>
+
+struct device;
+struct fw_card;
+struct fw_device;
+struct fw_iso_buffer;
+struct fw_iso_context;
+struct fw_iso_packet;
+struct fw_node;
+struct fw_packet;
+
+
+/* -card */
+
+// This is the arbitrary value we use to indicate a mismatched gap count.
+#define GAP_COUNT_MISMATCHED 0
+
+#define isoc_cycles_to_jiffies(cycles) usecs_to_jiffies((u32)div_u64((u64)cycles * USEC_PER_SEC, 8000))
+
+extern __printf(2, 3)
+void fw_err(const struct fw_card *card, const char *fmt, ...);
+extern __printf(2, 3)
+void fw_notice(const struct fw_card *card, const char *fmt, ...);
+
+/* bitfields within the PHY registers */
+#define PHY_LINK_ACTIVE 0x80
+#define PHY_CONTENDER 0x40
+#define PHY_BUS_RESET 0x40
+#define PHY_EXTENDED_REGISTERS 0xe0
+#define PHY_BUS_SHORT_RESET 0x40
+#define PHY_INT_STATUS_BITS 0x3c
+#define PHY_ENABLE_ACCEL 0x02
+#define PHY_ENABLE_MULTI 0x01
+#define PHY_PAGE_SELECT 0xe0
+
+#define BANDWIDTH_AVAILABLE_INITIAL 4915
+#define BROADCAST_CHANNEL_INITIAL (1 << 31 | 31)
+#define BROADCAST_CHANNEL_VALID (1 << 30)
+
+#define CSR_STATE_BIT_CMSTR (1 << 8)
+#define CSR_STATE_BIT_ABDICATE (1 << 10)
+
+struct fw_card_driver {
+ /*
+ * Enable the given card with the given initial config rom.
+ * This function is expected to activate the card, and either
+ * enable the PHY or set the link_on bit and initiate a bus
+ * reset.
+ */
+ int (*enable)(struct fw_card *card,
+ const __be32 *config_rom, size_t length);
+
+ // After returning the call, any function is no longer triggered to handle hardware event.
+ void (*disable)(struct fw_card *card);
+
+ int (*read_phy_reg)(struct fw_card *card, int address);
+ int (*update_phy_reg)(struct fw_card *card, int address,
+ int clear_bits, int set_bits);
+
+ /*
+ * Update the config rom for an enabled card. This function
+ * should change the config rom that is presented on the bus
+ * and initiate a bus reset.
+ */
+ int (*set_config_rom)(struct fw_card *card,
+ const __be32 *config_rom, size_t length);
+
+ void (*send_request)(struct fw_card *card, struct fw_packet *packet);
+ void (*send_response)(struct fw_card *card, struct fw_packet *packet);
+ /* Calling cancel is valid once a packet has been submitted. */
+ int (*cancel_packet)(struct fw_card *card, struct fw_packet *packet);
+
+ /*
+ * Allow the specified node ID to do direct DMA out and in of
+ * host memory. The card will disable this for all node when
+ * a bus reset happens, so driver need to re-enable this after
+ * bus reset. Returns 0 on success, -ENODEV if the card
+ * doesn't support this, -ESTALE if the generation doesn't
+ * match.
+ */
+ int (*enable_phys_dma)(struct fw_card *card,
+ int node_id, int generation);
+
+ u32 (*read_csr)(struct fw_card *card, int csr_offset);
+ void (*write_csr)(struct fw_card *card, int csr_offset, u32 value);
+
+ struct fw_iso_context *
+ (*allocate_iso_context)(struct fw_card *card, int type, int channel, size_t header_size,
+ size_t header_storage_size);
+ void (*free_iso_context)(struct fw_iso_context *ctx);
+
+ int (*start_iso)(struct fw_iso_context *ctx,
+ s32 cycle, u32 sync, u32 tags);
+
+ int (*set_iso_channels)(struct fw_iso_context *ctx, u64 *channels);
+
+ int (*queue_iso)(struct fw_iso_context *ctx,
+ struct fw_iso_packet *packet,
+ struct fw_iso_buffer *buffer,
+ unsigned long payload);
+
+ void (*flush_queue_iso)(struct fw_iso_context *ctx);
+
+ int (*flush_iso_completions)(struct fw_iso_context *ctx);
+
+ int (*stop_iso)(struct fw_iso_context *ctx);
+};
+
+void fw_card_initialize(struct fw_card *card,
+ const struct fw_card_driver *driver, struct device *device);
+int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid,
+ unsigned int supported_isoc_contexts);
+void fw_core_remove_card(struct fw_card *card);
+int fw_compute_block_crc(__be32 *block);
+void fw_schedule_bm_work(struct fw_card *card, unsigned long delay);
+
+/* -cdev */
+
+extern const struct file_operations fw_device_ops;
+
+void fw_device_cdev_update(struct fw_device *device);
+void fw_device_cdev_remove(struct fw_device *device);
+void fw_cdev_handle_phy_packet(struct fw_card *card, struct fw_packet *p);
+
+
+/* -device */
+
+extern struct rw_semaphore fw_device_rwsem;
+extern struct xarray fw_device_xa;
+extern int fw_cdev_major;
+
+static inline struct fw_device *fw_device_get(struct fw_device *device)
+{
+ get_device(&device->device);
+
+ return device;
+}
+
+static inline void fw_device_put(struct fw_device *device)
+{
+ put_device(&device->device);
+}
+
+struct fw_device *fw_device_get_by_devt(dev_t devt);
+int fw_device_set_broadcast_channel(struct device *dev, void *gen);
+void fw_node_event(struct fw_card *card, struct fw_node *node, int event);
+
+
+/* -iso */
+
+int fw_iso_buffer_alloc(struct fw_iso_buffer *buffer, int page_count);
+int fw_iso_buffer_map_dma(struct fw_iso_buffer *buffer, struct fw_card *card,
+ enum dma_data_direction direction);
+size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed);
+
+static inline void fw_iso_context_init_work(struct fw_iso_context *ctx, work_func_t func)
+{
+ INIT_WORK(&ctx->work, func);
+}
+
+static inline struct fw_iso_context *fw_iso_mc_context_create(struct fw_card *card,
+ fw_iso_mc_callback_t callback, void *callback_data)
+{
+ union fw_iso_callback cb = { .mc = callback };
+
+ return __fw_iso_context_create(card, FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL, 0, 0, 0, 0, cb,
+ callback_data);
+}
+
+
+/* -topology */
+
+// The initial value of BUS_MANAGER_ID register, to express nothing registered.
+#define BUS_MANAGER_ID_NOT_REGISTERED 0x3f
+
+enum {
+ FW_NODE_CREATED,
+ FW_NODE_UPDATED,
+ FW_NODE_DESTROYED,
+ FW_NODE_LINK_ON,
+ FW_NODE_LINK_OFF,
+ FW_NODE_INITIATED_RESET,
+};
+
+struct fw_node {
+ u16 node_id;
+ u8 color;
+ u8 port_count;
+ u8 link_on:1;
+ u8 initiated_reset:1;
+ u8 b_path:1;
+ u8 phy_speed:2; /* As in the self ID packet. */
+ u8 max_speed:2; /* Minimum of all phy-speeds on the path from the
+ * local node to this node. */
+ u8 max_depth:4; /* Maximum depth to any leaf node */
+ u8 max_hops:4; /* Max hops in this sub tree */
+
+ struct kref kref;
+
+ /* For serializing node topology into a list. */
+ struct list_head link;
+
+ // The device when already associated, else NULL.
+ struct fw_device *device;
+
+ struct fw_node *ports[] __counted_by(port_count);
+};
+
+static inline struct fw_node *fw_node_get(struct fw_node *node)
+{
+ kref_get(&node->kref);
+
+ return node;
+}
+
+static void release_node(struct kref *kref)
+{
+ struct fw_node *node = container_of(kref, struct fw_node, kref);
+
+ kfree(node);
+}
+
+static inline void fw_node_put(struct fw_node *node)
+{
+ kref_put(&node->kref, release_node);
+}
+
+static inline struct fw_device *fw_node_get_device(struct fw_node *node)
+{
+ return node->device;
+}
+
+static inline void fw_node_set_device(struct fw_node *node, struct fw_device *device)
+{
+ node->device = device;
+}
+
+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);
+void fw_destroy_nodes(struct fw_card *card);
+
+/*
+ * Check whether new_generation is the immediate successor of old_generation.
+ * Take counter roll-over at 255 (as per OHCI) into account.
+ */
+static inline bool is_next_generation(int new_generation, int old_generation)
+{
+ return (new_generation & 0xff) == ((old_generation + 1) & 0xff);
+}
+
+
+/* -transaction */
+
+#define TCODE_LINK_INTERNAL 0xe
+
+static inline bool tcode_is_read_request(unsigned int tcode)
+{
+ return (tcode & ~1u) == 4u;
+}
+
+static inline bool tcode_is_block_packet(unsigned int tcode)
+{
+ return (tcode & 1u) != 0u;
+}
+
+static inline bool tcode_is_link_internal(unsigned int tcode)
+{
+ return (tcode == TCODE_LINK_INTERNAL);
+}
+
+#define LOCAL_BUS 0xffc0
+
+/* OHCI-1394's default upper bound for physical DMA: 4 GB */
+#define FW_MAX_PHYSICAL_RANGE (1ULL << 32)
+
+void fw_core_handle_request(struct fw_card *card, struct fw_packet *request);
+void fw_core_handle_response(struct fw_card *card, struct fw_packet *packet);
+int fw_get_response_length(struct fw_request *request);
+void fw_fill_response(struct fw_packet *response, u32 *request_header,
+ int rcode, void *payload, size_t length);
+
+void fw_request_get(struct fw_request *request);
+void fw_request_put(struct fw_request *request);
+
+void fw_cancel_pending_transactions(struct fw_card *card);
+
+// Convert the value of IEEE 1394 CYCLE_TIME register to the format of timeStamp field in
+// descriptors of 1394 OHCI.
+static inline u32 cycle_time_to_ohci_tstamp(u32 tstamp)
+{
+ return (tstamp & 0x0ffff000) >> 12;
+}
+
+#define FW_PHY_CONFIG_NO_NODE_ID -1
+#define FW_PHY_CONFIG_CURRENT_GAP_COUNT -1
+void fw_send_phy_config(struct fw_card *card,
+ int node_id, int generation, int gap_count);
+
+static inline bool is_ping_packet(u32 *data)
+{
+ return (data[0] & 0xc0ffffff) == 0 && ~data[0] == data[1];
+}
+
+static inline bool is_in_fcp_region(u64 offset, size_t length)
+{
+ return offset >= (CSR_REGISTER_BASE | CSR_FCP_COMMAND) &&
+ offset + length <= (CSR_REGISTER_BASE | CSR_FCP_END);
+}
+
+#endif /* _FIREWIRE_CORE_H */
diff --git a/drivers/firewire/device-attribute-test.c b/drivers/firewire/device-attribute-test.c
new file mode 100644
index 000000000..97478a96d
--- /dev/null
+++ b/drivers/firewire/device-attribute-test.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// device-attribute-test.c - An application of Kunit to test implementation for device attributes.
+//
+// Copyright (c) 2023 Takashi Sakamoto
+//
+// This file can not be built independently since it is intentionally included in core-device.c.
+
+#include <kunit/test.h>
+
+// Configuration ROM for AV/C Devices 1.0 (Dec. 12, 2000, 1394 Trading Association)
+// Annex C:Configuration ROM example(informative)
+// C.1 Simple AV/C device
+//
+// Copied from the documentation.
+static const u32 simple_avc_config_rom[] = {
+ 0x0404eabf,
+ 0x31333934,
+ 0xe0646102,
+ 0xffffffff,
+ 0xffffffff,
+ 0x00063287, // root directory.
+ 0x03ffffff,
+ 0x8100000a,
+ 0x17ffffff,
+ 0x8100000e,
+ 0x0c0083c0,
+ 0xd1000001,
+ 0x0004442d, // unit 0 directory.
+ 0x1200a02d,
+ 0x13010001,
+ 0x17ffffff,
+ 0x81000007,
+ 0x0005c915, // leaf for textual descriptor.
+ 0x00000000,
+ 0x00000000,
+ 0x56656e64,
+ 0x6f72204e,
+ 0x616d6500,
+ 0x00057f16, // leaf for textual descriptor.
+ 0x00000000,
+ 0x00000000,
+ 0x4d6f6465,
+ 0x6c204e61,
+ 0x6d650000,
+};
+
+// Ibid.
+// Annex A:Consideration for configuration ROM reader design (informative)
+// A.1 Vendor directory
+//
+// Written by hand.
+static const u32 legacy_avc_config_rom[] = {
+ 0x04199fe7,
+ 0x31333934,
+ 0xe0644000,
+ 0x00112233,
+ 0x44556677,
+ 0x0005dace, // root directory.
+ 0x03012345,
+ 0x0c0083c0,
+ 0x8d000009,
+ 0xd1000002,
+ 0xc3000004,
+ 0x0002e107, // unit 0 directory.
+ 0x12abcdef,
+ 0x13543210,
+ 0x0002cb73, // vendor directory.
+ 0x17fedcba,
+ 0x81000004,
+ 0x00026dc1, // leaf for EUI-64.
+ 0x00112233,
+ 0x44556677,
+ 0x00050e84, // leaf for textual descriptor.
+ 0x00000000,
+ 0x00000000,
+ 0x41424344,
+ 0x45464748,
+ 0x494a0000,
+};
+
+static void device_attr_simple_avc(struct kunit *test)
+{
+ static const struct fw_device node = {
+ .device = {
+ .type = &fw_device_type,
+ },
+ .config_rom = simple_avc_config_rom,
+ .config_rom_length = sizeof(simple_avc_config_rom),
+ };
+ static const struct fw_unit unit0 = {
+ .device = {
+ .type = &fw_unit_type,
+ .parent = (struct device *)&node.device,
+ },
+ .directory = &simple_avc_config_rom[12],
+ };
+ struct device *node_dev = (struct device *)&node.device;
+ struct device *unit0_dev = (struct device *)&unit0.device;
+ static const int unit0_expected_ids[] = {0x00ffffff, 0x00ffffff, 0x0000a02d, 0x00010001};
+ char *buf = kunit_kzalloc(test, PAGE_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ int ids[4] = {0, 0, 0, 0};
+
+ // Ensure associations for node and unit devices.
+
+ KUNIT_ASSERT_TRUE(test, is_fw_device(node_dev));
+ KUNIT_ASSERT_FALSE(test, is_fw_unit(node_dev));
+ KUNIT_ASSERT_PTR_EQ(test, fw_device(node_dev), &node);
+
+ KUNIT_ASSERT_FALSE(test, is_fw_device(unit0_dev));
+ KUNIT_ASSERT_TRUE(test, is_fw_unit(unit0_dev));
+ KUNIT_ASSERT_PTR_EQ(test, fw_parent_device((&unit0)), &node);
+ KUNIT_ASSERT_PTR_EQ(test, fw_unit(unit0_dev), &unit0);
+
+ // For entries in root directory.
+
+ // Vendor immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(node_dev, &config_rom_attributes[0].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0xffffff\n");
+
+ // Model immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(node_dev, &config_rom_attributes[4].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0xffffff\n");
+
+ // Descriptor leaf entry for vendor is found.
+ KUNIT_EXPECT_GT(test, show_text_leaf(node_dev, &config_rom_attributes[5].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "Vendor Name\n");
+
+ // Descriptor leaf entry for model is found.
+ KUNIT_EXPECT_GT(test, show_text_leaf(node_dev, &config_rom_attributes[6].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "Model Name\n");
+
+ // For entries in unit 0 directory.
+
+ // Vendor immediate entry is not found.
+ KUNIT_EXPECT_LT(test, show_immediate(unit0_dev, &config_rom_attributes[0].attr, buf), 0);
+
+ // Model immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(unit0_dev, &config_rom_attributes[4].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0xffffff\n");
+
+ // Descriptor leaf entry for vendor is not found.
+ KUNIT_EXPECT_LT(test, show_text_leaf(unit0_dev, &config_rom_attributes[5].attr, buf), 0);
+
+ // Descriptor leaf entry for model is found.
+ KUNIT_EXPECT_GT(test, show_text_leaf(unit0_dev, &config_rom_attributes[6].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "Model Name\n");
+
+ // Specifier_ID immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(unit0_dev, &config_rom_attributes[2].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0x00a02d\n");
+
+ // Version immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(unit0_dev, &config_rom_attributes[3].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0x010001\n");
+
+ kunit_kfree(test, buf);
+
+ get_modalias_ids(&unit0, ids);
+ KUNIT_EXPECT_MEMEQ(test, ids, unit0_expected_ids, sizeof(ids));
+}
+
+static void device_attr_legacy_avc(struct kunit *test)
+{
+ static const struct fw_device node = {
+ .device = {
+ .type = &fw_device_type,
+ },
+ .config_rom = legacy_avc_config_rom,
+ .config_rom_length = sizeof(legacy_avc_config_rom),
+ };
+ static const struct fw_unit unit0 = {
+ .device = {
+ .type = &fw_unit_type,
+ .parent = (struct device *)&node.device,
+ },
+ .directory = &legacy_avc_config_rom[11],
+ };
+ struct device *node_dev = (struct device *)&node.device;
+ struct device *unit0_dev = (struct device *)&unit0.device;
+ static const int unit0_expected_ids[] = {0x00012345, 0x00fedcba, 0x00abcdef, 0x00543210};
+ char *buf = kunit_kzalloc(test, PAGE_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ int ids[4] = {0, 0, 0, 0};
+
+ // Ensure associations for node and unit devices.
+
+ KUNIT_ASSERT_TRUE(test, is_fw_device(node_dev));
+ KUNIT_ASSERT_FALSE(test, is_fw_unit(node_dev));
+ KUNIT_ASSERT_PTR_EQ(test, fw_device((node_dev)), &node);
+
+ KUNIT_ASSERT_FALSE(test, is_fw_device(unit0_dev));
+ KUNIT_ASSERT_TRUE(test, is_fw_unit(unit0_dev));
+ KUNIT_ASSERT_PTR_EQ(test, fw_parent_device((&unit0)), &node);
+ KUNIT_ASSERT_PTR_EQ(test, fw_unit(unit0_dev), &unit0);
+
+ // For entries in root directory.
+
+ // Vendor immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(node_dev, &config_rom_attributes[0].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0x012345\n");
+
+ // Model immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(node_dev, &config_rom_attributes[4].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0xfedcba\n");
+
+ // Descriptor leaf entry for vendor is not found.
+ KUNIT_EXPECT_LT(test, show_text_leaf(node_dev, &config_rom_attributes[5].attr, buf), 0);
+
+ // Descriptor leaf entry for model is found.
+ KUNIT_EXPECT_GT(test, show_text_leaf(node_dev, &config_rom_attributes[6].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "ABCDEFGHIJ\n");
+
+ // For entries in unit 0 directory.
+
+ // Vendor immediate entry is not found.
+ KUNIT_EXPECT_LT(test, show_immediate(unit0_dev, &config_rom_attributes[0].attr, buf), 0);
+
+ // Model immediate entry is not found.
+ KUNIT_EXPECT_LT(test, show_immediate(unit0_dev, &config_rom_attributes[4].attr, buf), 0);
+
+ // Descriptor leaf entry for vendor is not found.
+ KUNIT_EXPECT_LT(test, show_text_leaf(unit0_dev, &config_rom_attributes[5].attr, buf), 0);
+
+ // Descriptor leaf entry for model is not found.
+ KUNIT_EXPECT_LT(test, show_text_leaf(unit0_dev, &config_rom_attributes[6].attr, buf), 0);
+
+ // Specifier_ID immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(unit0_dev, &config_rom_attributes[2].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0xabcdef\n");
+
+ // Version immediate entry is found.
+ KUNIT_EXPECT_GT(test, show_immediate(unit0_dev, &config_rom_attributes[3].attr, buf), 0);
+ KUNIT_EXPECT_STREQ(test, buf, "0x543210\n");
+
+ kunit_kfree(test, buf);
+
+ get_modalias_ids(&unit0, ids);
+ KUNIT_EXPECT_MEMEQ(test, ids, unit0_expected_ids, sizeof(ids));
+}
+
+static struct kunit_case device_attr_test_cases[] = {
+ KUNIT_CASE(device_attr_simple_avc),
+ KUNIT_CASE(device_attr_legacy_avc),
+ {}
+};
+
+static struct kunit_suite device_attr_test_suite = {
+ .name = "firewire-device-attribute",
+ .test_cases = device_attr_test_cases,
+};
+kunit_test_suite(device_attr_test_suite);
diff --git a/drivers/firewire/init_ohci1394_dma.c b/drivers/firewire/init_ohci1394_dma.c
new file mode 100644
index 000000000..121f0c2f6
--- /dev/null
+++ b/drivers/firewire/init_ohci1394_dma.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * init_ohci1394_dma.c - Initializes physical DMA on all OHCI 1394 controllers
+ *
+ * Copyright (C) 2006-2007 Bernhard Kaindl <bk@suse.de>
+ *
+ * Derived from drivers/ieee1394/ohci1394.c and arch/x86/kernel/early-quirks.c
+ * this file has functions to:
+ * - scan the PCI very early on boot for all OHCI 1394-compliant controllers
+ * - reset and initialize them and make them join the IEEE1394 bus and
+ * - enable physical DMA on them to allow remote debugging
+ *
+ * All code and data is marked as __init and __initdata, respective as
+ * during boot, all OHCI1394 controllers may be claimed by the firewire
+ * stack and at this point, this code should not touch them anymore.
+ *
+ * To use physical DMA after the initialization of the firewire stack,
+ * be sure that the stack enables it and (re-)attach after the bus reset
+ * which may be caused by the firewire stack initialization.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/pci.h> /* for PCI defines */
+#include <linux/string.h>
+
+#include <asm/pci-direct.h> /* for direct PCI config space access */
+#include <asm/fixmap.h>
+
+#include <linux/init_ohci1394_dma.h>
+#include "ohci.h"
+
+int __initdata init_ohci1394_dma_early;
+
+struct ohci {
+ void __iomem *registers;
+};
+
+static inline void reg_write(const struct ohci *ohci, int offset, u32 data)
+{
+ writel(data, ohci->registers + offset);
+}
+
+static inline u32 reg_read(const struct ohci *ohci, int offset)
+{
+ return readl(ohci->registers + offset);
+}
+
+#define OHCI_LOOP_COUNT 100 /* Number of loops for reg read waits */
+
+/* Reads a PHY register of an OHCI-1394 controller */
+static inline u8 __init get_phy_reg(struct ohci *ohci, u8 addr)
+{
+ int i;
+ u32 r;
+
+ reg_write(ohci, OHCI1394_PhyControl, (addr << 8) | 0x00008000);
+
+ for (i = 0; i < OHCI_LOOP_COUNT; i++) {
+ if (reg_read(ohci, OHCI1394_PhyControl) & 0x80000000)
+ break;
+ mdelay(1);
+ }
+ r = reg_read(ohci, OHCI1394_PhyControl);
+
+ return (r & 0x00ff0000) >> 16;
+}
+
+/* Writes to a PHY register of an OHCI-1394 controller */
+static inline void __init set_phy_reg(struct ohci *ohci, u8 addr, u8 data)
+{
+ int i;
+
+ reg_write(ohci, OHCI1394_PhyControl, (addr << 8) | data | 0x00004000);
+
+ for (i = 0; i < OHCI_LOOP_COUNT; i++) {
+ if (!(reg_read(ohci, OHCI1394_PhyControl) & 0x00004000))
+ break;
+ mdelay(1);
+ }
+}
+
+/* Resets an OHCI-1394 controller (for sane state before initialization) */
+static inline void __init init_ohci1394_soft_reset(struct ohci *ohci)
+{
+ int i;
+
+ reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_softReset);
+
+ for (i = 0; i < OHCI_LOOP_COUNT; i++) {
+ if (!(reg_read(ohci, OHCI1394_HCControlSet)
+ & OHCI1394_HCControl_softReset))
+ break;
+ mdelay(1);
+ }
+}
+
+#define OHCI1394_MAX_AT_REQ_RETRIES 0xf
+#define OHCI1394_MAX_AT_RESP_RETRIES 0x2
+#define OHCI1394_MAX_PHYS_RESP_RETRIES 0x8
+
+/* Basic OHCI-1394 register and port inititalization */
+static inline void __init init_ohci1394_initialize(struct ohci *ohci)
+{
+ u32 bus_options;
+ int num_ports, i;
+
+ /* Put some defaults to these undefined bus options */
+ bus_options = reg_read(ohci, OHCI1394_BusOptions);
+ bus_options |= 0x60000000; /* Enable CMC and ISC */
+ bus_options &= ~0x00ff0000; /* XXX: Set cyc_clk_acc to zero for now */
+ bus_options &= ~0x18000000; /* Disable PMC and BMC */
+ reg_write(ohci, OHCI1394_BusOptions, bus_options);
+
+ /* Set the bus number */
+ reg_write(ohci, OHCI1394_NodeID, 0x0000ffc0);
+
+ /* Enable posted writes */
+ reg_write(ohci, OHCI1394_HCControlSet,
+ OHCI1394_HCControl_postedWriteEnable);
+
+ /* Clear link control register */
+ reg_write(ohci, OHCI1394_LinkControlClear, 0xffffffff);
+
+ /* enable phys */
+ reg_write(ohci, OHCI1394_LinkControlSet,
+ OHCI1394_LinkControl_rcvPhyPkt);
+
+ /* Don't accept phy packets into AR request context */
+ reg_write(ohci, OHCI1394_LinkControlClear, 0x00000400);
+
+ /* Clear the Isochonouys interrupt masks */
+ reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 0xffffffff);
+ reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 0xffffffff);
+ reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 0xffffffff);
+ reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 0xffffffff);
+
+ /* Accept asynchronous transfer requests from all nodes for now */
+ reg_write(ohci, OHCI1394_AsReqFilterHiSet, 0x80000000);
+
+ /* Specify asynchronous transfer retries */
+ reg_write(ohci, OHCI1394_ATRetries,
+ OHCI1394_MAX_AT_REQ_RETRIES |
+ (OHCI1394_MAX_AT_RESP_RETRIES<<4) |
+ (OHCI1394_MAX_PHYS_RESP_RETRIES<<8));
+
+ /* We don't want hardware swapping */
+ reg_write(ohci, OHCI1394_HCControlClear,
+ OHCI1394_HCControl_noByteSwapData);
+
+ /* Enable link */
+ reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_linkEnable);
+
+ /* If anything is connected to a port, make sure it is enabled */
+ num_ports = get_phy_reg(ohci, 2) & 0xf;
+ for (i = 0; i < num_ports; i++) {
+ unsigned int status;
+
+ set_phy_reg(ohci, 7, i);
+ status = get_phy_reg(ohci, 8);
+
+ if (status & 0x20)
+ set_phy_reg(ohci, 8, status & ~1);
+ }
+}
+
+/**
+ * init_ohci1394_wait_for_busresets - wait until bus resets are completed
+ * @ohci: Pointer to the OHCI-1394 controller structure
+ *
+ * OHCI1394 initialization itself and any device going on- or offline
+ * and any cable issue cause a IEEE1394 bus reset. The OHCI1394 spec
+ * specifies that physical DMA is disabled on each bus reset and it
+ * has to be enabled after each bus reset when needed. We resort
+ * to polling here because on early boot, we have no interrupts.
+ */
+static inline void __init init_ohci1394_wait_for_busresets(struct ohci *ohci)
+{
+ int i, events;
+
+ for (i = 0; i < 9; i++) {
+ mdelay(200);
+ events = reg_read(ohci, OHCI1394_IntEventSet);
+ if (events & OHCI1394_busReset)
+ reg_write(ohci, OHCI1394_IntEventClear,
+ OHCI1394_busReset);
+ }
+}
+
+/**
+ * init_ohci1394_enable_physical_dma - Enable physical DMA for remote debugging
+ * @ohci: Pointer to the OHCI-1394 controller structure
+ *
+ * This enables remote DMA access over IEEE1394 from every host for the low
+ * 4GB of address space. DMA accesses above 4GB are not available currently.
+ */
+static inline void __init init_ohci1394_enable_physical_dma(struct ohci *ohci)
+{
+ reg_write(ohci, OHCI1394_PhyReqFilterHiSet, 0xffffffff);
+ reg_write(ohci, OHCI1394_PhyReqFilterLoSet, 0xffffffff);
+ reg_write(ohci, OHCI1394_PhyUpperBound, 0xffff0000);
+}
+
+/**
+ * init_ohci1394_reset_and_init_dma - init controller and enable DMA
+ * @ohci: Pointer to the OHCI-1394 controller structure
+ *
+ * This initializes the given controller and enables physical DMA engine in it.
+ */
+static inline void __init init_ohci1394_reset_and_init_dma(struct ohci *ohci)
+{
+ /* Start off with a soft reset, clears everything to a sane state. */
+ init_ohci1394_soft_reset(ohci);
+
+ /* Accessing some registers without LPS enabled may cause lock up */
+ reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_LPS);
+
+ /* Disable and clear interrupts */
+ reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff);
+ reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff);
+
+ mdelay(50); /* Wait 50msec to make sure we have full link enabled */
+
+ init_ohci1394_initialize(ohci);
+ /*
+ * The initialization causes at least one IEEE1394 bus reset. Enabling
+ * physical DMA only works *after* *all* bus resets have calmed down:
+ */
+ init_ohci1394_wait_for_busresets(ohci);
+
+ /* We had to wait and do this now if we want to debug early problems */
+ init_ohci1394_enable_physical_dma(ohci);
+}
+
+/**
+ * init_ohci1394_controller - Map the registers of the controller and init DMA
+ * @num: PCI bus number
+ * @slot: PCI device number
+ * @func: PCI function number
+ *
+ * This maps the registers of the specified controller and initializes it
+ */
+static inline void __init init_ohci1394_controller(int num, int slot, int func)
+{
+ unsigned long ohci_base;
+ struct ohci ohci;
+
+ printk(KERN_INFO "init_ohci1394_dma: initializing OHCI-1394"
+ " at %02x:%02x.%x\n", num, slot, func);
+
+ ohci_base = read_pci_config(num, slot, func, PCI_BASE_ADDRESS_0+(0<<2))
+ & PCI_BASE_ADDRESS_MEM_MASK;
+
+ set_fixmap_nocache(FIX_OHCI1394_BASE, ohci_base);
+
+ ohci.registers = (void __iomem *)fix_to_virt(FIX_OHCI1394_BASE);
+
+ init_ohci1394_reset_and_init_dma(&ohci);
+}
+
+/**
+ * init_ohci1394_dma_on_all_controllers - scan for OHCI1394 controllers and init DMA on them
+ * Scans the whole PCI space for OHCI1394 controllers and inits DMA on them
+ */
+void __init init_ohci1394_dma_on_all_controllers(void)
+{
+ int num, slot, func;
+ u32 class;
+
+ if (!early_pci_allowed())
+ return;
+
+ /* Poor man's PCI discovery, the only thing we can do at early boot */
+ for (num = 0; num < 32; num++) {
+ for (slot = 0; slot < 32; slot++) {
+ for (func = 0; func < 8; func++) {
+ class = read_pci_config(num, slot, func,
+ PCI_CLASS_REVISION);
+ if (class == 0xffffffff)
+ continue; /* No device at this func */
+
+ if (class>>8 != PCI_CLASS_SERIAL_FIREWIRE_OHCI)
+ continue; /* Not an OHCI-1394 device */
+
+ init_ohci1394_controller(num, slot, func);
+ break; /* Assume one controller per device */
+ }
+ }
+ }
+ printk(KERN_INFO "init_ohci1394_dma: finished initializing OHCI DMA\n");
+}
+
+/**
+ * setup_ohci1394_dma - enables early OHCI1394 DMA initialization
+ * @opt: Kernel command line parameter string
+ */
+static int __init setup_ohci1394_dma(char *opt)
+{
+ if (!strcmp(opt, "early"))
+ init_ohci1394_dma_early = 1;
+ return 0;
+}
+
+/* passing ohci1394_dma=early on boot causes early OHCI1394 DMA initialization */
+early_param("ohci1394_dma", setup_ohci1394_dma);
diff --git a/drivers/firewire/net.c b/drivers/firewire/net.c
new file mode 100644
index 000000000..961f1a046
--- /dev/null
+++ b/drivers/firewire/net.c
@@ -0,0 +1,1711 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IPv4 over IEEE 1394, per RFC 2734
+ * IPv6 over IEEE 1394, per RFC 3146
+ *
+ * Copyright (C) 2009 Jay Fenlason <fenlason@redhat.com>
+ *
+ * based on eth1394 by Ben Collins et al
+ */
+
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/ethtool.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/highmem.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/unaligned.h>
+#include <net/arp.h>
+#include <net/firewire.h>
+
+/* rx limits */
+#define FWNET_MAX_FRAGMENTS 30 /* arbitrary, > TX queue depth */
+#define FWNET_ISO_PAGE_COUNT (PAGE_SIZE < 16*1024 ? 4 : 2)
+
+/* tx limits */
+#define FWNET_MAX_QUEUED_DATAGRAMS 20 /* < 64 = number of tlabels */
+#define FWNET_MIN_QUEUED_DATAGRAMS 10 /* should keep AT DMA busy enough */
+#define FWNET_TX_QUEUE_LEN FWNET_MAX_QUEUED_DATAGRAMS /* ? */
+
+#define IEEE1394_BROADCAST_CHANNEL 31
+#define IEEE1394_ALL_NODES (0xffc0 | 0x003f)
+#define IEEE1394_MAX_PAYLOAD_S100 512
+#define FWNET_NO_FIFO_ADDR (~0ULL)
+
+#define IANA_SPECIFIER_ID 0x00005eU
+#define RFC2734_SW_VERSION 0x000001U
+#define RFC3146_SW_VERSION 0x000002U
+
+#define IEEE1394_GASP_HDR_SIZE 8
+
+#define RFC2374_UNFRAG_HDR_SIZE 4
+#define RFC2374_FRAG_HDR_SIZE 8
+#define RFC2374_FRAG_OVERHEAD 4
+
+#define RFC2374_HDR_UNFRAG 0 /* unfragmented */
+#define RFC2374_HDR_FIRSTFRAG 1 /* first fragment */
+#define RFC2374_HDR_LASTFRAG 2 /* last fragment */
+#define RFC2374_HDR_INTFRAG 3 /* interior fragment */
+
+static bool fwnet_hwaddr_is_multicast(u8 *ha)
+{
+ return !!(*ha & 1);
+}
+
+/* IPv4 and IPv6 encapsulation header */
+struct rfc2734_header {
+ u32 w0;
+ u32 w1;
+};
+
+#define fwnet_get_hdr_lf(h) (((h)->w0 & 0xc0000000) >> 30)
+#define fwnet_get_hdr_ether_type(h) (((h)->w0 & 0x0000ffff))
+#define fwnet_get_hdr_dg_size(h) ((((h)->w0 & 0x0fff0000) >> 16) + 1)
+#define fwnet_get_hdr_fg_off(h) (((h)->w0 & 0x00000fff))
+#define fwnet_get_hdr_dgl(h) (((h)->w1 & 0xffff0000) >> 16)
+
+#define fwnet_set_hdr_lf(lf) ((lf) << 30)
+#define fwnet_set_hdr_ether_type(et) (et)
+#define fwnet_set_hdr_dg_size(dgs) (((dgs) - 1) << 16)
+#define fwnet_set_hdr_fg_off(fgo) (fgo)
+
+#define fwnet_set_hdr_dgl(dgl) ((dgl) << 16)
+
+static inline void fwnet_make_uf_hdr(struct rfc2734_header *hdr,
+ unsigned ether_type)
+{
+ hdr->w0 = fwnet_set_hdr_lf(RFC2374_HDR_UNFRAG)
+ | fwnet_set_hdr_ether_type(ether_type);
+}
+
+static inline void fwnet_make_ff_hdr(struct rfc2734_header *hdr,
+ unsigned ether_type, unsigned dg_size, unsigned dgl)
+{
+ hdr->w0 = fwnet_set_hdr_lf(RFC2374_HDR_FIRSTFRAG)
+ | fwnet_set_hdr_dg_size(dg_size)
+ | fwnet_set_hdr_ether_type(ether_type);
+ hdr->w1 = fwnet_set_hdr_dgl(dgl);
+}
+
+static inline void fwnet_make_sf_hdr(struct rfc2734_header *hdr,
+ unsigned lf, unsigned dg_size, unsigned fg_off, unsigned dgl)
+{
+ hdr->w0 = fwnet_set_hdr_lf(lf)
+ | fwnet_set_hdr_dg_size(dg_size)
+ | fwnet_set_hdr_fg_off(fg_off);
+ hdr->w1 = fwnet_set_hdr_dgl(dgl);
+}
+
+/* This list keeps track of what parts of the datagram have been filled in */
+struct fwnet_fragment_info {
+ struct list_head fi_link;
+ u16 offset;
+ u16 len;
+};
+
+struct fwnet_partial_datagram {
+ struct list_head pd_link;
+ struct list_head fi_list;
+ struct sk_buff *skb;
+ /* FIXME Why not use skb->data? */
+ char *pbuf;
+ u16 datagram_label;
+ u16 ether_type;
+ u16 datagram_size;
+};
+
+static DEFINE_MUTEX(fwnet_device_mutex);
+static LIST_HEAD(fwnet_device_list);
+
+struct fwnet_device {
+ struct list_head dev_link;
+ spinlock_t lock;
+ enum {
+ FWNET_BROADCAST_ERROR,
+ FWNET_BROADCAST_RUNNING,
+ FWNET_BROADCAST_STOPPED,
+ } broadcast_state;
+ struct fw_iso_context *broadcast_rcv_context;
+ struct fw_iso_buffer broadcast_rcv_buffer;
+ void **broadcast_rcv_buffer_ptrs;
+ unsigned broadcast_rcv_next_ptr;
+ unsigned num_broadcast_rcv_ptrs;
+ unsigned rcv_buffer_size;
+ /*
+ * This value is the maximum unfragmented datagram size that can be
+ * sent by the hardware. It already has the GASP overhead and the
+ * unfragmented datagram header overhead calculated into it.
+ */
+ unsigned broadcast_xmt_max_payload;
+ u16 broadcast_xmt_datagramlabel;
+
+ /*
+ * The CSR address that remote nodes must send datagrams to for us to
+ * receive them.
+ */
+ struct fw_address_handler handler;
+ u64 local_fifo;
+
+ /* Number of tx datagrams that have been queued but not yet acked */
+ int queued_datagrams;
+
+ int peer_count;
+ struct list_head peer_list;
+ struct fw_card *card;
+ struct net_device *netdev;
+};
+
+struct fwnet_peer {
+ struct list_head peer_link;
+ struct fwnet_device *dev;
+ u64 guid;
+
+ /* guarded by dev->lock */
+ struct list_head pd_list; /* received partial datagrams */
+ unsigned pdg_size; /* pd_list size */
+
+ u16 datagram_label; /* outgoing datagram label */
+ u16 max_payload; /* includes RFC2374_FRAG_HDR_SIZE overhead */
+ int node_id;
+ int generation;
+ unsigned speed;
+};
+
+/* This is our task struct. It's used for the packet complete callback. */
+struct fwnet_packet_task {
+ struct fw_transaction transaction;
+ struct rfc2734_header hdr;
+ struct sk_buff *skb;
+ struct fwnet_device *dev;
+
+ int outstanding_pkts;
+ u64 fifo_addr;
+ u16 dest_node;
+ u16 max_payload;
+ u8 generation;
+ u8 speed;
+ u8 enqueued;
+};
+
+/*
+ * saddr == NULL means use device source address.
+ * daddr == NULL means leave destination address (eg unresolved arp).
+ */
+static int fwnet_header_create(struct sk_buff *skb, struct net_device *net,
+ unsigned short type, const void *daddr,
+ const void *saddr, unsigned len)
+{
+ struct fwnet_header *h;
+
+ h = skb_push(skb, sizeof(*h));
+ put_unaligned_be16(type, &h->h_proto);
+
+ if (net->flags & (IFF_LOOPBACK | IFF_NOARP)) {
+ memset(h->h_dest, 0, net->addr_len);
+
+ return net->hard_header_len;
+ }
+
+ if (daddr) {
+ memcpy(h->h_dest, daddr, net->addr_len);
+
+ return net->hard_header_len;
+ }
+
+ return -net->hard_header_len;
+}
+
+static int fwnet_header_cache(const struct neighbour *neigh,
+ struct hh_cache *hh, __be16 type)
+{
+ struct net_device *net;
+ struct fwnet_header *h;
+
+ if (type == cpu_to_be16(ETH_P_802_3))
+ return -1;
+ net = neigh->dev;
+ h = (struct fwnet_header *)((u8 *)hh->hh_data + HH_DATA_OFF(sizeof(*h)));
+ h->h_proto = type;
+ memcpy(h->h_dest, neigh->ha, net->addr_len);
+
+ /* Pairs with the READ_ONCE() in neigh_resolve_output(),
+ * neigh_hh_output() and neigh_update_hhs().
+ */
+ smp_store_release(&hh->hh_len, FWNET_HLEN);
+
+ return 0;
+}
+
+/* Called by Address Resolution module to notify changes in address. */
+static void fwnet_header_cache_update(struct hh_cache *hh,
+ const struct net_device *net, const unsigned char *haddr)
+{
+ memcpy((u8 *)hh->hh_data + HH_DATA_OFF(FWNET_HLEN), haddr, net->addr_len);
+}
+
+static int fwnet_header_parse(const struct sk_buff *skb, const struct net_device *dev,
+ unsigned char *haddr)
+{
+ memcpy(haddr, dev->dev_addr, FWNET_ALEN);
+
+ return FWNET_ALEN;
+}
+
+static const struct header_ops fwnet_header_ops = {
+ .create = fwnet_header_create,
+ .cache = fwnet_header_cache,
+ .cache_update = fwnet_header_cache_update,
+ .parse = fwnet_header_parse,
+};
+
+/* FIXME: is this correct for all cases? */
+static bool fwnet_frag_overlap(struct fwnet_partial_datagram *pd,
+ unsigned offset, unsigned len)
+{
+ struct fwnet_fragment_info *fi;
+ unsigned end = offset + len;
+
+ list_for_each_entry(fi, &pd->fi_list, fi_link)
+ if (offset < fi->offset + fi->len && end > fi->offset)
+ return true;
+
+ return false;
+}
+
+/* Assumes that new fragment does not overlap any existing fragments */
+static struct fwnet_fragment_info *fwnet_frag_new(
+ struct fwnet_partial_datagram *pd, unsigned offset, unsigned len)
+{
+ struct fwnet_fragment_info *fi, *fi2, *new;
+ struct list_head *list;
+
+ list = &pd->fi_list;
+ list_for_each_entry(fi, &pd->fi_list, fi_link) {
+ if (fi->offset + fi->len == offset) {
+ /* The new fragment can be tacked on to the end */
+ /* Did the new fragment plug a hole? */
+ if (!list_is_last(&fi->fi_link, &pd->fi_list)) {
+ fi2 = list_next_entry(fi, fi_link);
+ if (offset + len == fi2->offset) {
+ /* glue fragments together */
+ fi->len += len + fi2->len;
+ list_del(&fi2->fi_link);
+ kfree(fi2);
+
+ return fi;
+ }
+ }
+ fi->len += len;
+
+ return fi;
+ }
+ if (offset + len == fi->offset) {
+ /* The new fragment can be tacked on to the beginning */
+ /* Did the new fragment plug a hole? */
+ if (!list_is_first(&fi->fi_link, &pd->fi_list)) {
+ fi2 = list_prev_entry(fi, fi_link);
+ if (fi2->offset + fi2->len == offset) {
+ /* glue fragments together */
+ fi2->len += fi->len + len;
+ list_del(&fi->fi_link);
+ kfree(fi);
+
+ return fi2;
+ }
+ }
+ fi->offset = offset;
+ fi->len += len;
+
+ return fi;
+ }
+ if (offset > fi->offset + fi->len) {
+ list = &fi->fi_link;
+ break;
+ }
+ if (offset + len < fi->offset) {
+ list = fi->fi_link.prev;
+ break;
+ }
+ }
+
+ new = kmalloc_obj(*new, GFP_ATOMIC);
+ if (!new)
+ return NULL;
+
+ new->offset = offset;
+ new->len = len;
+ list_add(&new->fi_link, list);
+
+ return new;
+}
+
+static struct fwnet_partial_datagram *fwnet_pd_new(struct net_device *net,
+ struct fwnet_peer *peer, u16 datagram_label, unsigned dg_size,
+ void *frag_buf, unsigned frag_off, unsigned frag_len)
+{
+ struct fwnet_partial_datagram *new;
+ struct fwnet_fragment_info *fi;
+
+ new = kmalloc_obj(*new, GFP_ATOMIC);
+ if (!new)
+ goto fail;
+
+ INIT_LIST_HEAD(&new->fi_list);
+ fi = fwnet_frag_new(new, frag_off, frag_len);
+ if (fi == NULL)
+ goto fail_w_new;
+
+ new->datagram_label = datagram_label;
+ new->datagram_size = dg_size;
+ new->skb = dev_alloc_skb(dg_size + LL_RESERVED_SPACE(net));
+ if (new->skb == NULL)
+ goto fail_w_fi;
+
+ skb_reserve(new->skb, LL_RESERVED_SPACE(net));
+ new->pbuf = skb_put(new->skb, dg_size);
+ memcpy(new->pbuf + frag_off, frag_buf, frag_len);
+ list_add_tail(&new->pd_link, &peer->pd_list);
+
+ return new;
+
+fail_w_fi:
+ kfree(fi);
+fail_w_new:
+ kfree(new);
+fail:
+ return NULL;
+}
+
+static struct fwnet_partial_datagram *fwnet_pd_find(struct fwnet_peer *peer,
+ u16 datagram_label)
+{
+ struct fwnet_partial_datagram *pd;
+
+ list_for_each_entry(pd, &peer->pd_list, pd_link)
+ if (pd->datagram_label == datagram_label)
+ return pd;
+
+ return NULL;
+}
+
+
+static void fwnet_pd_delete(struct fwnet_partial_datagram *old)
+{
+ struct fwnet_fragment_info *fi, *n;
+
+ list_for_each_entry_safe(fi, n, &old->fi_list, fi_link)
+ kfree(fi);
+
+ list_del(&old->pd_link);
+ dev_kfree_skb_any(old->skb);
+ kfree(old);
+}
+
+static bool fwnet_pd_update(struct fwnet_peer *peer,
+ struct fwnet_partial_datagram *pd, void *frag_buf,
+ unsigned frag_off, unsigned frag_len)
+{
+ if (fwnet_frag_new(pd, frag_off, frag_len) == NULL)
+ return false;
+
+ memcpy(pd->pbuf + frag_off, frag_buf, frag_len);
+
+ /*
+ * Move list entry to beginning of list so that oldest partial
+ * datagrams percolate to the end of the list
+ */
+ list_move_tail(&pd->pd_link, &peer->pd_list);
+
+ return true;
+}
+
+static bool fwnet_pd_is_complete(struct fwnet_partial_datagram *pd)
+{
+ struct fwnet_fragment_info *fi;
+
+ fi = list_entry(pd->fi_list.next, struct fwnet_fragment_info, fi_link);
+
+ return fi->len == pd->datagram_size;
+}
+
+/* caller must hold dev->lock */
+static struct fwnet_peer *fwnet_peer_find_by_guid(struct fwnet_device *dev,
+ u64 guid)
+{
+ struct fwnet_peer *peer;
+
+ list_for_each_entry(peer, &dev->peer_list, peer_link)
+ if (peer->guid == guid)
+ return peer;
+
+ return NULL;
+}
+
+/* caller must hold dev->lock */
+static struct fwnet_peer *fwnet_peer_find_by_node_id(struct fwnet_device *dev,
+ int node_id, int generation)
+{
+ struct fwnet_peer *peer;
+
+ list_for_each_entry(peer, &dev->peer_list, peer_link)
+ if (peer->node_id == node_id &&
+ peer->generation == generation)
+ return peer;
+
+ return NULL;
+}
+
+/* See IEEE 1394-2008 table 6-4, table 8-8, table 16-18. */
+static unsigned fwnet_max_payload(unsigned max_rec, unsigned speed)
+{
+ max_rec = min(max_rec, speed + 8);
+ max_rec = clamp(max_rec, 8U, 11U); /* 512...4096 */
+
+ return (1 << (max_rec + 1)) - RFC2374_FRAG_HDR_SIZE;
+}
+
+
+static int fwnet_finish_incoming_packet(struct net_device *net,
+ struct sk_buff *skb, u16 source_node_id,
+ bool is_broadcast, u16 ether_type)
+{
+ int status, len;
+
+ switch (ether_type) {
+ case ETH_P_ARP:
+ case ETH_P_IP:
+#if IS_ENABLED(CONFIG_IPV6)
+ case ETH_P_IPV6:
+#endif
+ break;
+ default:
+ goto err;
+ }
+
+ /* Write metadata, and then pass to the receive level */
+ skb->dev = net;
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /*
+ * Parse the encapsulation header. This actually does the job of
+ * converting to an ethernet-like pseudo frame header.
+ */
+ if (dev_hard_header(skb, net, ether_type,
+ is_broadcast ? net->broadcast : net->dev_addr,
+ NULL, skb->len) >= 0) {
+ struct fwnet_header *eth;
+ u16 *rawp;
+ __be16 protocol;
+
+ skb_reset_mac_header(skb);
+ skb_pull(skb, sizeof(*eth));
+ eth = (struct fwnet_header *)skb_mac_header(skb);
+ if (fwnet_hwaddr_is_multicast(eth->h_dest)) {
+ if (memcmp(eth->h_dest, net->broadcast,
+ net->addr_len) == 0)
+ skb->pkt_type = PACKET_BROADCAST;
+#if 0
+ else
+ skb->pkt_type = PACKET_MULTICAST;
+#endif
+ } else {
+ if (memcmp(eth->h_dest, net->dev_addr, net->addr_len))
+ skb->pkt_type = PACKET_OTHERHOST;
+ }
+ if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) {
+ protocol = eth->h_proto;
+ } else {
+ rawp = (u16 *)skb->data;
+ if (*rawp == 0xffff)
+ protocol = htons(ETH_P_802_3);
+ else
+ protocol = htons(ETH_P_802_2);
+ }
+ skb->protocol = protocol;
+ }
+
+ len = skb->len;
+ status = netif_rx(skb);
+ if (status == NET_RX_DROP) {
+ net->stats.rx_errors++;
+ net->stats.rx_dropped++;
+ } else {
+ net->stats.rx_packets++;
+ net->stats.rx_bytes += len;
+ }
+
+ return 0;
+
+ err:
+ net->stats.rx_errors++;
+ net->stats.rx_dropped++;
+
+ dev_kfree_skb_any(skb);
+
+ return -ENOENT;
+}
+
+static int fwnet_incoming_packet(struct fwnet_device *dev, __be32 *buf, int len,
+ int source_node_id, int generation,
+ bool is_broadcast)
+{
+ struct sk_buff *skb;
+ struct net_device *net = dev->netdev;
+ struct rfc2734_header hdr;
+ unsigned lf;
+ unsigned long flags;
+ struct fwnet_peer *peer;
+ struct fwnet_partial_datagram *pd;
+ int fg_off;
+ int dg_size;
+ u16 datagram_label;
+ int retval;
+ u16 ether_type;
+
+ if (len <= RFC2374_UNFRAG_HDR_SIZE)
+ return 0;
+
+ hdr.w0 = be32_to_cpu(buf[0]);
+ lf = fwnet_get_hdr_lf(&hdr);
+ if (lf == RFC2374_HDR_UNFRAG) {
+ /*
+ * An unfragmented datagram has been received by the ieee1394
+ * bus. Build an skbuff around it so we can pass it to the
+ * high level network layer.
+ */
+ ether_type = fwnet_get_hdr_ether_type(&hdr);
+ buf++;
+ len -= RFC2374_UNFRAG_HDR_SIZE;
+
+ skb = dev_alloc_skb(len + LL_RESERVED_SPACE(net));
+ if (unlikely(!skb)) {
+ net->stats.rx_dropped++;
+
+ return -ENOMEM;
+ }
+ skb_reserve(skb, LL_RESERVED_SPACE(net));
+ skb_put_data(skb, buf, len);
+
+ return fwnet_finish_incoming_packet(net, skb, source_node_id,
+ is_broadcast, ether_type);
+ }
+
+ /* A datagram fragment has been received, now the fun begins. */
+
+ if (len <= RFC2374_FRAG_HDR_SIZE)
+ return 0;
+
+ hdr.w1 = ntohl(buf[1]);
+ buf += 2;
+ len -= RFC2374_FRAG_HDR_SIZE;
+ if (lf == RFC2374_HDR_FIRSTFRAG) {
+ ether_type = fwnet_get_hdr_ether_type(&hdr);
+ fg_off = 0;
+ } else {
+ ether_type = 0;
+ fg_off = fwnet_get_hdr_fg_off(&hdr);
+ }
+ datagram_label = fwnet_get_hdr_dgl(&hdr);
+ dg_size = fwnet_get_hdr_dg_size(&hdr);
+
+ if (fg_off + len > dg_size)
+ return 0;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ peer = fwnet_peer_find_by_node_id(dev, source_node_id, generation);
+ if (!peer) {
+ retval = -ENOENT;
+ goto fail;
+ }
+
+ pd = fwnet_pd_find(peer, datagram_label);
+ if (pd == NULL) {
+ while (peer->pdg_size >= FWNET_MAX_FRAGMENTS) {
+ /* remove the oldest */
+ fwnet_pd_delete(list_first_entry(&peer->pd_list,
+ struct fwnet_partial_datagram, pd_link));
+ peer->pdg_size--;
+ }
+ pd = fwnet_pd_new(net, peer, datagram_label,
+ dg_size, buf, fg_off, len);
+ if (pd == NULL) {
+ retval = -ENOMEM;
+ goto fail;
+ }
+ peer->pdg_size++;
+ } else {
+ if (fwnet_frag_overlap(pd, fg_off, len) ||
+ pd->datagram_size != dg_size) {
+ /*
+ * Differing datagram sizes or overlapping fragments,
+ * discard old datagram and start a new one.
+ */
+ fwnet_pd_delete(pd);
+ pd = fwnet_pd_new(net, peer, datagram_label,
+ dg_size, buf, fg_off, len);
+ if (pd == NULL) {
+ peer->pdg_size--;
+ retval = -ENOMEM;
+ goto fail;
+ }
+ } else {
+ if (!fwnet_pd_update(peer, pd, buf, fg_off, len)) {
+ /*
+ * Couldn't save off fragment anyway
+ * so might as well obliterate the
+ * datagram now.
+ */
+ fwnet_pd_delete(pd);
+ peer->pdg_size--;
+ retval = -ENOMEM;
+ goto fail;
+ }
+ }
+ } /* new datagram or add to existing one */
+
+ if (lf == RFC2374_HDR_FIRSTFRAG)
+ pd->ether_type = ether_type;
+
+ if (fwnet_pd_is_complete(pd)) {
+ ether_type = pd->ether_type;
+ peer->pdg_size--;
+ skb = skb_get(pd->skb);
+ fwnet_pd_delete(pd);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return fwnet_finish_incoming_packet(net, skb, source_node_id,
+ false, ether_type);
+ }
+ /*
+ * Datagram is not complete, we're done for the
+ * moment.
+ */
+ retval = 0;
+ fail:
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return retval;
+}
+
+static void fwnet_receive_packet(struct fw_card *card, struct fw_request *r,
+ int tcode, int destination, int source, int generation,
+ unsigned long long offset, void *payload, size_t length,
+ void *callback_data)
+{
+ struct fwnet_device *dev = callback_data;
+ int rcode;
+
+ if (destination == IEEE1394_ALL_NODES) {
+ // Although the response to the broadcast packet is not necessarily required, the
+ // fw_send_response() function should still be called to maintain the reference
+ // counting of the object. In the case, the call of function just releases the
+ // object as a result to decrease the reference counting.
+ rcode = RCODE_COMPLETE;
+ } else if (offset != dev->handler.offset) {
+ rcode = RCODE_ADDRESS_ERROR;
+ } else if (tcode != TCODE_WRITE_BLOCK_REQUEST) {
+ rcode = RCODE_TYPE_ERROR;
+ } else if (fwnet_incoming_packet(dev, payload, length,
+ source, generation, false) != 0) {
+ dev_err(&dev->netdev->dev, "incoming packet failure\n");
+ rcode = RCODE_CONFLICT_ERROR;
+ } else {
+ rcode = RCODE_COMPLETE;
+ }
+
+ fw_send_response(card, r, rcode);
+}
+
+static int gasp_source_id(__be32 *p)
+{
+ return be32_to_cpu(p[0]) >> 16;
+}
+
+static u32 gasp_specifier_id(__be32 *p)
+{
+ return (be32_to_cpu(p[0]) & 0xffff) << 8 |
+ (be32_to_cpu(p[1]) & 0xff000000) >> 24;
+}
+
+static u32 gasp_version(__be32 *p)
+{
+ return be32_to_cpu(p[1]) & 0xffffff;
+}
+
+static void fwnet_receive_broadcast(struct fw_iso_context *context,
+ u32 cycle, size_t header_length, void *header, void *data)
+{
+ struct fwnet_device *dev;
+ struct fw_iso_packet packet;
+ __be16 *hdr_ptr;
+ __be32 *buf_ptr;
+ int retval;
+ u32 length;
+ unsigned long offset;
+ unsigned long flags;
+
+ dev = data;
+ hdr_ptr = header;
+ length = be16_to_cpup(hdr_ptr);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ offset = dev->rcv_buffer_size * dev->broadcast_rcv_next_ptr;
+ buf_ptr = dev->broadcast_rcv_buffer_ptrs[dev->broadcast_rcv_next_ptr++];
+ if (dev->broadcast_rcv_next_ptr == dev->num_broadcast_rcv_ptrs)
+ dev->broadcast_rcv_next_ptr = 0;
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (length > IEEE1394_GASP_HDR_SIZE &&
+ gasp_specifier_id(buf_ptr) == IANA_SPECIFIER_ID &&
+ (gasp_version(buf_ptr) == RFC2734_SW_VERSION
+#if IS_ENABLED(CONFIG_IPV6)
+ || gasp_version(buf_ptr) == RFC3146_SW_VERSION
+#endif
+ ))
+ fwnet_incoming_packet(dev, buf_ptr + 2,
+ length - IEEE1394_GASP_HDR_SIZE,
+ gasp_source_id(buf_ptr),
+ context->card->generation, true);
+
+ packet.payload_length = dev->rcv_buffer_size;
+ packet.interrupt = 1;
+ packet.skip = 0;
+ packet.tag = 3;
+ packet.sy = 0;
+ packet.header_length = IEEE1394_GASP_HDR_SIZE;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ retval = fw_iso_context_queue(dev->broadcast_rcv_context, &packet,
+ &dev->broadcast_rcv_buffer, offset);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (retval >= 0)
+ fw_iso_context_queue_flush(dev->broadcast_rcv_context);
+ else
+ dev_err(&dev->netdev->dev, "requeue failed\n");
+}
+
+static struct kmem_cache *fwnet_packet_task_cache;
+
+static void fwnet_free_ptask(struct fwnet_packet_task *ptask)
+{
+ dev_kfree_skb_any(ptask->skb);
+ kmem_cache_free(fwnet_packet_task_cache, ptask);
+}
+
+/* Caller must hold dev->lock. */
+static void dec_queued_datagrams(struct fwnet_device *dev)
+{
+ if (--dev->queued_datagrams == FWNET_MIN_QUEUED_DATAGRAMS)
+ netif_wake_queue(dev->netdev);
+}
+
+static int fwnet_send_packet(struct fwnet_packet_task *ptask);
+
+static void fwnet_transmit_packet_done(struct fwnet_packet_task *ptask)
+{
+ struct fwnet_device *dev = ptask->dev;
+ struct sk_buff *skb = ptask->skb;
+ unsigned long flags;
+ bool free;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ ptask->outstanding_pkts--;
+
+ /* Check whether we or the networking TX soft-IRQ is last user. */
+ free = (ptask->outstanding_pkts == 0 && ptask->enqueued);
+ if (free)
+ dec_queued_datagrams(dev);
+
+ if (ptask->outstanding_pkts == 0) {
+ dev->netdev->stats.tx_packets++;
+ dev->netdev->stats.tx_bytes += skb->len;
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (ptask->outstanding_pkts > 0) {
+ u16 dg_size;
+ u16 fg_off;
+ u16 datagram_label;
+ u16 lf;
+
+ /* Update the ptask to point to the next fragment and send it */
+ lf = fwnet_get_hdr_lf(&ptask->hdr);
+ switch (lf) {
+ case RFC2374_HDR_LASTFRAG:
+ case RFC2374_HDR_UNFRAG:
+ default:
+ dev_err(&dev->netdev->dev,
+ "outstanding packet %x lf %x, header %x,%x\n",
+ ptask->outstanding_pkts, lf, ptask->hdr.w0,
+ ptask->hdr.w1);
+ BUG();
+
+ case RFC2374_HDR_FIRSTFRAG:
+ /* Set frag type here for future interior fragments */
+ dg_size = fwnet_get_hdr_dg_size(&ptask->hdr);
+ fg_off = ptask->max_payload - RFC2374_FRAG_HDR_SIZE;
+ datagram_label = fwnet_get_hdr_dgl(&ptask->hdr);
+ break;
+
+ case RFC2374_HDR_INTFRAG:
+ dg_size = fwnet_get_hdr_dg_size(&ptask->hdr);
+ fg_off = fwnet_get_hdr_fg_off(&ptask->hdr)
+ + ptask->max_payload - RFC2374_FRAG_HDR_SIZE;
+ datagram_label = fwnet_get_hdr_dgl(&ptask->hdr);
+ break;
+ }
+
+ if (ptask->dest_node == IEEE1394_ALL_NODES) {
+ skb_pull(skb,
+ ptask->max_payload + IEEE1394_GASP_HDR_SIZE);
+ } else {
+ skb_pull(skb, ptask->max_payload);
+ }
+ if (ptask->outstanding_pkts > 1) {
+ fwnet_make_sf_hdr(&ptask->hdr, RFC2374_HDR_INTFRAG,
+ dg_size, fg_off, datagram_label);
+ } else {
+ fwnet_make_sf_hdr(&ptask->hdr, RFC2374_HDR_LASTFRAG,
+ dg_size, fg_off, datagram_label);
+ ptask->max_payload = skb->len + RFC2374_FRAG_HDR_SIZE;
+ }
+ fwnet_send_packet(ptask);
+ }
+
+ if (free)
+ fwnet_free_ptask(ptask);
+}
+
+static void fwnet_transmit_packet_failed(struct fwnet_packet_task *ptask)
+{
+ struct fwnet_device *dev = ptask->dev;
+ unsigned long flags;
+ bool free;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ /* One fragment failed; don't try to send remaining fragments. */
+ ptask->outstanding_pkts = 0;
+
+ /* Check whether we or the networking TX soft-IRQ is last user. */
+ free = ptask->enqueued;
+ if (free)
+ dec_queued_datagrams(dev);
+
+ dev->netdev->stats.tx_dropped++;
+ dev->netdev->stats.tx_errors++;
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (free)
+ fwnet_free_ptask(ptask);
+}
+
+static void fwnet_write_complete(struct fw_card *card, int rcode,
+ void *payload, size_t length, void *data)
+{
+ struct fwnet_packet_task *ptask = data;
+ static unsigned long j;
+ static int last_rcode, errors_skipped;
+
+ if (rcode == RCODE_COMPLETE) {
+ fwnet_transmit_packet_done(ptask);
+ } else {
+ if (printk_timed_ratelimit(&j, 1000) || rcode != last_rcode) {
+ dev_err(&ptask->dev->netdev->dev,
+ "fwnet_write_complete failed: %x (skipped %d)\n",
+ rcode, errors_skipped);
+
+ errors_skipped = 0;
+ last_rcode = rcode;
+ } else {
+ errors_skipped++;
+ }
+ fwnet_transmit_packet_failed(ptask);
+ }
+}
+
+static int fwnet_send_packet(struct fwnet_packet_task *ptask)
+{
+ struct fwnet_device *dev;
+ unsigned tx_len;
+ struct rfc2734_header *bufhdr;
+ unsigned long flags;
+ bool free;
+
+ dev = ptask->dev;
+ tx_len = ptask->max_payload;
+ switch (fwnet_get_hdr_lf(&ptask->hdr)) {
+ case RFC2374_HDR_UNFRAG:
+ bufhdr = skb_push(ptask->skb, RFC2374_UNFRAG_HDR_SIZE);
+ put_unaligned_be32(ptask->hdr.w0, &bufhdr->w0);
+ break;
+
+ case RFC2374_HDR_FIRSTFRAG:
+ case RFC2374_HDR_INTFRAG:
+ case RFC2374_HDR_LASTFRAG:
+ bufhdr = skb_push(ptask->skb, RFC2374_FRAG_HDR_SIZE);
+ put_unaligned_be32(ptask->hdr.w0, &bufhdr->w0);
+ put_unaligned_be32(ptask->hdr.w1, &bufhdr->w1);
+ break;
+
+ default:
+ BUG();
+ }
+ if (ptask->dest_node == IEEE1394_ALL_NODES) {
+ u8 *p;
+ int generation;
+ int node_id;
+ unsigned int sw_version;
+
+ /* ptask->generation may not have been set yet */
+ generation = dev->card->generation;
+ smp_rmb();
+ node_id = dev->card->node_id;
+
+ switch (ptask->skb->protocol) {
+ default:
+ sw_version = RFC2734_SW_VERSION;
+ break;
+#if IS_ENABLED(CONFIG_IPV6)
+ case htons(ETH_P_IPV6):
+ sw_version = RFC3146_SW_VERSION;
+#endif
+ }
+
+ p = skb_push(ptask->skb, IEEE1394_GASP_HDR_SIZE);
+ put_unaligned_be32(node_id << 16 | IANA_SPECIFIER_ID >> 8, p);
+ put_unaligned_be32((IANA_SPECIFIER_ID & 0xff) << 24
+ | sw_version, &p[4]);
+
+ /* We should not transmit if broadcast_channel.valid == 0. */
+ fw_send_request(dev->card, &ptask->transaction,
+ TCODE_STREAM_DATA,
+ fw_stream_packet_destination_id(3,
+ IEEE1394_BROADCAST_CHANNEL, 0),
+ generation, SCODE_100, 0ULL, ptask->skb->data,
+ tx_len + 8, fwnet_write_complete, ptask);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ /* If the AT work item already ran, we may be last user. */
+ free = (ptask->outstanding_pkts == 0 && !ptask->enqueued);
+ if (!free)
+ ptask->enqueued = true;
+ else
+ dec_queued_datagrams(dev);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ goto out;
+ }
+
+ fw_send_request(dev->card, &ptask->transaction,
+ TCODE_WRITE_BLOCK_REQUEST, ptask->dest_node,
+ ptask->generation, ptask->speed, ptask->fifo_addr,
+ ptask->skb->data, tx_len, fwnet_write_complete, ptask);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ /* If the AT work item already ran, we may be last user. */
+ free = (ptask->outstanding_pkts == 0 && !ptask->enqueued);
+ if (!free)
+ ptask->enqueued = true;
+ else
+ dec_queued_datagrams(dev);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ netif_trans_update(dev->netdev);
+ out:
+ if (free)
+ fwnet_free_ptask(ptask);
+
+ return 0;
+}
+
+static void fwnet_fifo_stop(struct fwnet_device *dev)
+{
+ if (dev->local_fifo == FWNET_NO_FIFO_ADDR)
+ return;
+
+ fw_core_remove_address_handler(&dev->handler);
+ dev->local_fifo = FWNET_NO_FIFO_ADDR;
+}
+
+static int fwnet_fifo_start(struct fwnet_device *dev)
+{
+ int retval;
+
+ if (dev->local_fifo != FWNET_NO_FIFO_ADDR)
+ return 0;
+
+ dev->handler.length = 4096;
+ dev->handler.address_callback = fwnet_receive_packet;
+ dev->handler.callback_data = dev;
+
+ retval = fw_core_add_address_handler(&dev->handler,
+ &fw_high_memory_region);
+ if (retval < 0)
+ return retval;
+
+ dev->local_fifo = dev->handler.offset;
+
+ return 0;
+}
+
+static void __fwnet_broadcast_stop(struct fwnet_device *dev)
+{
+ unsigned u;
+
+ if (dev->broadcast_state != FWNET_BROADCAST_ERROR) {
+ for (u = 0; u < FWNET_ISO_PAGE_COUNT; u++)
+ kunmap(dev->broadcast_rcv_buffer.pages[u]);
+ fw_iso_buffer_destroy(&dev->broadcast_rcv_buffer, dev->card);
+ }
+ if (dev->broadcast_rcv_context) {
+ fw_iso_context_destroy(dev->broadcast_rcv_context);
+ dev->broadcast_rcv_context = NULL;
+ }
+ kfree(dev->broadcast_rcv_buffer_ptrs);
+ dev->broadcast_rcv_buffer_ptrs = NULL;
+ dev->broadcast_state = FWNET_BROADCAST_ERROR;
+}
+
+static void fwnet_broadcast_stop(struct fwnet_device *dev)
+{
+ if (dev->broadcast_state == FWNET_BROADCAST_ERROR)
+ return;
+ fw_iso_context_stop(dev->broadcast_rcv_context);
+ __fwnet_broadcast_stop(dev);
+}
+
+static int fwnet_broadcast_start(struct fwnet_device *dev)
+{
+ struct fw_iso_context *context;
+ int retval;
+ unsigned num_packets;
+ unsigned max_receive;
+ struct fw_iso_packet packet;
+ unsigned long offset;
+ void **ptrptr;
+ unsigned u;
+
+ if (dev->broadcast_state != FWNET_BROADCAST_ERROR)
+ return 0;
+
+ max_receive = 1U << (dev->card->max_receive + 1);
+ num_packets = (FWNET_ISO_PAGE_COUNT * PAGE_SIZE) / max_receive;
+
+ ptrptr = kmalloc_array(num_packets, sizeof(void *), GFP_KERNEL);
+ if (!ptrptr) {
+ retval = -ENOMEM;
+ goto failed;
+ }
+ dev->broadcast_rcv_buffer_ptrs = ptrptr;
+
+ context = fw_iso_context_create(dev->card, FW_ISO_CONTEXT_RECEIVE,
+ IEEE1394_BROADCAST_CHANNEL,
+ dev->card->link_speed, 8,
+ fwnet_receive_broadcast, dev);
+ if (IS_ERR(context)) {
+ retval = PTR_ERR(context);
+ goto failed;
+ }
+
+ retval = fw_iso_buffer_init(&dev->broadcast_rcv_buffer, dev->card,
+ FWNET_ISO_PAGE_COUNT, DMA_FROM_DEVICE);
+ if (retval < 0)
+ goto failed;
+
+ dev->broadcast_state = FWNET_BROADCAST_STOPPED;
+
+ for (u = 0; u < FWNET_ISO_PAGE_COUNT; u++) {
+ void *ptr;
+ unsigned v;
+
+ ptr = kmap(dev->broadcast_rcv_buffer.pages[u]);
+ for (v = 0; v < num_packets / FWNET_ISO_PAGE_COUNT; v++)
+ *ptrptr++ = (void *) ((char *)ptr + v * max_receive);
+ }
+ dev->broadcast_rcv_context = context;
+
+ packet.payload_length = max_receive;
+ packet.interrupt = 1;
+ packet.skip = 0;
+ packet.tag = 3;
+ packet.sy = 0;
+ packet.header_length = IEEE1394_GASP_HDR_SIZE;
+ offset = 0;
+
+ for (u = 0; u < num_packets; u++) {
+ retval = fw_iso_context_queue(context, &packet,
+ &dev->broadcast_rcv_buffer, offset);
+ if (retval < 0)
+ goto failed;
+
+ offset += max_receive;
+ }
+ dev->num_broadcast_rcv_ptrs = num_packets;
+ dev->rcv_buffer_size = max_receive;
+ dev->broadcast_rcv_next_ptr = 0U;
+ retval = fw_iso_context_start(context, -1, 0,
+ FW_ISO_CONTEXT_MATCH_ALL_TAGS); /* ??? sync */
+ if (retval < 0)
+ goto failed;
+
+ /* FIXME: adjust it according to the min. speed of all known peers? */
+ dev->broadcast_xmt_max_payload = IEEE1394_MAX_PAYLOAD_S100
+ - IEEE1394_GASP_HDR_SIZE - RFC2374_UNFRAG_HDR_SIZE;
+ dev->broadcast_state = FWNET_BROADCAST_RUNNING;
+
+ return 0;
+
+ failed:
+ __fwnet_broadcast_stop(dev);
+ return retval;
+}
+
+static void set_carrier_state(struct fwnet_device *dev)
+{
+ if (dev->peer_count > 1)
+ netif_carrier_on(dev->netdev);
+ else
+ netif_carrier_off(dev->netdev);
+}
+
+/* ifup */
+static int fwnet_open(struct net_device *net)
+{
+ struct fwnet_device *dev = netdev_priv(net);
+ int ret;
+
+ ret = fwnet_broadcast_start(dev);
+ if (ret)
+ return ret;
+
+ netif_start_queue(net);
+
+ spin_lock_irq(&dev->lock);
+ set_carrier_state(dev);
+ spin_unlock_irq(&dev->lock);
+
+ return 0;
+}
+
+/* ifdown */
+static int fwnet_stop(struct net_device *net)
+{
+ struct fwnet_device *dev = netdev_priv(net);
+
+ netif_stop_queue(net);
+ fwnet_broadcast_stop(dev);
+
+ return 0;
+}
+
+static netdev_tx_t fwnet_tx(struct sk_buff *skb, struct net_device *net)
+{
+ struct fwnet_header hdr_buf;
+ struct fwnet_device *dev = netdev_priv(net);
+ __be16 proto;
+ u16 dest_node;
+ unsigned max_payload;
+ u16 dg_size;
+ u16 *datagram_label_ptr;
+ struct fwnet_packet_task *ptask;
+ struct fwnet_peer *peer;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ /* Can this happen? */
+ if (netif_queue_stopped(dev->netdev)) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return NETDEV_TX_BUSY;
+ }
+
+ ptask = kmem_cache_alloc(fwnet_packet_task_cache, GFP_ATOMIC);
+ if (ptask == NULL)
+ goto fail;
+
+ skb = skb_share_check(skb, GFP_ATOMIC);
+ if (!skb)
+ goto fail;
+
+ /*
+ * Make a copy of the driver-specific header.
+ * We might need to rebuild the header on tx failure.
+ */
+ memcpy(&hdr_buf, skb->data, sizeof(hdr_buf));
+ proto = hdr_buf.h_proto;
+
+ switch (proto) {
+ case htons(ETH_P_ARP):
+ case htons(ETH_P_IP):
+#if IS_ENABLED(CONFIG_IPV6)
+ case htons(ETH_P_IPV6):
+#endif
+ break;
+ default:
+ goto fail;
+ }
+
+ skb_pull(skb, sizeof(hdr_buf));
+ dg_size = skb->len;
+
+ /*
+ * Set the transmission type for the packet. ARP packets and IP
+ * broadcast packets are sent via GASP.
+ */
+ if (fwnet_hwaddr_is_multicast(hdr_buf.h_dest)) {
+ max_payload = dev->broadcast_xmt_max_payload;
+ datagram_label_ptr = &dev->broadcast_xmt_datagramlabel;
+
+ ptask->fifo_addr = FWNET_NO_FIFO_ADDR;
+ ptask->generation = 0;
+ ptask->dest_node = IEEE1394_ALL_NODES;
+ ptask->speed = SCODE_100;
+ } else {
+ union fwnet_hwaddr *ha = (union fwnet_hwaddr *)hdr_buf.h_dest;
+ __be64 guid = get_unaligned(&ha->uc.uniq_id);
+ u8 generation;
+
+ peer = fwnet_peer_find_by_guid(dev, be64_to_cpu(guid));
+ if (!peer)
+ goto fail;
+
+ generation = peer->generation;
+ dest_node = peer->node_id;
+ max_payload = peer->max_payload;
+ datagram_label_ptr = &peer->datagram_label;
+
+ ptask->fifo_addr = get_unaligned_be48(ha->uc.fifo);
+ ptask->generation = generation;
+ ptask->dest_node = dest_node;
+ ptask->speed = peer->speed;
+ }
+
+ ptask->hdr.w0 = 0;
+ ptask->hdr.w1 = 0;
+ ptask->skb = skb;
+ ptask->dev = dev;
+
+ /* Does it all fit in one packet? */
+ if (dg_size <= max_payload) {
+ fwnet_make_uf_hdr(&ptask->hdr, ntohs(proto));
+ ptask->outstanding_pkts = 1;
+ max_payload = dg_size + RFC2374_UNFRAG_HDR_SIZE;
+ } else {
+ u16 datagram_label;
+
+ max_payload -= RFC2374_FRAG_OVERHEAD;
+ datagram_label = (*datagram_label_ptr)++;
+ fwnet_make_ff_hdr(&ptask->hdr, ntohs(proto), dg_size,
+ datagram_label);
+ ptask->outstanding_pkts = DIV_ROUND_UP(dg_size, max_payload);
+ max_payload += RFC2374_FRAG_HDR_SIZE;
+ }
+
+ if (++dev->queued_datagrams == FWNET_MAX_QUEUED_DATAGRAMS)
+ netif_stop_queue(dev->netdev);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ ptask->max_payload = max_payload;
+ ptask->enqueued = 0;
+
+ fwnet_send_packet(ptask);
+
+ return NETDEV_TX_OK;
+
+ fail:
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (ptask)
+ kmem_cache_free(fwnet_packet_task_cache, ptask);
+
+ if (skb != NULL)
+ dev_kfree_skb(skb);
+
+ net->stats.tx_dropped++;
+ net->stats.tx_errors++;
+
+ /*
+ * FIXME: According to a patch from 2003-02-26, "returning non-zero
+ * causes serious problems" here, allegedly. Before that patch,
+ * -ERRNO was returned which is not appropriate under Linux 2.6.
+ * Perhaps more needs to be done? Stop the queue in serious
+ * conditions and restart it elsewhere?
+ */
+ return NETDEV_TX_OK;
+}
+
+static const struct ethtool_ops fwnet_ethtool_ops = {
+ .get_link = ethtool_op_get_link,
+};
+
+static const struct net_device_ops fwnet_netdev_ops = {
+ .ndo_open = fwnet_open,
+ .ndo_stop = fwnet_stop,
+ .ndo_start_xmit = fwnet_tx,
+};
+
+static void fwnet_init_dev(struct net_device *net)
+{
+ net->header_ops = &fwnet_header_ops;
+ net->netdev_ops = &fwnet_netdev_ops;
+ net->watchdog_timeo = 2 * HZ;
+ net->flags = IFF_BROADCAST | IFF_MULTICAST;
+ net->features = NETIF_F_HIGHDMA;
+ net->addr_len = FWNET_ALEN;
+ net->hard_header_len = FWNET_HLEN;
+ net->type = ARPHRD_IEEE1394;
+ net->tx_queue_len = FWNET_TX_QUEUE_LEN;
+ net->ethtool_ops = &fwnet_ethtool_ops;
+}
+
+/* caller must hold fwnet_device_mutex */
+static struct fwnet_device *fwnet_dev_find(struct fw_card *card)
+{
+ struct fwnet_device *dev;
+
+ list_for_each_entry(dev, &fwnet_device_list, dev_link)
+ if (dev->card == card)
+ return dev;
+
+ return NULL;
+}
+
+static int fwnet_add_peer(struct fwnet_device *dev,
+ struct fw_unit *unit, struct fw_device *device)
+{
+ struct fwnet_peer *peer;
+
+ peer = kmalloc_obj(*peer);
+ if (!peer)
+ return -ENOMEM;
+
+ dev_set_drvdata(&unit->device, peer);
+
+ peer->dev = dev;
+ peer->guid = (u64)device->config_rom[3] << 32 | device->config_rom[4];
+ INIT_LIST_HEAD(&peer->pd_list);
+ peer->pdg_size = 0;
+ peer->datagram_label = 0;
+ peer->speed = device->max_speed;
+ peer->max_payload = fwnet_max_payload(device->max_rec, peer->speed);
+
+ peer->generation = device->generation;
+ smp_rmb();
+ peer->node_id = device->node_id;
+
+ spin_lock_irq(&dev->lock);
+ list_add_tail(&peer->peer_link, &dev->peer_list);
+ dev->peer_count++;
+ set_carrier_state(dev);
+ spin_unlock_irq(&dev->lock);
+
+ return 0;
+}
+
+static int fwnet_probe(struct fw_unit *unit,
+ const struct ieee1394_device_id *id)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ struct fw_card *card = device->card;
+ struct net_device *net;
+ bool allocated_netdev = false;
+ struct fwnet_device *dev;
+ union fwnet_hwaddr ha;
+ int ret;
+
+ mutex_lock(&fwnet_device_mutex);
+
+ dev = fwnet_dev_find(card);
+ if (dev) {
+ net = dev->netdev;
+ goto have_dev;
+ }
+
+ net = alloc_netdev(sizeof(*dev), "firewire%d", NET_NAME_UNKNOWN,
+ fwnet_init_dev);
+ if (net == NULL) {
+ mutex_unlock(&fwnet_device_mutex);
+ return -ENOMEM;
+ }
+
+ allocated_netdev = true;
+ SET_NETDEV_DEV(net, card->device);
+ dev = netdev_priv(net);
+
+ spin_lock_init(&dev->lock);
+ dev->broadcast_state = FWNET_BROADCAST_ERROR;
+ dev->broadcast_rcv_context = NULL;
+ dev->broadcast_xmt_max_payload = 0;
+ dev->broadcast_xmt_datagramlabel = 0;
+ dev->local_fifo = FWNET_NO_FIFO_ADDR;
+ dev->queued_datagrams = 0;
+ INIT_LIST_HEAD(&dev->peer_list);
+ dev->card = card;
+ dev->netdev = net;
+
+ ret = fwnet_fifo_start(dev);
+ if (ret < 0)
+ goto out;
+ dev->local_fifo = dev->handler.offset;
+
+ /*
+ * default MTU: RFC 2734 cl. 4, RFC 3146 cl. 4
+ * maximum MTU: RFC 2734 cl. 4.2, fragment encapsulation header's
+ * maximum possible datagram_size + 1 = 0xfff + 1
+ */
+ net->mtu = 1500U;
+ net->min_mtu = ETH_MIN_MTU;
+ net->max_mtu = 4096U;
+
+ /* Set our hardware address while we're at it */
+ ha.uc.uniq_id = cpu_to_be64(card->guid);
+ ha.uc.max_rec = dev->card->max_receive;
+ ha.uc.sspd = dev->card->link_speed;
+ put_unaligned_be48(dev->local_fifo, ha.uc.fifo);
+ dev_addr_set(net, ha.u);
+
+ memset(net->broadcast, -1, net->addr_len);
+
+ ret = register_netdev(net);
+ if (ret)
+ goto out;
+
+ list_add_tail(&dev->dev_link, &fwnet_device_list);
+ dev_notice(&net->dev, "IP over IEEE 1394 on card %s\n",
+ dev_name(card->device));
+ have_dev:
+ ret = fwnet_add_peer(dev, unit, device);
+ if (ret && allocated_netdev) {
+ unregister_netdev(net);
+ list_del(&dev->dev_link);
+ out:
+ fwnet_fifo_stop(dev);
+ free_netdev(net);
+ }
+
+ mutex_unlock(&fwnet_device_mutex);
+
+ return ret;
+}
+
+/*
+ * FIXME abort partially sent fragmented datagrams,
+ * discard partially received fragmented datagrams
+ */
+static void fwnet_update(struct fw_unit *unit)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ struct fwnet_peer *peer = dev_get_drvdata(&unit->device);
+ int generation;
+
+ generation = device->generation;
+
+ spin_lock_irq(&peer->dev->lock);
+ peer->node_id = device->node_id;
+ peer->generation = generation;
+ spin_unlock_irq(&peer->dev->lock);
+}
+
+static void fwnet_remove_peer(struct fwnet_peer *peer, struct fwnet_device *dev)
+{
+ struct fwnet_partial_datagram *pd, *pd_next;
+
+ spin_lock_irq(&dev->lock);
+ list_del(&peer->peer_link);
+ dev->peer_count--;
+ set_carrier_state(dev);
+ spin_unlock_irq(&dev->lock);
+
+ list_for_each_entry_safe(pd, pd_next, &peer->pd_list, pd_link)
+ fwnet_pd_delete(pd);
+
+ kfree(peer);
+}
+
+static void fwnet_remove(struct fw_unit *unit)
+{
+ struct fwnet_peer *peer = dev_get_drvdata(&unit->device);
+ struct fwnet_device *dev = peer->dev;
+ struct net_device *net;
+ int i;
+
+ mutex_lock(&fwnet_device_mutex);
+
+ net = dev->netdev;
+
+ fwnet_remove_peer(peer, dev);
+
+ if (list_empty(&dev->peer_list)) {
+ unregister_netdev(net);
+
+ fwnet_fifo_stop(dev);
+
+ for (i = 0; dev->queued_datagrams && i < 5; i++)
+ ssleep(1);
+ WARN_ON(dev->queued_datagrams);
+ list_del(&dev->dev_link);
+
+ free_netdev(net);
+ }
+
+ mutex_unlock(&fwnet_device_mutex);
+}
+
+static const struct ieee1394_device_id fwnet_id_table[] = {
+ {
+ .match_flags = IEEE1394_MATCH_SPECIFIER_ID |
+ IEEE1394_MATCH_VERSION,
+ .specifier_id = IANA_SPECIFIER_ID,
+ .version = RFC2734_SW_VERSION,
+ },
+#if IS_ENABLED(CONFIG_IPV6)
+ {
+ .match_flags = IEEE1394_MATCH_SPECIFIER_ID |
+ IEEE1394_MATCH_VERSION,
+ .specifier_id = IANA_SPECIFIER_ID,
+ .version = RFC3146_SW_VERSION,
+ },
+#endif
+ { }
+};
+
+static struct fw_driver fwnet_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .bus = &fw_bus_type,
+ },
+ .probe = fwnet_probe,
+ .update = fwnet_update,
+ .remove = fwnet_remove,
+ .id_table = fwnet_id_table,
+};
+
+static const u32 rfc2374_unit_directory_data[] = {
+ 0x00040000, /* directory_length */
+ 0x1200005e, /* unit_specifier_id: IANA */
+ 0x81000003, /* textual descriptor offset */
+ 0x13000001, /* unit_sw_version: RFC 2734 */
+ 0x81000005, /* textual descriptor offset */
+ 0x00030000, /* descriptor_length */
+ 0x00000000, /* text */
+ 0x00000000, /* minimal ASCII, en */
+ 0x49414e41, /* I A N A */
+ 0x00030000, /* descriptor_length */
+ 0x00000000, /* text */
+ 0x00000000, /* minimal ASCII, en */
+ 0x49507634, /* I P v 4 */
+};
+
+static struct fw_descriptor rfc2374_unit_directory = {
+ .length = ARRAY_SIZE(rfc2374_unit_directory_data),
+ .key = (CSR_DIRECTORY | CSR_UNIT) << 24,
+ .data = rfc2374_unit_directory_data
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static const u32 rfc3146_unit_directory_data[] = {
+ 0x00040000, /* directory_length */
+ 0x1200005e, /* unit_specifier_id: IANA */
+ 0x81000003, /* textual descriptor offset */
+ 0x13000002, /* unit_sw_version: RFC 3146 */
+ 0x81000005, /* textual descriptor offset */
+ 0x00030000, /* descriptor_length */
+ 0x00000000, /* text */
+ 0x00000000, /* minimal ASCII, en */
+ 0x49414e41, /* I A N A */
+ 0x00030000, /* descriptor_length */
+ 0x00000000, /* text */
+ 0x00000000, /* minimal ASCII, en */
+ 0x49507636, /* I P v 6 */
+};
+
+static struct fw_descriptor rfc3146_unit_directory = {
+ .length = ARRAY_SIZE(rfc3146_unit_directory_data),
+ .key = (CSR_DIRECTORY | CSR_UNIT) << 24,
+ .data = rfc3146_unit_directory_data
+};
+#endif
+
+static int __init fwnet_init(void)
+{
+ int err;
+
+ err = fw_core_add_descriptor(&rfc2374_unit_directory);
+ if (err)
+ return err;
+
+#if IS_ENABLED(CONFIG_IPV6)
+ err = fw_core_add_descriptor(&rfc3146_unit_directory);
+ if (err)
+ goto out;
+#endif
+
+ fwnet_packet_task_cache = kmem_cache_create("packet_task",
+ sizeof(struct fwnet_packet_task), 0, 0, NULL);
+ if (!fwnet_packet_task_cache) {
+ err = -ENOMEM;
+ goto out2;
+ }
+
+ err = driver_register(&fwnet_driver.driver);
+ if (!err)
+ return 0;
+
+ kmem_cache_destroy(fwnet_packet_task_cache);
+out2:
+#if IS_ENABLED(CONFIG_IPV6)
+ fw_core_remove_descriptor(&rfc3146_unit_directory);
+out:
+#endif
+ fw_core_remove_descriptor(&rfc2374_unit_directory);
+
+ return err;
+}
+module_init(fwnet_init);
+
+static void __exit fwnet_cleanup(void)
+{
+ driver_unregister(&fwnet_driver.driver);
+ kmem_cache_destroy(fwnet_packet_task_cache);
+#if IS_ENABLED(CONFIG_IPV6)
+ fw_core_remove_descriptor(&rfc3146_unit_directory);
+#endif
+ fw_core_remove_descriptor(&rfc2374_unit_directory);
+}
+module_exit(fwnet_cleanup);
+
+MODULE_AUTHOR("Jay Fenlason <fenlason@redhat.com>");
+MODULE_DESCRIPTION("IP over IEEE1394 as per RFC 2734/3146");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ieee1394, fwnet_id_table);
diff --git a/drivers/firewire/nosy-user.h b/drivers/firewire/nosy-user.h
new file mode 100644
index 000000000..3446c5b77
--- /dev/null
+++ b/drivers/firewire/nosy-user.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __nosy_user_h
+#define __nosy_user_h
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define NOSY_IOC_GET_STATS _IOR('&', 0, struct nosy_stats)
+#define NOSY_IOC_START _IO('&', 1)
+#define NOSY_IOC_STOP _IO('&', 2)
+#define NOSY_IOC_FILTER _IOW('&', 2, __u32)
+
+struct nosy_stats {
+ __u32 total_packet_count;
+ __u32 lost_packet_count;
+};
+
+/*
+ * Format of packets returned from the kernel driver:
+ *
+ * quadlet with timestamp (microseconds, CPU endian)
+ * quadlet-padded packet data... (little endian)
+ * quadlet with ack (little endian)
+ */
+
+#endif /* __nosy_user_h */
diff --git a/drivers/firewire/nosy.c b/drivers/firewire/nosy.c
new file mode 100644
index 000000000..900b7d904
--- /dev/null
+++ b/drivers/firewire/nosy.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * nosy - Snoop mode driver for TI PCILynx 1394 controllers
+ * Copyright (C) 2002-2007 Kristian Høgsberg
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/poll.h>
+#include <linux/sched.h> /* required for linux/wait.h */
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/time64.h>
+#include <linux/timex.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/dma-mapping.h>
+#include <linux/atomic.h>
+#include <asm/byteorder.h>
+
+#include "nosy.h"
+#include "nosy-user.h"
+
+#define TCODE_PHY_PACKET 0x10
+#define PCI_DEVICE_ID_TI_PCILYNX 0x8000
+
+static char driver_name[] = KBUILD_MODNAME;
+
+#define RCV_BUFFER_SIZE (16 * 1024)
+
+/* this is the physical layout of a PCL, its size is 128 bytes */
+struct pcl {
+ __le32 next;
+ __le32 async_error_next;
+ u32 user_data;
+ __le32 pcl_status;
+ __le32 remaining_transfer_count;
+ __le32 next_data_buffer;
+ struct {
+ __le32 control;
+ __le32 pointer;
+ } buffer[13];
+};
+
+struct packet {
+ unsigned int length;
+ char data[];
+};
+
+struct packet_buffer {
+ char *data;
+ size_t capacity;
+ long total_packet_count, lost_packet_count;
+ atomic_t size;
+ struct packet *head, *tail;
+ wait_queue_head_t wait;
+};
+
+struct pcilynx {
+ struct pci_dev *pci_device;
+ __iomem char *registers;
+
+ struct pcl *rcv_start_pcl, *rcv_pcl;
+ __le32 *rcv_buffer;
+
+ dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;
+
+ spinlock_t client_list_lock;
+ struct list_head client_list;
+
+ struct miscdevice misc;
+ struct list_head link;
+ struct kref kref;
+};
+
+static inline struct pcilynx *
+lynx_get(struct pcilynx *lynx)
+{
+ kref_get(&lynx->kref);
+
+ return lynx;
+}
+
+static void
+lynx_release(struct kref *kref)
+{
+ kfree(container_of(kref, struct pcilynx, kref));
+}
+
+static inline void
+lynx_put(struct pcilynx *lynx)
+{
+ kref_put(&lynx->kref, lynx_release);
+}
+
+struct client {
+ struct pcilynx *lynx;
+ u32 tcode_mask;
+ struct packet_buffer buffer;
+ struct list_head link;
+};
+
+static DEFINE_MUTEX(card_mutex);
+static LIST_HEAD(card_list);
+
+static int
+packet_buffer_init(struct packet_buffer *buffer, size_t capacity)
+{
+ buffer->data = kmalloc(capacity, GFP_KERNEL);
+ if (buffer->data == NULL)
+ return -ENOMEM;
+ buffer->head = (struct packet *) buffer->data;
+ buffer->tail = (struct packet *) buffer->data;
+ buffer->capacity = capacity;
+ buffer->lost_packet_count = 0;
+ atomic_set(&buffer->size, 0);
+ init_waitqueue_head(&buffer->wait);
+
+ return 0;
+}
+
+static void
+packet_buffer_destroy(struct packet_buffer *buffer)
+{
+ kfree(buffer->data);
+}
+
+static int
+packet_buffer_get(struct client *client, char __user *data, size_t user_length)
+{
+ struct packet_buffer *buffer = &client->buffer;
+ size_t length;
+ char *end;
+
+ if (wait_event_interruptible(buffer->wait,
+ atomic_read(&buffer->size) > 0) ||
+ list_empty(&client->lynx->link))
+ return -ERESTARTSYS;
+
+ if (atomic_read(&buffer->size) == 0)
+ return -ENODEV;
+
+ length = buffer->head->length;
+
+ if (length > user_length)
+ return 0;
+
+ end = buffer->data + buffer->capacity;
+
+ if (&buffer->head->data[length] < end) {
+ if (copy_to_user(data, buffer->head->data, length))
+ return -EFAULT;
+ buffer->head = (struct packet *) &buffer->head->data[length];
+ } else {
+ size_t split = end - buffer->head->data;
+
+ if (copy_to_user(data, buffer->head->data, split))
+ return -EFAULT;
+ if (copy_to_user(data + split, buffer->data, length - split))
+ return -EFAULT;
+ buffer->head = (struct packet *) &buffer->data[length - split];
+ }
+
+ /*
+ * Decrease buffer->size as the last thing, since this is what
+ * keeps the interrupt from overwriting the packet we are
+ * retrieving from the buffer.
+ */
+ atomic_sub(sizeof(struct packet) + length, &buffer->size);
+
+ return length;
+}
+
+static void
+packet_buffer_put(struct packet_buffer *buffer, void *data, size_t length)
+{
+ char *end;
+
+ buffer->total_packet_count++;
+
+ if (buffer->capacity <
+ atomic_read(&buffer->size) + sizeof(struct packet) + length) {
+ buffer->lost_packet_count++;
+ return;
+ }
+
+ end = buffer->data + buffer->capacity;
+ buffer->tail->length = length;
+
+ if (&buffer->tail->data[length] < end) {
+ memcpy(buffer->tail->data, data, length);
+ buffer->tail = (struct packet *) &buffer->tail->data[length];
+ } else {
+ size_t split = end - buffer->tail->data;
+
+ memcpy(buffer->tail->data, data, split);
+ memcpy(buffer->data, data + split, length - split);
+ buffer->tail = (struct packet *) &buffer->data[length - split];
+ }
+
+ /* Finally, adjust buffer size and wake up userspace reader. */
+
+ atomic_add(sizeof(struct packet) + length, &buffer->size);
+ wake_up_interruptible(&buffer->wait);
+}
+
+static inline void
+reg_write(struct pcilynx *lynx, int offset, u32 data)
+{
+ writel(data, lynx->registers + offset);
+}
+
+static inline u32
+reg_read(struct pcilynx *lynx, int offset)
+{
+ return readl(lynx->registers + offset);
+}
+
+static inline void
+reg_set_bits(struct pcilynx *lynx, int offset, u32 mask)
+{
+ reg_write(lynx, offset, (reg_read(lynx, offset) | mask));
+}
+
+/*
+ * Maybe the pcl programs could be set up to just append data instead
+ * of using a whole packet.
+ */
+static inline void
+run_pcl(struct pcilynx *lynx, dma_addr_t pcl_bus,
+ int dmachan)
+{
+ reg_write(lynx, DMA0_CURRENT_PCL + dmachan * 0x20, pcl_bus);
+ reg_write(lynx, DMA0_CHAN_CTRL + dmachan * 0x20,
+ DMA_CHAN_CTRL_ENABLE | DMA_CHAN_CTRL_LINK);
+}
+
+static int
+set_phy_reg(struct pcilynx *lynx, int addr, int val)
+{
+ if (addr > 15) {
+ dev_err(&lynx->pci_device->dev,
+ "PHY register address %d out of range\n", addr);
+ return -1;
+ }
+ if (val > 0xff) {
+ dev_err(&lynx->pci_device->dev,
+ "PHY register value %d out of range\n", val);
+ return -1;
+ }
+ reg_write(lynx, LINK_PHY, LINK_PHY_WRITE |
+ LINK_PHY_ADDR(addr) | LINK_PHY_WDATA(val));
+
+ return 0;
+}
+
+static int
+nosy_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct client *client;
+ struct pcilynx *tmp, *lynx = NULL;
+
+ mutex_lock(&card_mutex);
+ list_for_each_entry(tmp, &card_list, link)
+ if (tmp->misc.minor == minor) {
+ lynx = lynx_get(tmp);
+ break;
+ }
+ mutex_unlock(&card_mutex);
+ if (lynx == NULL)
+ return -ENODEV;
+
+ client = kmalloc_obj(*client);
+ if (client == NULL)
+ goto fail;
+
+ client->tcode_mask = ~0;
+ client->lynx = lynx;
+ INIT_LIST_HEAD(&client->link);
+
+ if (packet_buffer_init(&client->buffer, 128 * 1024) < 0)
+ goto fail;
+
+ file->private_data = client;
+
+ return stream_open(inode, file);
+fail:
+ kfree(client);
+ lynx_put(lynx);
+
+ return -ENOMEM;
+}
+
+static int
+nosy_release(struct inode *inode, struct file *file)
+{
+ struct client *client = file->private_data;
+ struct pcilynx *lynx = client->lynx;
+
+ spin_lock_irq(&lynx->client_list_lock);
+ list_del_init(&client->link);
+ spin_unlock_irq(&lynx->client_list_lock);
+
+ packet_buffer_destroy(&client->buffer);
+ kfree(client);
+ lynx_put(lynx);
+
+ return 0;
+}
+
+static __poll_t
+nosy_poll(struct file *file, poll_table *pt)
+{
+ struct client *client = file->private_data;
+ __poll_t ret = 0;
+
+ poll_wait(file, &client->buffer.wait, pt);
+
+ if (atomic_read(&client->buffer.size) > 0)
+ ret = EPOLLIN | EPOLLRDNORM;
+
+ if (list_empty(&client->lynx->link))
+ ret |= EPOLLHUP;
+
+ return ret;
+}
+
+static ssize_t
+nosy_read(struct file *file, char __user *buffer, size_t count, loff_t *offset)
+{
+ struct client *client = file->private_data;
+
+ return packet_buffer_get(client, buffer, count);
+}
+
+static long
+nosy_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct client *client = file->private_data;
+ spinlock_t *client_list_lock = &client->lynx->client_list_lock;
+ struct nosy_stats stats;
+ int ret;
+
+ switch (cmd) {
+ case NOSY_IOC_GET_STATS:
+ spin_lock_irq(client_list_lock);
+ stats.total_packet_count = client->buffer.total_packet_count;
+ stats.lost_packet_count = client->buffer.lost_packet_count;
+ spin_unlock_irq(client_list_lock);
+
+ if (copy_to_user((void __user *) arg, &stats, sizeof stats))
+ return -EFAULT;
+ else
+ return 0;
+
+ case NOSY_IOC_START:
+ ret = -EBUSY;
+ spin_lock_irq(client_list_lock);
+ if (list_empty(&client->link)) {
+ list_add_tail(&client->link, &client->lynx->client_list);
+ ret = 0;
+ }
+ spin_unlock_irq(client_list_lock);
+
+ return ret;
+
+ case NOSY_IOC_STOP:
+ spin_lock_irq(client_list_lock);
+ list_del_init(&client->link);
+ spin_unlock_irq(client_list_lock);
+
+ return 0;
+
+ case NOSY_IOC_FILTER:
+ spin_lock_irq(client_list_lock);
+ client->tcode_mask = arg;
+ spin_unlock_irq(client_list_lock);
+
+ return 0;
+
+ default:
+ return -EINVAL;
+ /* Flush buffer, configure filter. */
+ }
+}
+
+static const struct file_operations nosy_ops = {
+ .owner = THIS_MODULE,
+ .read = nosy_read,
+ .unlocked_ioctl = nosy_ioctl,
+ .poll = nosy_poll,
+ .open = nosy_open,
+ .release = nosy_release,
+};
+
+#define PHY_PACKET_SIZE 12 /* 1 payload, 1 inverse, 1 ack = 3 quadlets */
+
+static void
+packet_irq_handler(struct pcilynx *lynx)
+{
+ struct client *client;
+ u32 tcode_mask, tcode, timestamp;
+ size_t length;
+ struct timespec64 ts64;
+
+ /* FIXME: Also report rcv_speed. */
+
+ length = __le32_to_cpu(lynx->rcv_pcl->pcl_status) & 0x00001fff;
+ tcode = __le32_to_cpu(lynx->rcv_buffer[1]) >> 4 & 0xf;
+
+ ktime_get_real_ts64(&ts64);
+ timestamp = ts64.tv_nsec / NSEC_PER_USEC;
+ lynx->rcv_buffer[0] = (__force __le32)timestamp;
+
+ if (length == PHY_PACKET_SIZE)
+ tcode_mask = 1 << TCODE_PHY_PACKET;
+ else
+ tcode_mask = 1 << tcode;
+
+ spin_lock(&lynx->client_list_lock);
+
+ list_for_each_entry(client, &lynx->client_list, link)
+ if (client->tcode_mask & tcode_mask)
+ packet_buffer_put(&client->buffer,
+ lynx->rcv_buffer, length + 4);
+
+ spin_unlock(&lynx->client_list_lock);
+}
+
+static void
+bus_reset_irq_handler(struct pcilynx *lynx)
+{
+ struct client *client;
+ struct timespec64 ts64;
+ u32 timestamp;
+
+ ktime_get_real_ts64(&ts64);
+ timestamp = ts64.tv_nsec / NSEC_PER_USEC;
+
+ spin_lock(&lynx->client_list_lock);
+
+ list_for_each_entry(client, &lynx->client_list, link)
+ packet_buffer_put(&client->buffer, &timestamp, 4);
+
+ spin_unlock(&lynx->client_list_lock);
+}
+
+static irqreturn_t
+irq_handler(int irq, void *device)
+{
+ struct pcilynx *lynx = device;
+ u32 pci_int_status;
+
+ pci_int_status = reg_read(lynx, PCI_INT_STATUS);
+
+ if (pci_int_status == ~0)
+ /* Card was ejected. */
+ return IRQ_NONE;
+
+ if ((pci_int_status & PCI_INT_INT_PEND) == 0)
+ /* Not our interrupt, bail out quickly. */
+ return IRQ_NONE;
+
+ if ((pci_int_status & PCI_INT_P1394_INT) != 0) {
+ u32 link_int_status;
+
+ link_int_status = reg_read(lynx, LINK_INT_STATUS);
+ reg_write(lynx, LINK_INT_STATUS, link_int_status);
+
+ if ((link_int_status & LINK_INT_PHY_BUSRESET) > 0)
+ bus_reset_irq_handler(lynx);
+ }
+
+ /* Clear the PCI_INT_STATUS register only after clearing the
+ * LINK_INT_STATUS register; otherwise the PCI_INT_P1394 will
+ * be set again immediately. */
+
+ reg_write(lynx, PCI_INT_STATUS, pci_int_status);
+
+ if ((pci_int_status & PCI_INT_DMA0_HLT) > 0) {
+ packet_irq_handler(lynx);
+ run_pcl(lynx, lynx->rcv_start_pcl_bus, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void
+remove_card(struct pci_dev *dev)
+{
+ struct pcilynx *lynx = pci_get_drvdata(dev);
+ struct client *client;
+
+ mutex_lock(&card_mutex);
+ list_del_init(&lynx->link);
+ misc_deregister(&lynx->misc);
+ mutex_unlock(&card_mutex);
+
+ reg_write(lynx, PCI_INT_ENABLE, 0);
+ free_irq(lynx->pci_device->irq, lynx);
+
+ spin_lock_irq(&lynx->client_list_lock);
+ list_for_each_entry(client, &lynx->client_list, link)
+ wake_up_interruptible(&client->buffer.wait);
+ spin_unlock_irq(&lynx->client_list_lock);
+
+ dma_free_coherent(&lynx->pci_device->dev, sizeof(struct pcl),
+ lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus);
+ dma_free_coherent(&lynx->pci_device->dev, sizeof(struct pcl),
+ lynx->rcv_pcl, lynx->rcv_pcl_bus);
+ dma_free_coherent(&lynx->pci_device->dev, RCV_BUFFER_SIZE,
+ lynx->rcv_buffer, lynx->rcv_buffer_bus);
+
+ iounmap(lynx->registers);
+ pci_disable_device(dev);
+ lynx_put(lynx);
+}
+
+static int
+add_card(struct pci_dev *dev, const struct pci_device_id *unused)
+{
+ struct pcilynx *lynx;
+ u32 p, end;
+ int ret, i;
+
+ if (dma_set_mask(&dev->dev, DMA_BIT_MASK(32))) {
+ dev_err(&dev->dev,
+ "DMA address limits not supported for PCILynx hardware\n");
+ return -ENXIO;
+ }
+ if (pci_enable_device(dev)) {
+ dev_err(&dev->dev, "Failed to enable PCILynx hardware\n");
+ return -ENXIO;
+ }
+ pci_set_master(dev);
+
+ lynx = kzalloc_obj(*lynx);
+ if (lynx == NULL) {
+ dev_err(&dev->dev, "Failed to allocate control structure\n");
+ ret = -ENOMEM;
+ goto fail_disable;
+ }
+ lynx->pci_device = dev;
+ pci_set_drvdata(dev, lynx);
+
+ spin_lock_init(&lynx->client_list_lock);
+ INIT_LIST_HEAD(&lynx->client_list);
+ kref_init(&lynx->kref);
+
+ lynx->registers = ioremap(pci_resource_start(dev, 0),
+ PCILYNX_MAX_REGISTER);
+ if (lynx->registers == NULL) {
+ dev_err(&dev->dev, "Failed to map registers\n");
+ ret = -ENOMEM;
+ goto fail_deallocate_lynx;
+ }
+
+ lynx->rcv_start_pcl = dma_alloc_coherent(&lynx->pci_device->dev,
+ sizeof(struct pcl),
+ &lynx->rcv_start_pcl_bus,
+ GFP_KERNEL);
+ lynx->rcv_pcl = dma_alloc_coherent(&lynx->pci_device->dev,
+ sizeof(struct pcl),
+ &lynx->rcv_pcl_bus, GFP_KERNEL);
+ lynx->rcv_buffer = dma_alloc_coherent(&lynx->pci_device->dev,
+ RCV_BUFFER_SIZE,
+ &lynx->rcv_buffer_bus, GFP_KERNEL);
+ if (lynx->rcv_start_pcl == NULL ||
+ lynx->rcv_pcl == NULL ||
+ lynx->rcv_buffer == NULL) {
+ dev_err(&dev->dev, "Failed to allocate receive buffer\n");
+ ret = -ENOMEM;
+ goto fail_deallocate_buffers;
+ }
+ lynx->rcv_start_pcl->next = cpu_to_le32(lynx->rcv_pcl_bus);
+ lynx->rcv_pcl->next = cpu_to_le32(PCL_NEXT_INVALID);
+ lynx->rcv_pcl->async_error_next = cpu_to_le32(PCL_NEXT_INVALID);
+
+ lynx->rcv_pcl->buffer[0].control =
+ cpu_to_le32(PCL_CMD_RCV | PCL_BIGENDIAN | 2044);
+ lynx->rcv_pcl->buffer[0].pointer =
+ cpu_to_le32(lynx->rcv_buffer_bus + 4);
+ p = lynx->rcv_buffer_bus + 2048;
+ end = lynx->rcv_buffer_bus + RCV_BUFFER_SIZE;
+ for (i = 1; p < end; i++, p += 2048) {
+ lynx->rcv_pcl->buffer[i].control =
+ cpu_to_le32(PCL_CMD_RCV | PCL_BIGENDIAN | 2048);
+ lynx->rcv_pcl->buffer[i].pointer = cpu_to_le32(p);
+ }
+ lynx->rcv_pcl->buffer[i - 1].control |= cpu_to_le32(PCL_LAST_BUFF);
+
+ reg_set_bits(lynx, MISC_CONTROL, MISC_CONTROL_SWRESET);
+ /* Fix buggy cards with autoboot pin not tied low: */
+ reg_write(lynx, DMA0_CHAN_CTRL, 0);
+ reg_write(lynx, DMA_GLOBAL_REGISTER, 0x00 << 24);
+
+#if 0
+ /* now, looking for PHY register set */
+ if ((get_phy_reg(lynx, 2) & 0xe0) == 0xe0) {
+ lynx->phyic.reg_1394a = 1;
+ PRINT(KERN_INFO, lynx->id,
+ "found 1394a conform PHY (using extended register set)");
+ lynx->phyic.vendor = get_phy_vendorid(lynx);
+ lynx->phyic.product = get_phy_productid(lynx);
+ } else {
+ lynx->phyic.reg_1394a = 0;
+ PRINT(KERN_INFO, lynx->id, "found old 1394 PHY");
+ }
+#endif
+
+ /* Setup the general receive FIFO max size. */
+ reg_write(lynx, FIFO_SIZES, 255);
+
+ reg_set_bits(lynx, PCI_INT_ENABLE, PCI_INT_DMA_ALL);
+
+ reg_write(lynx, LINK_INT_ENABLE,
+ LINK_INT_PHY_TIME_OUT | LINK_INT_PHY_REG_RCVD |
+ LINK_INT_PHY_BUSRESET | LINK_INT_IT_STUCK |
+ LINK_INT_AT_STUCK | LINK_INT_SNTRJ |
+ LINK_INT_TC_ERR | LINK_INT_GRF_OVER_FLOW |
+ LINK_INT_ITF_UNDER_FLOW | LINK_INT_ATF_UNDER_FLOW);
+
+ /* Disable the L flag in self ID packets. */
+ set_phy_reg(lynx, 4, 0);
+
+ /* Put this baby into snoop mode */
+ reg_set_bits(lynx, LINK_CONTROL, LINK_CONTROL_SNOOP_ENABLE);
+
+ run_pcl(lynx, lynx->rcv_start_pcl_bus, 0);
+
+ if (request_irq(dev->irq, irq_handler, IRQF_SHARED,
+ driver_name, lynx)) {
+ dev_err(&dev->dev,
+ "Failed to allocate shared interrupt %d\n", dev->irq);
+ ret = -EIO;
+ goto fail_deallocate_buffers;
+ }
+
+ lynx->misc.parent = &dev->dev;
+ lynx->misc.minor = MISC_DYNAMIC_MINOR;
+ lynx->misc.name = "nosy";
+ lynx->misc.fops = &nosy_ops;
+
+ mutex_lock(&card_mutex);
+ ret = misc_register(&lynx->misc);
+ if (ret) {
+ dev_err(&dev->dev, "Failed to register misc char device\n");
+ mutex_unlock(&card_mutex);
+ goto fail_free_irq;
+ }
+ list_add_tail(&lynx->link, &card_list);
+ mutex_unlock(&card_mutex);
+
+ dev_info(&dev->dev,
+ "Initialized PCILynx IEEE1394 card, irq=%d\n", dev->irq);
+
+ return 0;
+
+fail_free_irq:
+ reg_write(lynx, PCI_INT_ENABLE, 0);
+ free_irq(lynx->pci_device->irq, lynx);
+
+fail_deallocate_buffers:
+ if (lynx->rcv_start_pcl)
+ dma_free_coherent(&lynx->pci_device->dev, sizeof(struct pcl),
+ lynx->rcv_start_pcl,
+ lynx->rcv_start_pcl_bus);
+ if (lynx->rcv_pcl)
+ dma_free_coherent(&lynx->pci_device->dev, sizeof(struct pcl),
+ lynx->rcv_pcl, lynx->rcv_pcl_bus);
+ if (lynx->rcv_buffer)
+ dma_free_coherent(&lynx->pci_device->dev, RCV_BUFFER_SIZE,
+ lynx->rcv_buffer, lynx->rcv_buffer_bus);
+ iounmap(lynx->registers);
+
+fail_deallocate_lynx:
+ kfree(lynx);
+
+fail_disable:
+ pci_disable_device(dev);
+
+ return ret;
+}
+
+static struct pci_device_id pci_table[] = {
+ {
+ .vendor = PCI_VENDOR_ID_TI,
+ .device = PCI_DEVICE_ID_TI_PCILYNX,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(pci, pci_table);
+
+static struct pci_driver lynx_pci_driver = {
+ .name = driver_name,
+ .id_table = pci_table,
+ .probe = add_card,
+ .remove = remove_card,
+};
+
+module_pci_driver(lynx_pci_driver);
+
+MODULE_AUTHOR("Kristian Hoegsberg");
+MODULE_DESCRIPTION("Snoop mode driver for TI pcilynx 1394 controllers");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firewire/nosy.h b/drivers/firewire/nosy.h
new file mode 100644
index 000000000..4078d69e9
--- /dev/null
+++ b/drivers/firewire/nosy.h
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Chip register definitions for PCILynx chipset. Based on pcilynx.h
+ * from the Linux 1394 drivers, but modified a bit so the names here
+ * match the specification exactly (even though they have weird names,
+ * like xxx_OVER_FLOW, or arbitrary abbreviations like SNTRJ for "sent
+ * reject" etc.)
+ */
+
+#define PCILYNX_MAX_REGISTER 0xfff
+#define PCILYNX_MAX_MEMORY 0xffff
+
+#define PCI_LATENCY_CACHELINE 0x0c
+
+#define MISC_CONTROL 0x40
+#define MISC_CONTROL_SWRESET (1<<0)
+
+#define SERIAL_EEPROM_CONTROL 0x44
+
+#define PCI_INT_STATUS 0x48
+#define PCI_INT_ENABLE 0x4c
+/* status and enable have identical bit numbers */
+#define PCI_INT_INT_PEND (1<<31)
+#define PCI_INT_FRC_INT (1<<30)
+#define PCI_INT_SLV_ADR_PERR (1<<28)
+#define PCI_INT_SLV_DAT_PERR (1<<27)
+#define PCI_INT_MST_DAT_PERR (1<<26)
+#define PCI_INT_MST_DEV_TO (1<<25)
+#define PCI_INT_INT_SLV_TO (1<<23)
+#define PCI_INT_AUX_TO (1<<18)
+#define PCI_INT_AUX_INT (1<<17)
+#define PCI_INT_P1394_INT (1<<16)
+#define PCI_INT_DMA4_PCL (1<<9)
+#define PCI_INT_DMA4_HLT (1<<8)
+#define PCI_INT_DMA3_PCL (1<<7)
+#define PCI_INT_DMA3_HLT (1<<6)
+#define PCI_INT_DMA2_PCL (1<<5)
+#define PCI_INT_DMA2_HLT (1<<4)
+#define PCI_INT_DMA1_PCL (1<<3)
+#define PCI_INT_DMA1_HLT (1<<2)
+#define PCI_INT_DMA0_PCL (1<<1)
+#define PCI_INT_DMA0_HLT (1<<0)
+/* all DMA interrupts combined: */
+#define PCI_INT_DMA_ALL 0x3ff
+
+#define PCI_INT_DMA_HLT(chan) (1 << (chan * 2))
+#define PCI_INT_DMA_PCL(chan) (1 << (chan * 2 + 1))
+
+#define LBUS_ADDR 0xb4
+#define LBUS_ADDR_SEL_RAM (0x0<<16)
+#define LBUS_ADDR_SEL_ROM (0x1<<16)
+#define LBUS_ADDR_SEL_AUX (0x2<<16)
+#define LBUS_ADDR_SEL_ZV (0x3<<16)
+
+#define GPIO_CTRL_A 0xb8
+#define GPIO_CTRL_B 0xbc
+#define GPIO_DATA_BASE 0xc0
+
+#define DMA_BREG(base, chan) (base + chan * 0x20)
+#define DMA_SREG(base, chan) (base + chan * 0x10)
+
+#define PCL_NEXT_INVALID (1<<0)
+
+/* transfer commands */
+#define PCL_CMD_RCV (0x1<<24)
+#define PCL_CMD_RCV_AND_UPDATE (0xa<<24)
+#define PCL_CMD_XMT (0x2<<24)
+#define PCL_CMD_UNFXMT (0xc<<24)
+#define PCL_CMD_PCI_TO_LBUS (0x8<<24)
+#define PCL_CMD_LBUS_TO_PCI (0x9<<24)
+
+/* aux commands */
+#define PCL_CMD_NOP (0x0<<24)
+#define PCL_CMD_LOAD (0x3<<24)
+#define PCL_CMD_STOREQ (0x4<<24)
+#define PCL_CMD_STORED (0xb<<24)
+#define PCL_CMD_STORE0 (0x5<<24)
+#define PCL_CMD_STORE1 (0x6<<24)
+#define PCL_CMD_COMPARE (0xe<<24)
+#define PCL_CMD_SWAP_COMPARE (0xf<<24)
+#define PCL_CMD_ADD (0xd<<24)
+#define PCL_CMD_BRANCH (0x7<<24)
+
+/* BRANCH condition codes */
+#define PCL_COND_DMARDY_SET (0x1<<20)
+#define PCL_COND_DMARDY_CLEAR (0x2<<20)
+
+#define PCL_GEN_INTR (1<<19)
+#define PCL_LAST_BUFF (1<<18)
+#define PCL_LAST_CMD (PCL_LAST_BUFF)
+#define PCL_WAITSTAT (1<<17)
+#define PCL_BIGENDIAN (1<<16)
+#define PCL_ISOMODE (1<<12)
+
+#define DMA0_PREV_PCL 0x100
+#define DMA1_PREV_PCL 0x120
+#define DMA2_PREV_PCL 0x140
+#define DMA3_PREV_PCL 0x160
+#define DMA4_PREV_PCL 0x180
+#define DMA_PREV_PCL(chan) (DMA_BREG(DMA0_PREV_PCL, chan))
+
+#define DMA0_CURRENT_PCL 0x104
+#define DMA1_CURRENT_PCL 0x124
+#define DMA2_CURRENT_PCL 0x144
+#define DMA3_CURRENT_PCL 0x164
+#define DMA4_CURRENT_PCL 0x184
+#define DMA_CURRENT_PCL(chan) (DMA_BREG(DMA0_CURRENT_PCL, chan))
+
+#define DMA0_CHAN_STAT 0x10c
+#define DMA1_CHAN_STAT 0x12c
+#define DMA2_CHAN_STAT 0x14c
+#define DMA3_CHAN_STAT 0x16c
+#define DMA4_CHAN_STAT 0x18c
+#define DMA_CHAN_STAT(chan) (DMA_BREG(DMA0_CHAN_STAT, chan))
+/* CHAN_STATUS registers share bits */
+#define DMA_CHAN_STAT_SELFID (1<<31)
+#define DMA_CHAN_STAT_ISOPKT (1<<30)
+#define DMA_CHAN_STAT_PCIERR (1<<29)
+#define DMA_CHAN_STAT_PKTERR (1<<28)
+#define DMA_CHAN_STAT_PKTCMPL (1<<27)
+#define DMA_CHAN_STAT_SPECIALACK (1<<14)
+
+#define DMA0_CHAN_CTRL 0x110
+#define DMA1_CHAN_CTRL 0x130
+#define DMA2_CHAN_CTRL 0x150
+#define DMA3_CHAN_CTRL 0x170
+#define DMA4_CHAN_CTRL 0x190
+#define DMA_CHAN_CTRL(chan) (DMA_BREG(DMA0_CHAN_CTRL, chan))
+/* CHAN_CTRL registers share bits */
+#define DMA_CHAN_CTRL_ENABLE (1<<31)
+#define DMA_CHAN_CTRL_BUSY (1<<30)
+#define DMA_CHAN_CTRL_LINK (1<<29)
+
+#define DMA0_READY 0x114
+#define DMA1_READY 0x134
+#define DMA2_READY 0x154
+#define DMA3_READY 0x174
+#define DMA4_READY 0x194
+#define DMA_READY(chan) (DMA_BREG(DMA0_READY, chan))
+
+#define DMA_GLOBAL_REGISTER 0x908
+
+#define FIFO_SIZES 0xa00
+
+#define FIFO_CONTROL 0xa10
+#define FIFO_CONTROL_GRF_FLUSH (1<<4)
+#define FIFO_CONTROL_ITF_FLUSH (1<<3)
+#define FIFO_CONTROL_ATF_FLUSH (1<<2)
+
+#define FIFO_XMIT_THRESHOLD 0xa14
+
+#define DMA0_WORD0_CMP_VALUE 0xb00
+#define DMA1_WORD0_CMP_VALUE 0xb10
+#define DMA2_WORD0_CMP_VALUE 0xb20
+#define DMA3_WORD0_CMP_VALUE 0xb30
+#define DMA4_WORD0_CMP_VALUE 0xb40
+#define DMA_WORD0_CMP_VALUE(chan) (DMA_SREG(DMA0_WORD0_CMP_VALUE, chan))
+
+#define DMA0_WORD0_CMP_ENABLE 0xb04
+#define DMA1_WORD0_CMP_ENABLE 0xb14
+#define DMA2_WORD0_CMP_ENABLE 0xb24
+#define DMA3_WORD0_CMP_ENABLE 0xb34
+#define DMA4_WORD0_CMP_ENABLE 0xb44
+#define DMA_WORD0_CMP_ENABLE(chan) (DMA_SREG(DMA0_WORD0_CMP_ENABLE, chan))
+
+#define DMA0_WORD1_CMP_VALUE 0xb08
+#define DMA1_WORD1_CMP_VALUE 0xb18
+#define DMA2_WORD1_CMP_VALUE 0xb28
+#define DMA3_WORD1_CMP_VALUE 0xb38
+#define DMA4_WORD1_CMP_VALUE 0xb48
+#define DMA_WORD1_CMP_VALUE(chan) (DMA_SREG(DMA0_WORD1_CMP_VALUE, chan))
+
+#define DMA0_WORD1_CMP_ENABLE 0xb0c
+#define DMA1_WORD1_CMP_ENABLE 0xb1c
+#define DMA2_WORD1_CMP_ENABLE 0xb2c
+#define DMA3_WORD1_CMP_ENABLE 0xb3c
+#define DMA4_WORD1_CMP_ENABLE 0xb4c
+#define DMA_WORD1_CMP_ENABLE(chan) (DMA_SREG(DMA0_WORD1_CMP_ENABLE, chan))
+/* word 1 compare enable flags */
+#define DMA_WORD1_CMP_MATCH_OTHERBUS (1<<15)
+#define DMA_WORD1_CMP_MATCH_BROADCAST (1<<14)
+#define DMA_WORD1_CMP_MATCH_BUS_BCAST (1<<13)
+#define DMA_WORD1_CMP_MATCH_LOCAL_NODE (1<<12)
+#define DMA_WORD1_CMP_MATCH_EXACT (1<<11)
+#define DMA_WORD1_CMP_ENABLE_SELF_ID (1<<10)
+#define DMA_WORD1_CMP_ENABLE_MASTER (1<<8)
+
+#define LINK_ID 0xf00
+#define LINK_ID_BUS(id) (id<<22)
+#define LINK_ID_NODE(id) (id<<16)
+
+#define LINK_CONTROL 0xf04
+#define LINK_CONTROL_BUSY (1<<29)
+#define LINK_CONTROL_TX_ISO_EN (1<<26)
+#define LINK_CONTROL_RX_ISO_EN (1<<25)
+#define LINK_CONTROL_TX_ASYNC_EN (1<<24)
+#define LINK_CONTROL_RX_ASYNC_EN (1<<23)
+#define LINK_CONTROL_RESET_TX (1<<21)
+#define LINK_CONTROL_RESET_RX (1<<20)
+#define LINK_CONTROL_CYCMASTER (1<<11)
+#define LINK_CONTROL_CYCSOURCE (1<<10)
+#define LINK_CONTROL_CYCTIMEREN (1<<9)
+#define LINK_CONTROL_RCV_CMP_VALID (1<<7)
+#define LINK_CONTROL_SNOOP_ENABLE (1<<6)
+
+#define CYCLE_TIMER 0xf08
+
+#define LINK_PHY 0xf0c
+#define LINK_PHY_READ (1<<31)
+#define LINK_PHY_WRITE (1<<30)
+#define LINK_PHY_ADDR(addr) (addr<<24)
+#define LINK_PHY_WDATA(data) (data<<16)
+#define LINK_PHY_RADDR(addr) (addr<<8)
+
+#define LINK_INT_STATUS 0xf14
+#define LINK_INT_ENABLE 0xf18
+/* status and enable have identical bit numbers */
+#define LINK_INT_LINK_INT (1<<31)
+#define LINK_INT_PHY_TIME_OUT (1<<30)
+#define LINK_INT_PHY_REG_RCVD (1<<29)
+#define LINK_INT_PHY_BUSRESET (1<<28)
+#define LINK_INT_TX_RDY (1<<26)
+#define LINK_INT_RX_DATA_RDY (1<<25)
+#define LINK_INT_IT_STUCK (1<<20)
+#define LINK_INT_AT_STUCK (1<<19)
+#define LINK_INT_SNTRJ (1<<17)
+#define LINK_INT_HDR_ERR (1<<16)
+#define LINK_INT_TC_ERR (1<<15)
+#define LINK_INT_CYC_SEC (1<<11)
+#define LINK_INT_CYC_STRT (1<<10)
+#define LINK_INT_CYC_DONE (1<<9)
+#define LINK_INT_CYC_PEND (1<<8)
+#define LINK_INT_CYC_LOST (1<<7)
+#define LINK_INT_CYC_ARB_FAILED (1<<6)
+#define LINK_INT_GRF_OVER_FLOW (1<<5)
+#define LINK_INT_ITF_UNDER_FLOW (1<<4)
+#define LINK_INT_ATF_UNDER_FLOW (1<<3)
+#define LINK_INT_IARB_FAILED (1<<0)
diff --git a/drivers/firewire/ohci-serdes-test.c b/drivers/firewire/ohci-serdes-test.c
new file mode 100644
index 000000000..258f66861
--- /dev/null
+++ b/drivers/firewire/ohci-serdes-test.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// ohci-serdes-test.c - An application of Kunit to check serialization/deserialization of data in
+// buffers and registers defined in 1394 OHCI specification.
+//
+// Copyright (c) 2024 Takashi Sakamoto
+
+#include <kunit/test.h>
+
+#include "ohci.h"
+
+
+static void test_self_id_count_register_deserialization(struct kunit *test)
+{
+ const u32 expected = 0x803d0594;
+
+ bool is_error = ohci1394_self_id_count_is_error(expected);
+ u8 generation = ohci1394_self_id_count_get_generation(expected);
+ u32 size = ohci1394_self_id_count_get_size(expected);
+
+ KUNIT_EXPECT_TRUE(test, is_error);
+ KUNIT_EXPECT_EQ(test, 0x3d, generation);
+ KUNIT_EXPECT_EQ(test, 0x165, size);
+}
+
+static void test_self_id_receive_buffer_deserialization(struct kunit *test)
+{
+ const u32 buffer[] = {
+ 0x0006f38b,
+ 0x807fcc56,
+ 0x7f8033a9,
+ 0x8145cc5e,
+ 0x7eba33a1,
+ };
+
+ u8 generation = ohci1394_self_id_receive_q0_get_generation(buffer[0]);
+ u16 timestamp = ohci1394_self_id_receive_q0_get_timestamp(buffer[0]);
+
+ KUNIT_EXPECT_EQ(test, 0x6, generation);
+ KUNIT_EXPECT_EQ(test, 0xf38b, timestamp);
+}
+
+static void test_at_data_serdes(struct kunit *test)
+{
+ static const __le32 expected[] = {
+ cpu_to_le32(0x00020e80),
+ cpu_to_le32(0xffc2ffff),
+ cpu_to_le32(0xe0000000),
+ };
+ __le32 quadlets[] = {0, 0, 0};
+ bool has_src_bus_id = ohci1394_at_data_get_src_bus_id(expected);
+ unsigned int speed = ohci1394_at_data_get_speed(expected);
+ unsigned int tlabel = ohci1394_at_data_get_tlabel(expected);
+ unsigned int retry = ohci1394_at_data_get_retry(expected);
+ unsigned int tcode = ohci1394_at_data_get_tcode(expected);
+ 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_EQ(test, 0x02, speed);
+ KUNIT_EXPECT_EQ(test, 0x03, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x02, retry);
+ KUNIT_EXPECT_EQ(test, 0x08, tcode);
+
+ ohci1394_at_data_set_src_bus_id(quadlets, has_src_bus_id);
+ ohci1394_at_data_set_speed(quadlets, speed);
+ ohci1394_at_data_set_tlabel(quadlets, tlabel);
+ ohci1394_at_data_set_retry(quadlets, retry);
+ ohci1394_at_data_set_tcode(quadlets, tcode);
+ ohci1394_at_data_set_destination_id(quadlets, destination_id);
+ ohci1394_at_data_set_destination_offset(quadlets, destination_offset);
+
+ KUNIT_EXPECT_MEMEQ(test, quadlets, expected, sizeof(expected));
+}
+
+static void test_it_data_serdes(struct kunit *test)
+{
+ static const __le32 expected[] = {
+ cpu_to_le32(0x000349a7),
+ cpu_to_le32(0x02300000),
+ };
+ __le32 quadlets[] = {0, 0};
+ unsigned int scode = ohci1394_it_data_get_speed(expected);
+ unsigned int tag = ohci1394_it_data_get_tag(expected);
+ unsigned int channel = ohci1394_it_data_get_channel(expected);
+ unsigned int tcode = ohci1394_it_data_get_tcode(expected);
+ unsigned int sync = ohci1394_it_data_get_sync(expected);
+ unsigned int data_length = ohci1394_it_data_get_data_length(expected);
+
+ KUNIT_EXPECT_EQ(test, 0x03, scode);
+ KUNIT_EXPECT_EQ(test, 0x01, tag);
+ KUNIT_EXPECT_EQ(test, 0x09, channel);
+ KUNIT_EXPECT_EQ(test, 0x0a, tcode);
+ KUNIT_EXPECT_EQ(test, 0x7, sync);
+ KUNIT_EXPECT_EQ(test, 0x0230, data_length);
+
+ ohci1394_it_data_set_speed(quadlets, scode);
+ ohci1394_it_data_set_tag(quadlets, tag);
+ ohci1394_it_data_set_channel(quadlets, channel);
+ ohci1394_it_data_set_tcode(quadlets, tcode);
+ ohci1394_it_data_set_sync(quadlets, sync);
+ ohci1394_it_data_set_data_length(quadlets, data_length);
+
+ KUNIT_EXPECT_MEMEQ(test, quadlets, expected, sizeof(expected));
+}
+
+static struct kunit_case ohci_serdes_test_cases[] = {
+ KUNIT_CASE(test_self_id_count_register_deserialization),
+ KUNIT_CASE(test_self_id_receive_buffer_deserialization),
+ KUNIT_CASE(test_at_data_serdes),
+ KUNIT_CASE(test_it_data_serdes),
+ {}
+};
+
+static struct kunit_suite ohci_serdes_test_suite = {
+ .name = "firewire-ohci-serdes",
+ .test_cases = ohci_serdes_test_cases,
+};
+kunit_test_suite(ohci_serdes_test_suite);
+
+MODULE_DESCRIPTION("FireWire buffers and registers serialization/deserialization unit test suite");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c
new file mode 100644
index 000000000..46dd53ece
--- /dev/null
+++ b/drivers/firewire/ohci.c
@@ -0,0 +1,3835 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for OHCI 1394 controllers
+ *
+ * Copyright (C) 2003-2006 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+
+#include <asm/byteorder.h>
+#include <asm/page.h>
+
+#ifdef CONFIG_PPC_PMAC
+#include <asm/pmac_feature.h>
+#endif
+
+#include "core.h"
+#include "ohci.h"
+#include "packet-header-definitions.h"
+#include "phy-packet-definitions.h"
+
+#include <trace/events/firewire.h>
+
+static u32 cond_le32_to_cpu(__le32 value, bool has_be_header_quirk);
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/firewire_ohci.h>
+
+#define ohci_notice(ohci, f, args...) dev_notice(ohci->card.device, f, ##args)
+#define ohci_err(ohci, f, args...) dev_err(ohci->card.device, f, ##args)
+
+#define DESCRIPTOR_OUTPUT_MORE 0
+#define DESCRIPTOR_OUTPUT_LAST (1 << 12)
+#define DESCRIPTOR_INPUT_MORE (2 << 12)
+#define DESCRIPTOR_INPUT_LAST (3 << 12)
+#define DESCRIPTOR_STATUS (1 << 11)
+#define DESCRIPTOR_KEY_IMMEDIATE (2 << 8)
+#define DESCRIPTOR_PING (1 << 7)
+#define DESCRIPTOR_YY (1 << 6)
+#define DESCRIPTOR_NO_IRQ (0 << 4)
+#define DESCRIPTOR_IRQ_ERROR (1 << 4)
+#define DESCRIPTOR_IRQ_ALWAYS (3 << 4)
+#define DESCRIPTOR_BRANCH_ALWAYS (3 << 2)
+#define DESCRIPTOR_WAIT (3 << 0)
+
+#define DESCRIPTOR_CMD (0xf << 12)
+
+struct descriptor {
+ __le16 req_count;
+ __le16 control;
+ __le32 data_address;
+ __le32 branch_address;
+ __le16 res_count;
+ __le16 transfer_status;
+} __aligned(16);
+
+#define CONTROL_SET(regs) (regs)
+#define CONTROL_CLEAR(regs) ((regs) + 4)
+#define COMMAND_PTR(regs) ((regs) + 12)
+#define CONTEXT_MATCH(regs) ((regs) + 16)
+
+#define AR_BUFFER_SIZE (32*1024)
+#define AR_BUFFERS_MIN DIV_ROUND_UP(AR_BUFFER_SIZE, PAGE_SIZE)
+/* we need at least two pages for proper list management */
+#define AR_BUFFERS MAX(2, AR_BUFFERS_MIN)
+
+#define MAX_ASYNC_PAYLOAD 4096
+#define MAX_AR_PACKET_SIZE (16 + MAX_ASYNC_PAYLOAD + 4)
+#define AR_WRAPAROUND_PAGES DIV_ROUND_UP(MAX_AR_PACKET_SIZE, PAGE_SIZE)
+
+struct ar_context {
+ struct fw_ohci *ohci;
+ struct page *pages[AR_BUFFERS];
+ void *buffer;
+ dma_addr_t dma_addrs[AR_BUFFERS];
+ struct descriptor *descriptors;
+ dma_addr_t descriptors_bus;
+ void *pointer;
+ unsigned int last_buffer_index;
+ u32 regs;
+ struct work_struct work;
+};
+
+struct context;
+
+typedef int (*descriptor_callback_t)(struct context *ctx,
+ struct descriptor *d,
+ struct descriptor *last);
+
+/*
+ * A buffer that contains a block of DMA-able coherent memory used for
+ * storing a portion of a DMA descriptor program.
+ */
+struct descriptor_buffer {
+ struct list_head list;
+ dma_addr_t buffer_bus;
+ size_t buffer_size;
+ size_t used;
+ struct descriptor buffer[];
+};
+
+struct context {
+ struct fw_ohci *ohci;
+ u32 regs;
+ int total_allocation;
+ u32 current_bus;
+ bool running;
+
+ /*
+ * List of page-sized buffers for storing DMA descriptors.
+ * Head of list contains buffers in use and tail of list contains
+ * free buffers.
+ */
+ struct list_head buffer_list;
+
+ /*
+ * Pointer to a buffer inside buffer_list that contains the tail
+ * end of the current DMA program.
+ */
+ struct descriptor_buffer *buffer_tail;
+
+ /*
+ * The descriptor containing the branch address of the first
+ * descriptor that has not yet been filled by the device.
+ */
+ struct descriptor *last;
+
+ /*
+ * The last descriptor block in the DMA program. It contains the branch
+ * address that must be updated upon appending a new descriptor.
+ */
+ struct descriptor *prev;
+ int prev_z;
+
+ descriptor_callback_t callback;
+};
+
+struct at_context {
+ struct context context;
+ struct work_struct work;
+ bool flushing;
+};
+
+struct iso_context {
+ struct fw_iso_context base;
+ struct context context;
+ unsigned long flushing_completions;
+ u8 sync;
+ u8 tags;
+ union {
+ struct {
+ u16 last_timestamp;
+ size_t header_length;
+ void *header;
+ } sc;
+ struct {
+ u32 buffer_bus;
+ u16 completed;
+ } mc;
+ };
+};
+
+#define CONFIG_ROM_SIZE (CSR_CONFIG_ROM_END - CSR_CONFIG_ROM)
+
+struct fw_ohci {
+ struct fw_card card;
+
+ __iomem char *registers;
+ int node_id;
+ int generation;
+ int request_generation; /* for timestamping incoming requests */
+ unsigned quirks;
+ unsigned int pri_req_max;
+ u32 bus_time;
+ bool bus_time_running;
+ bool is_root;
+ bool csr_state_setclear_abdicate;
+ int n_ir;
+ int n_it;
+ /*
+ * Spinlock for accessing fw_ohci data. Never call out of
+ * this driver with this lock held.
+ */
+ spinlock_t lock;
+
+ struct mutex phy_reg_mutex;
+
+ void *misc_buffer;
+ dma_addr_t misc_buffer_bus;
+
+ struct ar_context ar_request_ctx;
+ struct ar_context ar_response_ctx;
+ struct at_context at_request_ctx;
+ struct at_context at_response_ctx;
+
+ u32 it_context_support;
+ u32 it_context_mask; /* unoccupied IT contexts */
+ struct iso_context *it_context_list;
+ u64 ir_context_channels; /* unoccupied channels */
+ u32 ir_context_support;
+ u32 ir_context_mask; /* unoccupied IR contexts */
+ struct iso_context *ir_context_list;
+ u64 mc_channels; /* channels in use by the multichannel IR context */
+ bool mc_allocated;
+
+ __be32 *config_rom;
+ dma_addr_t config_rom_bus;
+ __be32 *next_config_rom;
+ dma_addr_t next_config_rom_bus;
+ __be32 next_header;
+
+ __le32 *self_id;
+ dma_addr_t self_id_bus;
+
+ u32 self_id_buffer[512];
+};
+
+static inline struct fw_ohci *fw_ohci(struct fw_card *card)
+{
+ return container_of(card, struct fw_ohci, card);
+}
+
+#define IT_CONTEXT_CYCLE_MATCH_ENABLE 0x80000000
+#define IR_CONTEXT_BUFFER_FILL 0x80000000
+#define IR_CONTEXT_ISOCH_HEADER 0x40000000
+#define IR_CONTEXT_CYCLE_MATCH_ENABLE 0x20000000
+#define IR_CONTEXT_MULTI_CHANNEL_MODE 0x10000000
+#define IR_CONTEXT_DUAL_BUFFER_MODE 0x08000000
+
+#define CONTEXT_RUN 0x8000
+#define CONTEXT_WAKE 0x1000
+#define CONTEXT_DEAD 0x0800
+#define CONTEXT_ACTIVE 0x0400
+
+#define OHCI1394_MAX_AT_REQ_RETRIES 0xf
+#define OHCI1394_MAX_AT_RESP_RETRIES 0x2
+#define OHCI1394_MAX_PHYS_RESP_RETRIES 0x8
+
+#define OHCI1394_REGISTER_SIZE 0x800
+#define OHCI1394_PCI_HCI_Control 0x40
+#define SELF_ID_BUF_SIZE 0x800
+#define OHCI_VERSION_1_1 0x010010
+
+static char ohci_driver_name[] = KBUILD_MODNAME;
+
+#define PCI_VENDOR_ID_PINNACLE_SYSTEMS 0x11bd
+#define PCI_DEVICE_ID_AGERE_FW643 0x5901
+#define PCI_DEVICE_ID_CREATIVE_SB1394 0x4001
+#define PCI_DEVICE_ID_JMICRON_JMB38X_FW 0x2380
+#define PCI_DEVICE_ID_TI_TSB12LV22 0x8009
+#define PCI_DEVICE_ID_TI_TSB12LV26 0x8020
+#define PCI_DEVICE_ID_TI_TSB82AA2 0x8025
+#define PCI_DEVICE_ID_VIA_VT630X 0x3044
+#define PCI_REV_ID_VIA_VT6306 0x46
+#define PCI_DEVICE_ID_VIA_VT6315 0x3403
+
+#define QUIRK_CYCLE_TIMER 0x1
+#define QUIRK_RESET_PACKET 0x2
+#define QUIRK_BE_HEADERS 0x4
+#define QUIRK_NO_1394A 0x8
+#define QUIRK_NO_MSI 0x10
+#define QUIRK_TI_SLLZ059 0x20
+#define QUIRK_IR_WAKE 0x40
+
+// On PCI Express Root Complex in any type of AMD Ryzen machine, VIA VT6306/6307/6308 with Asmedia
+// ASM1083/1085 brings an inconvenience that the read accesses to 'Isochronous Cycle Timer' register
+// (at offset 0xf0 in PCI I/O space) often causes unexpected system reboot. The mechanism is not
+// clear, since the read access to the other registers is enough safe; e.g. 'Node ID' register,
+// while it is probable due to detection of any type of PCIe error.
+#define QUIRK_REBOOT_BY_CYCLE_TIMER_READ 0x80000000
+
+#if IS_ENABLED(CONFIG_X86)
+
+static bool has_reboot_by_cycle_timer_read_quirk(const struct fw_ohci *ohci)
+{
+ return !!(ohci->quirks & QUIRK_REBOOT_BY_CYCLE_TIMER_READ);
+}
+
+#define PCI_DEVICE_ID_ASMEDIA_ASM108X 0x1080
+
+static bool detect_vt630x_with_asm1083_on_amd_ryzen_machine(const struct pci_dev *pdev)
+{
+ const struct pci_dev *pcie_to_pci_bridge;
+
+ // Detect any type of AMD Ryzen machine.
+ if (!cpu_feature_enabled(X86_FEATURE_ZEN))
+ return false;
+
+ // Detect VIA VT6306/6307/6308.
+ if (pdev->vendor != PCI_VENDOR_ID_VIA)
+ return false;
+ if (pdev->device != PCI_DEVICE_ID_VIA_VT630X)
+ return false;
+
+ // Detect Asmedia ASM1083/1085.
+ pcie_to_pci_bridge = pdev->bus->self;
+ if (pcie_to_pci_bridge->vendor != PCI_VENDOR_ID_ASMEDIA)
+ return false;
+ if (pcie_to_pci_bridge->device != PCI_DEVICE_ID_ASMEDIA_ASM108X)
+ return false;
+
+ return true;
+}
+
+#else
+#define has_reboot_by_cycle_timer_read_quirk(ohci) false
+#define detect_vt630x_with_asm1083_on_amd_ryzen_machine(pdev) false
+#endif
+
+/* In case of multiple matches in ohci_quirks[], only the first one is used. */
+static const struct {
+ unsigned short vendor, device, revision, flags;
+} ohci_quirks[] = {
+ {PCI_VENDOR_ID_AL, PCI_ANY_ID, PCI_ANY_ID,
+ QUIRK_CYCLE_TIMER},
+
+ {PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_UNI_N_FW, PCI_ANY_ID,
+ QUIRK_BE_HEADERS},
+
+ {PCI_VENDOR_ID_ATT, PCI_DEVICE_ID_AGERE_FW643, 6,
+ QUIRK_NO_MSI},
+
+ {PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_SB1394, PCI_ANY_ID,
+ QUIRK_RESET_PACKET},
+
+ {PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_FW, PCI_ANY_ID,
+ QUIRK_NO_MSI},
+
+ {PCI_VENDOR_ID_NEC, PCI_ANY_ID, PCI_ANY_ID,
+ QUIRK_CYCLE_TIMER},
+
+ {PCI_VENDOR_ID_O2, PCI_ANY_ID, PCI_ANY_ID,
+ QUIRK_NO_MSI},
+
+ {PCI_VENDOR_ID_RICOH, PCI_ANY_ID, PCI_ANY_ID,
+ QUIRK_CYCLE_TIMER | QUIRK_NO_MSI},
+
+ {PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TSB12LV22, PCI_ANY_ID,
+ QUIRK_CYCLE_TIMER | QUIRK_RESET_PACKET | QUIRK_NO_1394A},
+
+ {PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TSB12LV26, PCI_ANY_ID,
+ QUIRK_RESET_PACKET | QUIRK_TI_SLLZ059},
+
+ {PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TSB82AA2, PCI_ANY_ID,
+ QUIRK_RESET_PACKET | QUIRK_TI_SLLZ059},
+
+ {PCI_VENDOR_ID_TI, PCI_ANY_ID, PCI_ANY_ID,
+ QUIRK_RESET_PACKET},
+
+ {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VT630X, PCI_REV_ID_VIA_VT6306,
+ QUIRK_CYCLE_TIMER | QUIRK_IR_WAKE},
+
+ {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VT6315, 0,
+ QUIRK_CYCLE_TIMER /* FIXME: necessary? */ | QUIRK_NO_MSI},
+
+ {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VT6315, PCI_ANY_ID,
+ QUIRK_NO_MSI},
+
+ {PCI_VENDOR_ID_VIA, PCI_ANY_ID, PCI_ANY_ID,
+ QUIRK_CYCLE_TIMER | QUIRK_NO_MSI},
+};
+
+/* This overrides anything that was found in ohci_quirks[]. */
+static int param_quirks;
+module_param_named(quirks, param_quirks, int, 0644);
+MODULE_PARM_DESC(quirks, "Chip quirks (default = 0"
+ ", nonatomic cycle timer = " __stringify(QUIRK_CYCLE_TIMER)
+ ", reset packet generation = " __stringify(QUIRK_RESET_PACKET)
+ ", AR/selfID endianness = " __stringify(QUIRK_BE_HEADERS)
+ ", no 1394a enhancements = " __stringify(QUIRK_NO_1394A)
+ ", disable MSI = " __stringify(QUIRK_NO_MSI)
+ ", TI SLLZ059 erratum = " __stringify(QUIRK_TI_SLLZ059)
+ ", IR wake unreliable = " __stringify(QUIRK_IR_WAKE)
+ ")");
+
+static bool param_remote_dma;
+module_param_named(remote_dma, param_remote_dma, bool, 0444);
+MODULE_PARM_DESC(remote_dma, "Enable unfiltered remote DMA (default = N)");
+
+static inline void reg_write(const struct fw_ohci *ohci, int offset, u32 data)
+{
+ writel(data, ohci->registers + offset);
+}
+
+static inline u32 reg_read(const struct fw_ohci *ohci, int offset)
+{
+ return readl(ohci->registers + offset);
+}
+
+static inline void flush_writes(const struct fw_ohci *ohci)
+{
+ /* Do a dummy read to flush writes. */
+ reg_read(ohci, OHCI1394_Version);
+}
+
+/*
+ * Beware! read_phy_reg(), write_phy_reg(), update_phy_reg(), and
+ * read_paged_phy_reg() require the caller to hold ohci->phy_reg_mutex.
+ * In other words, only use ohci_read_phy_reg() and ohci_update_phy_reg()
+ * directly. Exceptions are intrinsically serialized contexts like pci_probe.
+ */
+static int read_phy_reg(struct fw_ohci *ohci, int addr)
+{
+ u32 val;
+ int i;
+
+ reg_write(ohci, OHCI1394_PhyControl, OHCI1394_PhyControl_Read(addr));
+ for (i = 0; i < 3 + 100; i++) {
+ val = reg_read(ohci, OHCI1394_PhyControl);
+ if (!~val)
+ return -ENODEV; /* Card was ejected. */
+
+ if (val & OHCI1394_PhyControl_ReadDone)
+ return OHCI1394_PhyControl_ReadData(val);
+
+ /*
+ * Try a few times without waiting. Sleeping is necessary
+ * only when the link/PHY interface is busy.
+ */
+ if (i >= 3)
+ msleep(1);
+ }
+ ohci_err(ohci, "failed to read phy reg %d\n", addr);
+ dump_stack();
+
+ return -EBUSY;
+}
+
+static int write_phy_reg(const struct fw_ohci *ohci, int addr, u32 val)
+{
+ int i;
+
+ reg_write(ohci, OHCI1394_PhyControl,
+ OHCI1394_PhyControl_Write(addr, val));
+ for (i = 0; i < 3 + 100; i++) {
+ val = reg_read(ohci, OHCI1394_PhyControl);
+ if (!~val)
+ return -ENODEV; /* Card was ejected. */
+
+ if (!(val & OHCI1394_PhyControl_WritePending))
+ return 0;
+
+ if (i >= 3)
+ msleep(1);
+ }
+ ohci_err(ohci, "failed to write phy reg %d, val %u\n", addr, val);
+ dump_stack();
+
+ return -EBUSY;
+}
+
+static int update_phy_reg(struct fw_ohci *ohci, int addr,
+ int clear_bits, int set_bits)
+{
+ int ret = read_phy_reg(ohci, addr);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The interrupt status bits are cleared by writing a one bit.
+ * Avoid clearing them unless explicitly requested in set_bits.
+ */
+ if (addr == 5)
+ clear_bits |= PHY_INT_STATUS_BITS;
+
+ return write_phy_reg(ohci, addr, (ret & ~clear_bits) | set_bits);
+}
+
+static int read_paged_phy_reg(struct fw_ohci *ohci, int page, int addr)
+{
+ int ret;
+
+ ret = update_phy_reg(ohci, 7, PHY_PAGE_SELECT, page << 5);
+ if (ret < 0)
+ return ret;
+
+ return read_phy_reg(ohci, addr);
+}
+
+static int ohci_read_phy_reg(struct fw_card *card, int addr)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+
+ guard(mutex)(&ohci->phy_reg_mutex);
+
+ return read_phy_reg(ohci, addr);
+}
+
+static int ohci_update_phy_reg(struct fw_card *card, int addr,
+ int clear_bits, int set_bits)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+
+ guard(mutex)(&ohci->phy_reg_mutex);
+
+ return update_phy_reg(ohci, addr, clear_bits, set_bits);
+}
+
+static void ar_context_link_page(struct ar_context *ctx, unsigned int index)
+{
+ struct descriptor *d;
+
+ d = &ctx->descriptors[index];
+ d->branch_address &= cpu_to_le32(~0xf);
+ d->res_count = cpu_to_le16(PAGE_SIZE);
+ d->transfer_status = 0;
+
+ wmb(); /* finish init of new descriptors before branch_address update */
+ d = &ctx->descriptors[ctx->last_buffer_index];
+ d->branch_address |= cpu_to_le32(1);
+
+ ctx->last_buffer_index = index;
+
+ reg_write(ctx->ohci, CONTROL_SET(ctx->regs), CONTEXT_WAKE);
+}
+
+static void ar_context_release(struct ar_context *ctx)
+{
+ struct device *dev;
+
+ if (!ctx->buffer)
+ return;
+
+ dev = ctx->ohci->card.device;
+
+ for (int i = 0; i < AR_BUFFERS; ++i) {
+ dma_addr_t dma_addr = ctx->dma_addrs[i];
+ if (dma_addr)
+ dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE);
+ }
+ memset(ctx->dma_addrs, 0, sizeof(ctx->dma_addrs));
+
+ vunmap(ctx->buffer);
+ ctx->buffer = NULL;
+
+ release_pages(ctx->pages, AR_BUFFERS);
+ memset(ctx->pages, 0, sizeof(ctx->pages));
+}
+
+static void ar_context_abort(struct ar_context *ctx, const char *error_msg)
+{
+ struct fw_ohci *ohci = ctx->ohci;
+
+ if (reg_read(ohci, CONTROL_CLEAR(ctx->regs)) & CONTEXT_RUN) {
+ reg_write(ohci, CONTROL_CLEAR(ctx->regs), CONTEXT_RUN);
+ flush_writes(ohci);
+
+ ohci_err(ohci, "AR error: %s; DMA stopped\n", error_msg);
+ }
+ /* FIXME: restart? */
+}
+
+static inline unsigned int ar_next_buffer_index(unsigned int index)
+{
+ return (index + 1) % AR_BUFFERS;
+}
+
+static inline unsigned int ar_first_buffer_index(struct ar_context *ctx)
+{
+ return ar_next_buffer_index(ctx->last_buffer_index);
+}
+
+/*
+ * We search for the buffer that contains the last AR packet DMA data written
+ * by the controller.
+ */
+static unsigned int ar_search_last_active_buffer(struct ar_context *ctx,
+ unsigned int *buffer_offset)
+{
+ unsigned int i, next_i, last = ctx->last_buffer_index;
+ __le16 res_count, next_res_count;
+
+ i = ar_first_buffer_index(ctx);
+ res_count = READ_ONCE(ctx->descriptors[i].res_count);
+
+ /* A buffer that is not yet completely filled must be the last one. */
+ while (i != last && res_count == 0) {
+
+ /* Peek at the next descriptor. */
+ next_i = ar_next_buffer_index(i);
+ rmb(); /* read descriptors in order */
+ next_res_count = READ_ONCE(ctx->descriptors[next_i].res_count);
+ /*
+ * If the next descriptor is still empty, we must stop at this
+ * descriptor.
+ */
+ if (next_res_count == cpu_to_le16(PAGE_SIZE)) {
+ /*
+ * The exception is when the DMA data for one packet is
+ * split over three buffers; in this case, the middle
+ * buffer's descriptor might be never updated by the
+ * controller and look still empty, and we have to peek
+ * at the third one.
+ */
+ if (MAX_AR_PACKET_SIZE > PAGE_SIZE && i != last) {
+ next_i = ar_next_buffer_index(next_i);
+ rmb();
+ next_res_count = READ_ONCE(ctx->descriptors[next_i].res_count);
+ if (next_res_count != cpu_to_le16(PAGE_SIZE))
+ goto next_buffer_is_active;
+ }
+
+ break;
+ }
+
+next_buffer_is_active:
+ i = next_i;
+ res_count = next_res_count;
+ }
+
+ rmb(); /* read res_count before the DMA data */
+
+ *buffer_offset = PAGE_SIZE - le16_to_cpu(res_count);
+ if (*buffer_offset > PAGE_SIZE) {
+ *buffer_offset = 0;
+ ar_context_abort(ctx, "corrupted descriptor");
+ }
+
+ return i;
+}
+
+static void ar_sync_buffers_for_cpu(struct ar_context *ctx,
+ unsigned int end_buffer_index,
+ unsigned int end_buffer_offset)
+{
+ unsigned int i;
+
+ i = ar_first_buffer_index(ctx);
+ while (i != end_buffer_index) {
+ dma_sync_single_for_cpu(ctx->ohci->card.device, ctx->dma_addrs[i], PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ i = ar_next_buffer_index(i);
+ }
+ if (end_buffer_offset > 0)
+ dma_sync_single_for_cpu(ctx->ohci->card.device, ctx->dma_addrs[i],
+ end_buffer_offset, DMA_FROM_DEVICE);
+}
+
+#if defined(CONFIG_PPC_PMAC) && defined(CONFIG_PPC32)
+static u32 cond_le32_to_cpu(__le32 value, bool has_be_header_quirk)
+{
+ return has_be_header_quirk ? (__force __u32)value : le32_to_cpu(value);
+}
+
+static bool has_be_header_quirk(const struct fw_ohci *ohci)
+{
+ return !!(ohci->quirks & QUIRK_BE_HEADERS);
+}
+#else
+static u32 cond_le32_to_cpu(__le32 value, bool has_be_header_quirk __maybe_unused)
+{
+ return le32_to_cpu(value);
+}
+
+static bool has_be_header_quirk(const struct fw_ohci *ohci)
+{
+ return false;
+}
+#endif
+
+static __le32 *handle_ar_packet(struct ar_context *ctx, __le32 *buffer)
+{
+ struct fw_ohci *ohci = ctx->ohci;
+ struct fw_packet p;
+ u32 status, length, tcode;
+ int evt;
+
+ p.header[0] = cond_le32_to_cpu(buffer[0], has_be_header_quirk(ohci));
+ p.header[1] = cond_le32_to_cpu(buffer[1], has_be_header_quirk(ohci));
+ p.header[2] = cond_le32_to_cpu(buffer[2], has_be_header_quirk(ohci));
+
+ tcode = async_header_get_tcode(p.header);
+ switch (tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ case TCODE_READ_QUADLET_RESPONSE:
+ p.header[3] = (__force __u32) buffer[3];
+ p.header_length = 16;
+ p.payload_length = 0;
+ break;
+
+ case TCODE_READ_BLOCK_REQUEST :
+ p.header[3] = cond_le32_to_cpu(buffer[3], has_be_header_quirk(ohci));
+ p.header_length = 16;
+ p.payload_length = 0;
+ break;
+
+ case TCODE_WRITE_BLOCK_REQUEST:
+ case TCODE_READ_BLOCK_RESPONSE:
+ case TCODE_LOCK_REQUEST:
+ case TCODE_LOCK_RESPONSE:
+ p.header[3] = cond_le32_to_cpu(buffer[3], has_be_header_quirk(ohci));
+ p.header_length = 16;
+ p.payload_length = async_header_get_data_length(p.header);
+ if (p.payload_length > MAX_ASYNC_PAYLOAD) {
+ ar_context_abort(ctx, "invalid packet length");
+ return NULL;
+ }
+ break;
+
+ case TCODE_WRITE_RESPONSE:
+ case TCODE_READ_QUADLET_REQUEST:
+ case TCODE_LINK_INTERNAL:
+ p.header_length = 12;
+ p.payload_length = 0;
+ break;
+
+ default:
+ ar_context_abort(ctx, "invalid tcode");
+ return NULL;
+ }
+
+ p.payload = (void *) buffer + p.header_length;
+
+ /* FIXME: What to do about evt_* errors? */
+ length = (p.header_length + p.payload_length + 3) / 4;
+ status = cond_le32_to_cpu(buffer[length], has_be_header_quirk(ohci));
+ evt = (status >> 16) & 0x1f;
+
+ p.ack = evt - 16;
+ p.speed = (status >> 21) & 0x7;
+ p.timestamp = status & 0xffff;
+ p.generation = ohci->request_generation;
+
+ /*
+ * Several controllers, notably from NEC and VIA, forget to
+ * write ack_complete status at PHY packet reception.
+ */
+ if (evt == OHCI1394_evt_no_status && tcode == TCODE_LINK_INTERNAL)
+ p.ack = ACK_COMPLETE;
+
+ /*
+ * The OHCI bus reset handler synthesizes a PHY packet with
+ * the new generation number when a bus reset happens (see
+ * section 8.4.2.3). This helps us determine when a request
+ * was received and make sure we send the response in the same
+ * generation. We only need this for requests; for responses
+ * we use the unique tlabel for finding the matching
+ * request.
+ *
+ * Alas some chips sometimes emit bus reset packets with a
+ * wrong generation. We set the correct generation for these
+ * at a slightly incorrect time (in handle_selfid_complete_event).
+ */
+ if (evt == OHCI1394_evt_bus_reset) {
+ if (!(ohci->quirks & QUIRK_RESET_PACKET))
+ ohci->request_generation = (p.header[2] >> 16) & 0xff;
+ } else if (ctx == &ohci->ar_request_ctx) {
+ fw_core_handle_request(&ohci->card, &p);
+ } else {
+ fw_core_handle_response(&ohci->card, &p);
+ }
+
+ return buffer + length + 1;
+}
+
+static void *handle_ar_packets(struct ar_context *ctx, void *p, void *end)
+{
+ void *next;
+
+ while (p < end) {
+ next = handle_ar_packet(ctx, p);
+ if (!next)
+ return p;
+ p = next;
+ }
+
+ return p;
+}
+
+static void ar_recycle_buffers(struct ar_context *ctx, unsigned int end_buffer)
+{
+ unsigned int i;
+
+ i = ar_first_buffer_index(ctx);
+ while (i != end_buffer) {
+ dma_sync_single_for_device(ctx->ohci->card.device, ctx->dma_addrs[i], PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ ar_context_link_page(ctx, i);
+ i = ar_next_buffer_index(i);
+ }
+}
+
+static void ohci_ar_context_work(struct work_struct *work)
+{
+ struct ar_context *ctx = from_work(ctx, work, work);
+ unsigned int end_buffer_index, end_buffer_offset;
+ void *p, *end;
+
+ p = ctx->pointer;
+ if (!p)
+ return;
+
+ end_buffer_index = ar_search_last_active_buffer(ctx, &end_buffer_offset);
+ ar_sync_buffers_for_cpu(ctx, end_buffer_index, end_buffer_offset);
+ end = ctx->buffer + end_buffer_index * PAGE_SIZE + end_buffer_offset;
+
+ if (end_buffer_index < ar_first_buffer_index(ctx)) {
+ // The filled part of the overall buffer wraps around; handle all packets up to the
+ // buffer end here. If the last packet wraps around, its tail will be visible after
+ // the buffer end because the buffer start pages are mapped there again.
+ void *buffer_end = ctx->buffer + AR_BUFFERS * PAGE_SIZE;
+ p = handle_ar_packets(ctx, p, buffer_end);
+ if (p < buffer_end)
+ goto error;
+ // adjust p to point back into the actual buffer
+ p -= AR_BUFFERS * PAGE_SIZE;
+ }
+
+ p = handle_ar_packets(ctx, p, end);
+ if (p != end) {
+ if (p > end)
+ ar_context_abort(ctx, "inconsistent descriptor");
+ goto error;
+ }
+
+ ctx->pointer = p;
+ ar_recycle_buffers(ctx, end_buffer_index);
+
+ return;
+error:
+ ctx->pointer = NULL;
+}
+
+static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci,
+ unsigned int descriptors_offset, u32 regs)
+{
+ struct device *dev = ohci->card.device;
+ unsigned int i;
+ struct page *pages[AR_BUFFERS + AR_WRAPAROUND_PAGES] = { NULL };
+ dma_addr_t dma_addrs[AR_BUFFERS];
+ void *vaddr;
+ struct descriptor *d;
+
+ ctx->regs = regs;
+ ctx->ohci = ohci;
+ INIT_WORK(&ctx->work, ohci_ar_context_work);
+
+ // Retrieve noncontiguous pages. The descriptors for 1394 OHCI AR DMA contexts have a set
+ // of address and length per each. The reason to use pages is to construct contiguous
+ // address range in kernel virtual address space.
+ unsigned long nr_populated = alloc_pages_bulk(GFP_KERNEL | GFP_DMA32, AR_BUFFERS, pages);
+
+ if (nr_populated != AR_BUFFERS) {
+ release_pages(pages, nr_populated);
+ return -ENOMEM;
+ }
+
+ // Map the pages into contiguous kernel virtual addresses so that the packet data
+ // across the pages can be referred as being contiguous, especially across the last
+ // and first pages.
+ for (i = 0; i < AR_WRAPAROUND_PAGES; i++)
+ pages[AR_BUFFERS + i] = pages[i];
+ vaddr = vmap(pages, ARRAY_SIZE(pages), VM_MAP, PAGE_KERNEL);
+ if (!vaddr) {
+ release_pages(pages, nr_populated);
+ return -ENOMEM;
+ }
+
+ // Retrieve DMA mapping addresses for the pages. They are not contiguous. Maintain the cache
+ // coherency for the pages by hand.
+ for (i = 0; i < AR_BUFFERS; i++) {
+ // The dma_map_phys() with a physical address per page is available here, instead.
+ dma_addr_t dma_addr = dma_map_page(dev, pages[i], 0, PAGE_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(dev, dma_addr))
+ break;
+ dma_addrs[i] = dma_addr;
+ dma_sync_single_for_device(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE);
+ }
+ if (i < AR_BUFFERS) {
+ while (i-- > 0)
+ dma_unmap_page(dev, dma_addrs[i], PAGE_SIZE, DMA_FROM_DEVICE);
+ vunmap(vaddr);
+ release_pages(pages, nr_populated);
+ return -ENOMEM;
+ }
+
+ memcpy(ctx->dma_addrs, dma_addrs, sizeof(ctx->dma_addrs));
+ ctx->buffer = vaddr;
+ memcpy(ctx->pages, pages, sizeof(ctx->pages));
+
+ ctx->descriptors = ohci->misc_buffer + descriptors_offset;
+ ctx->descriptors_bus = ohci->misc_buffer_bus + descriptors_offset;
+
+ for (i = 0; i < AR_BUFFERS; i++) {
+ d = &ctx->descriptors[i];
+ d->req_count = cpu_to_le16(PAGE_SIZE);
+ d->control = cpu_to_le16(DESCRIPTOR_INPUT_MORE |
+ DESCRIPTOR_STATUS |
+ DESCRIPTOR_BRANCH_ALWAYS);
+ d->data_address = cpu_to_le32(ctx->dma_addrs[i]);
+ d->branch_address = cpu_to_le32(ctx->descriptors_bus +
+ ar_next_buffer_index(i) * sizeof(struct descriptor));
+ }
+
+ return 0;
+}
+
+static void ar_context_run(struct ar_context *ctx)
+{
+ unsigned int i;
+
+ for (i = 0; i < AR_BUFFERS; i++)
+ ar_context_link_page(ctx, i);
+
+ ctx->pointer = ctx->buffer;
+
+ reg_write(ctx->ohci, COMMAND_PTR(ctx->regs), ctx->descriptors_bus | 1);
+ reg_write(ctx->ohci, CONTROL_SET(ctx->regs), CONTEXT_RUN);
+}
+
+static struct descriptor *find_branch_descriptor(struct descriptor *d, int z)
+{
+ __le16 branch;
+
+ branch = d->control & cpu_to_le16(DESCRIPTOR_BRANCH_ALWAYS);
+
+ /* figure out which descriptor the branch address goes in */
+ if (z == 2 && branch == cpu_to_le16(DESCRIPTOR_BRANCH_ALWAYS))
+ return d;
+ else
+ return d + z - 1;
+}
+
+static void context_retire_descriptors(struct context *ctx)
+{
+ struct descriptor *d, *last;
+ u32 address;
+ int z;
+ struct descriptor_buffer *desc;
+
+ desc = list_entry(ctx->buffer_list.next,
+ struct descriptor_buffer, list);
+ last = ctx->last;
+ while (last->branch_address != 0) {
+ struct descriptor_buffer *old_desc = desc;
+ address = le32_to_cpu(last->branch_address);
+ z = address & 0xf;
+ address &= ~0xf;
+ ctx->current_bus = address;
+
+ /* If the branch address points to a buffer outside of the
+ * current buffer, advance to the next buffer. */
+ if (address < desc->buffer_bus ||
+ address >= desc->buffer_bus + desc->used)
+ desc = list_entry(desc->list.next,
+ struct descriptor_buffer, list);
+ d = desc->buffer + (address - desc->buffer_bus) / sizeof(*d);
+ last = find_branch_descriptor(d, z);
+
+ if (!ctx->callback(ctx, d, last))
+ break;
+
+ if (old_desc != desc) {
+ // If we've advanced to the next buffer, move the previous buffer to the
+ // free list.
+ old_desc->used = 0;
+ guard(spinlock_irqsave)(&ctx->ohci->lock);
+ list_move_tail(&old_desc->list, &ctx->buffer_list);
+ }
+ ctx->last = last;
+ }
+}
+
+static void ohci_at_context_work(struct work_struct *work)
+{
+ struct at_context *ctx = from_work(ctx, work, work);
+
+ context_retire_descriptors(&ctx->context);
+}
+
+static void ohci_isoc_context_work(struct work_struct *work)
+{
+ struct fw_iso_context *base = from_work(base, work, work);
+ struct iso_context *isoc_ctx = container_of(base, struct iso_context, base);
+
+ context_retire_descriptors(&isoc_ctx->context);
+}
+
+/*
+ * Allocate a new buffer and add it to the list of free buffers for this
+ * context. Must be called with ohci->lock held.
+ */
+static int context_add_buffer(struct context *ctx)
+{
+ struct descriptor_buffer *desc;
+ dma_addr_t bus_addr;
+ int offset;
+
+ /*
+ * 16MB of descriptors should be far more than enough for any DMA
+ * program. This will catch run-away userspace or DoS attacks.
+ */
+ if (ctx->total_allocation >= 16*1024*1024)
+ return -ENOMEM;
+
+ desc = dmam_alloc_coherent(ctx->ohci->card.device, PAGE_SIZE, &bus_addr, GFP_ATOMIC);
+ if (!desc)
+ return -ENOMEM;
+
+ offset = (void *)&desc->buffer - (void *)desc;
+ /*
+ * Some controllers, like JMicron ones, always issue 0x20-byte DMA reads
+ * for descriptors, even 0x10-byte ones. This can cause page faults when
+ * an IOMMU is in use and the oversized read crosses a page boundary.
+ * Work around this by always leaving at least 0x10 bytes of padding.
+ */
+ desc->buffer_size = PAGE_SIZE - offset - 0x10;
+ desc->buffer_bus = bus_addr + offset;
+ desc->used = 0;
+
+ list_add_tail(&desc->list, &ctx->buffer_list);
+ ctx->total_allocation += PAGE_SIZE;
+
+ return 0;
+}
+
+static int context_init(struct context *ctx, struct fw_ohci *ohci,
+ u32 regs, descriptor_callback_t callback)
+{
+ ctx->ohci = ohci;
+ ctx->regs = regs;
+ ctx->total_allocation = 0;
+
+ INIT_LIST_HEAD(&ctx->buffer_list);
+ if (context_add_buffer(ctx) < 0)
+ return -ENOMEM;
+
+ ctx->buffer_tail = list_entry(ctx->buffer_list.next,
+ struct descriptor_buffer, list);
+
+ ctx->callback = callback;
+
+ /*
+ * We put a dummy descriptor in the buffer that has a NULL
+ * branch address and looks like it's been sent. That way we
+ * have a descriptor to append DMA programs to.
+ */
+ memset(ctx->buffer_tail->buffer, 0, sizeof(*ctx->buffer_tail->buffer));
+ ctx->buffer_tail->buffer->control = cpu_to_le16(DESCRIPTOR_OUTPUT_LAST);
+ ctx->buffer_tail->buffer->transfer_status = cpu_to_le16(0x8011);
+ ctx->buffer_tail->used += sizeof(*ctx->buffer_tail->buffer);
+ ctx->last = ctx->buffer_tail->buffer;
+ ctx->prev = ctx->buffer_tail->buffer;
+ ctx->prev_z = 1;
+
+ return 0;
+}
+
+static void context_release(struct context *ctx)
+{
+ struct fw_card *card = &ctx->ohci->card;
+ struct descriptor_buffer *desc, *tmp;
+
+ list_for_each_entry_safe(desc, tmp, &ctx->buffer_list, list) {
+ dmam_free_coherent(card->device, PAGE_SIZE, desc,
+ desc->buffer_bus - ((void *)&desc->buffer - (void *)desc));
+ }
+}
+
+/* Must be called with ohci->lock held */
+static struct descriptor *context_get_descriptors(struct context *ctx,
+ int z, dma_addr_t *d_bus)
+{
+ struct descriptor *d = NULL;
+ struct descriptor_buffer *desc = ctx->buffer_tail;
+
+ if (z * sizeof(*d) > desc->buffer_size)
+ return NULL;
+
+ if (z * sizeof(*d) > desc->buffer_size - desc->used) {
+ /* No room for the descriptor in this buffer, so advance to the
+ * next one. */
+
+ if (desc->list.next == &ctx->buffer_list) {
+ /* If there is no free buffer next in the list,
+ * allocate one. */
+ if (context_add_buffer(ctx) < 0)
+ return NULL;
+ }
+ desc = list_entry(desc->list.next,
+ struct descriptor_buffer, list);
+ ctx->buffer_tail = desc;
+ }
+
+ d = desc->buffer + desc->used / sizeof(*d);
+ memset(d, 0, z * sizeof(*d));
+ *d_bus = desc->buffer_bus + desc->used;
+
+ return d;
+}
+
+static void context_run(struct context *ctx, u32 extra)
+{
+ struct fw_ohci *ohci = ctx->ohci;
+
+ reg_write(ohci, COMMAND_PTR(ctx->regs),
+ le32_to_cpu(ctx->last->branch_address));
+ reg_write(ohci, CONTROL_CLEAR(ctx->regs), ~0);
+ reg_write(ohci, CONTROL_SET(ctx->regs), CONTEXT_RUN | extra);
+ ctx->running = true;
+ flush_writes(ohci);
+}
+
+static void context_append(struct context *ctx,
+ struct descriptor *d, int z, int extra)
+{
+ dma_addr_t d_bus;
+ struct descriptor_buffer *desc = ctx->buffer_tail;
+ struct descriptor *d_branch;
+
+ d_bus = desc->buffer_bus + (d - desc->buffer) * sizeof(*d);
+
+ desc->used += (z + extra) * sizeof(*d);
+
+ wmb(); /* finish init of new descriptors before branch_address update */
+
+ d_branch = find_branch_descriptor(ctx->prev, ctx->prev_z);
+ d_branch->branch_address = cpu_to_le32(d_bus | z);
+
+ /*
+ * VT6306 incorrectly checks only the single descriptor at the
+ * CommandPtr when the wake bit is written, so if it's a
+ * multi-descriptor block starting with an INPUT_MORE, put a copy of
+ * the branch address in the first descriptor.
+ *
+ * Not doing this for transmit contexts since not sure how it interacts
+ * with skip addresses.
+ */
+ if (unlikely(ctx->ohci->quirks & QUIRK_IR_WAKE) &&
+ d_branch != ctx->prev &&
+ (ctx->prev->control & cpu_to_le16(DESCRIPTOR_CMD)) ==
+ cpu_to_le16(DESCRIPTOR_INPUT_MORE)) {
+ ctx->prev->branch_address = cpu_to_le32(d_bus | z);
+ }
+
+ ctx->prev = d;
+ ctx->prev_z = z;
+}
+
+static void context_stop(struct context *ctx)
+{
+ struct fw_ohci *ohci = ctx->ohci;
+ u32 reg;
+ int i;
+
+ reg_write(ohci, CONTROL_CLEAR(ctx->regs), CONTEXT_RUN);
+ ctx->running = false;
+
+ for (i = 0; i < 1000; i++) {
+ reg = reg_read(ohci, CONTROL_SET(ctx->regs));
+ if ((reg & CONTEXT_ACTIVE) == 0)
+ return;
+
+ if (i)
+ udelay(10);
+ }
+ ohci_err(ohci, "DMA context still active (0x%08x)\n", reg);
+}
+
+struct driver_data {
+ u8 inline_data[8];
+ struct fw_packet *packet;
+};
+
+/*
+ * This function appends a packet to the DMA queue for transmission.
+ * Must always be called with the ochi->lock held to ensure proper
+ * generation handling and locking around packet queue manipulation.
+ */
+static int at_context_queue_packet(struct at_context *ctx, struct fw_packet *packet)
+{
+ struct context *context = &ctx->context;
+ struct fw_ohci *ohci = context->ohci;
+ dma_addr_t d_bus, payload_bus;
+ struct driver_data *driver_data;
+ struct descriptor *d, *last;
+ __le32 *header;
+ int z, tcode;
+
+ d = context_get_descriptors(context, 4, &d_bus);
+ if (d == NULL) {
+ packet->ack = RCODE_SEND_ERROR;
+ return -1;
+ }
+
+ d[0].control = cpu_to_le16(DESCRIPTOR_KEY_IMMEDIATE);
+ d[0].res_count = cpu_to_le16(packet->timestamp);
+
+ tcode = async_header_get_tcode(packet->header);
+ header = (__le32 *) &d[1];
+ switch (tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ case TCODE_WRITE_BLOCK_REQUEST:
+ case TCODE_WRITE_RESPONSE:
+ case TCODE_READ_QUADLET_REQUEST:
+ case TCODE_READ_BLOCK_REQUEST:
+ case TCODE_READ_QUADLET_RESPONSE:
+ case TCODE_READ_BLOCK_RESPONSE:
+ case TCODE_LOCK_REQUEST:
+ case TCODE_LOCK_RESPONSE:
+ ohci1394_at_data_set_src_bus_id(header, false);
+ ohci1394_at_data_set_speed(header, packet->speed);
+ ohci1394_at_data_set_tlabel(header, async_header_get_tlabel(packet->header));
+ ohci1394_at_data_set_retry(header, async_header_get_retry(packet->header));
+ ohci1394_at_data_set_tcode(header, tcode);
+
+ ohci1394_at_data_set_destination_id(header,
+ async_header_get_destination(packet->header));
+
+ if (ctx == &ohci->at_response_ctx) {
+ ohci1394_at_data_set_rcode(header, async_header_get_rcode(packet->header));
+ } else {
+ ohci1394_at_data_set_destination_offset(header,
+ async_header_get_offset(packet->header));
+ }
+
+ if (tcode_is_block_packet(tcode))
+ header[3] = cpu_to_le32(packet->header[3]);
+ else
+ header[3] = (__force __le32) packet->header[3];
+
+ d[0].req_count = cpu_to_le16(packet->header_length);
+ break;
+ case TCODE_LINK_INTERNAL:
+ ohci1394_at_data_set_speed(header, packet->speed);
+ ohci1394_at_data_set_tcode(header, TCODE_LINK_INTERNAL);
+
+ header[1] = cpu_to_le32(packet->header[1]);
+ header[2] = cpu_to_le32(packet->header[2]);
+ d[0].req_count = cpu_to_le16(12);
+
+ if (is_ping_packet(&packet->header[1]))
+ d[0].control |= cpu_to_le16(DESCRIPTOR_PING);
+ break;
+
+ case TCODE_STREAM_DATA:
+ ohci1394_it_data_set_speed(header, packet->speed);
+ ohci1394_it_data_set_tag(header, isoc_header_get_tag(packet->header[0]));
+ ohci1394_it_data_set_channel(header, isoc_header_get_channel(packet->header[0]));
+ ohci1394_it_data_set_tcode(header, TCODE_STREAM_DATA);
+ ohci1394_it_data_set_sync(header, isoc_header_get_sy(packet->header[0]));
+
+ ohci1394_it_data_set_data_length(header, isoc_header_get_data_length(packet->header[0]));
+
+ d[0].req_count = cpu_to_le16(8);
+ break;
+
+ default:
+ /* BUG(); */
+ packet->ack = RCODE_SEND_ERROR;
+ return -1;
+ }
+
+ BUILD_BUG_ON(sizeof(struct driver_data) > sizeof(struct descriptor));
+ driver_data = (struct driver_data *) &d[3];
+ driver_data->packet = packet;
+ packet->driver_data = driver_data;
+
+ if (packet->payload_length > 0) {
+ if (packet->payload_length > sizeof(driver_data->inline_data)) {
+ payload_bus = dma_map_single(ohci->card.device,
+ packet->payload,
+ packet->payload_length,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(ohci->card.device, payload_bus)) {
+ packet->ack = RCODE_SEND_ERROR;
+ return -1;
+ }
+ packet->payload_bus = payload_bus;
+ packet->payload_mapped = true;
+ } else {
+ memcpy(driver_data->inline_data, packet->payload,
+ packet->payload_length);
+ payload_bus = d_bus + 3 * sizeof(*d);
+ }
+
+ d[2].req_count = cpu_to_le16(packet->payload_length);
+ d[2].data_address = cpu_to_le32(payload_bus);
+ last = &d[2];
+ z = 3;
+ } else {
+ last = &d[0];
+ z = 2;
+ }
+
+ last->control |= cpu_to_le16(DESCRIPTOR_OUTPUT_LAST |
+ DESCRIPTOR_IRQ_ALWAYS |
+ DESCRIPTOR_BRANCH_ALWAYS);
+
+ /* FIXME: Document how the locking works. */
+ if (ohci->generation != packet->generation) {
+ if (packet->payload_mapped)
+ dma_unmap_single(ohci->card.device, payload_bus,
+ packet->payload_length, DMA_TO_DEVICE);
+ packet->ack = RCODE_GENERATION;
+ return -1;
+ }
+
+ context_append(context, d, z, 4 - z);
+
+ if (context->running)
+ reg_write(ohci, CONTROL_SET(context->regs), CONTEXT_WAKE);
+ else
+ context_run(context, 0);
+
+ return 0;
+}
+
+static void at_context_flush(struct at_context *ctx)
+{
+ // Avoid dead lock due to programming mistake.
+ if (WARN_ON_ONCE(current_work() == &ctx->work))
+ return;
+
+ 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);
+}
+
+static int find_fw_device(struct device *dev, const void *data)
+{
+ struct fw_device *device = fw_device(dev);
+ const u32 *params = data;
+
+ return (device->generation == params[0]) && (device->node_id == params[1]);
+}
+
+static int handle_at_packet(struct context *context,
+ struct descriptor *d,
+ struct descriptor *last)
+{
+ struct at_context *ctx = container_of(context, struct at_context, context);
+ struct fw_ohci *ohci = ctx->context.ohci;
+ struct driver_data *driver_data;
+ struct fw_packet *packet;
+ int evt;
+
+ if (last->transfer_status == 0 && !READ_ONCE(ctx->flushing))
+ /* This descriptor isn't done yet, stop iteration. */
+ return 0;
+
+ driver_data = (struct driver_data *) &d[3];
+ packet = driver_data->packet;
+ if (packet == NULL)
+ /* This packet was cancelled, just continue. */
+ return 1;
+
+ if (packet->payload_mapped)
+ dma_unmap_single(ohci->card.device, packet->payload_bus,
+ packet->payload_length, DMA_TO_DEVICE);
+
+ evt = le16_to_cpu(last->transfer_status) & 0x1f;
+ packet->timestamp = le16_to_cpu(last->res_count);
+
+ switch (evt) {
+ case OHCI1394_evt_timeout:
+ /* Async response transmit timed out. */
+ packet->ack = RCODE_CANCELLED;
+ break;
+
+ case OHCI1394_evt_flushed:
+ /*
+ * The packet was flushed should give same error as
+ * when we try to use a stale generation count.
+ */
+ packet->ack = RCODE_GENERATION;
+ break;
+
+ case OHCI1394_evt_missing_ack:
+ if (READ_ONCE(ctx->flushing))
+ packet->ack = RCODE_GENERATION;
+ else {
+ /*
+ * Using a valid (current) generation count, but the
+ * node is not on the bus or not sending acks.
+ */
+ packet->ack = RCODE_NO_ACK;
+ }
+ break;
+
+ case ACK_COMPLETE + 0x10:
+ case ACK_PENDING + 0x10:
+ case ACK_BUSY_X + 0x10:
+ case ACK_BUSY_A + 0x10:
+ case ACK_BUSY_B + 0x10:
+ case ACK_DATA_ERROR + 0x10:
+ case ACK_TYPE_ERROR + 0x10:
+ packet->ack = evt - 0x10;
+ break;
+
+ case OHCI1394_evt_no_status:
+ if (READ_ONCE(ctx->flushing)) {
+ packet->ack = RCODE_GENERATION;
+ break;
+ }
+ fallthrough;
+
+ default:
+ if (unlikely(evt == 0x10)) {
+ u32 params[2] = {
+ packet->generation,
+ async_header_get_destination(packet->header),
+ };
+ struct device *dev;
+
+ fw_card_get(&ohci->card);
+ dev = device_find_child(ohci->card.device, (const void *)params, find_fw_device);
+ fw_card_put(&ohci->card);
+ if (dev) {
+ struct fw_device *device = fw_device(dev);
+ int quirks = READ_ONCE(device->quirks);
+
+ put_device(dev);
+ if (quirks & FW_DEVICE_QUIRK_ACK_PACKET_WITH_INVALID_PENDING_CODE) {
+ packet->ack = ACK_PENDING;
+ break;
+ }
+ }
+ }
+ packet->ack = RCODE_SEND_ERROR;
+ break;
+ }
+
+ packet->callback(packet, &ohci->card, packet->ack);
+
+ return 1;
+}
+
+static u32 get_cycle_time(struct fw_ohci *ohci);
+
+static void handle_local_rom(struct fw_ohci *ohci,
+ struct fw_packet *packet, u32 csr)
+{
+ struct fw_packet response;
+ int tcode, length, i;
+
+ tcode = async_header_get_tcode(packet->header);
+ if (tcode_is_block_packet(tcode))
+ length = async_header_get_data_length(packet->header);
+ else
+ length = 4;
+
+ i = csr - CSR_CONFIG_ROM;
+ if (i + length > CONFIG_ROM_SIZE) {
+ fw_fill_response(&response, packet->header,
+ RCODE_ADDRESS_ERROR, NULL, 0);
+ } else if (!tcode_is_read_request(tcode)) {
+ fw_fill_response(&response, packet->header,
+ RCODE_TYPE_ERROR, NULL, 0);
+ } else {
+ fw_fill_response(&response, packet->header, RCODE_COMPLETE,
+ (void *) ohci->config_rom + i, length);
+ }
+
+ // Timestamping on behalf of the hardware.
+ response.timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
+ fw_core_handle_response(&ohci->card, &response);
+}
+
+static void handle_local_lock(struct fw_ohci *ohci,
+ struct fw_packet *packet, u32 csr)
+{
+ struct fw_packet response;
+ int tcode, length, ext_tcode, sel, try;
+ __be32 *payload, lock_old;
+ u32 lock_arg, lock_data;
+
+ tcode = async_header_get_tcode(packet->header);
+ length = async_header_get_data_length(packet->header);
+ payload = packet->payload;
+ ext_tcode = async_header_get_extended_tcode(packet->header);
+
+ if (tcode == TCODE_LOCK_REQUEST &&
+ ext_tcode == EXTCODE_COMPARE_SWAP && length == 8) {
+ lock_arg = be32_to_cpu(payload[0]);
+ lock_data = be32_to_cpu(payload[1]);
+ } else if (tcode == TCODE_READ_QUADLET_REQUEST) {
+ lock_arg = 0;
+ lock_data = 0;
+ } else {
+ fw_fill_response(&response, packet->header,
+ RCODE_TYPE_ERROR, NULL, 0);
+ goto out;
+ }
+
+ sel = (csr - CSR_BUS_MANAGER_ID) / 4;
+ reg_write(ohci, OHCI1394_CSRData, lock_data);
+ reg_write(ohci, OHCI1394_CSRCompareData, lock_arg);
+ reg_write(ohci, OHCI1394_CSRControl, sel);
+
+ for (try = 0; try < 20; try++)
+ 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;
+ }
+
+ 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);
+}
+
+static void handle_local_request(struct at_context *ctx, struct fw_packet *packet)
+{
+ struct fw_ohci *ohci = ctx->context.ohci;
+ u64 offset, csr;
+
+ if (ctx == &ohci->at_request_ctx) {
+ packet->ack = ACK_PENDING;
+ packet->callback(packet, &ohci->card, packet->ack);
+ }
+
+ offset = async_header_get_offset(packet->header);
+ csr = offset - CSR_REGISTER_BASE;
+
+ /* Handle config rom reads. */
+ if (csr >= CSR_CONFIG_ROM && csr < CSR_CONFIG_ROM_END)
+ handle_local_rom(ohci, packet, csr);
+ else switch (csr) {
+ case CSR_BUS_MANAGER_ID:
+ case CSR_BANDWIDTH_AVAILABLE:
+ case CSR_CHANNELS_AVAILABLE_HI:
+ case CSR_CHANNELS_AVAILABLE_LO:
+ handle_local_lock(ohci, packet, csr);
+ break;
+ default:
+ if (ctx == &ohci->at_request_ctx)
+ fw_core_handle_request(&ohci->card, packet);
+ else
+ fw_core_handle_response(&ohci->card, packet);
+ break;
+ }
+
+ if (ctx == &ohci->at_response_ctx) {
+ packet->ack = ACK_COMPLETE;
+ packet->callback(packet, &ohci->card, packet->ack);
+ }
+}
+
+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;
+
+ spin_lock_irqsave(&ohci->lock, flags);
+
+ 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;
+ }
+
+ 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);
+ }
+}
+
+static void detect_dead_context(struct fw_ohci *ohci,
+ const char *name, unsigned int regs)
+{
+ static const char *const evts[] = {
+ [0x00] = "evt_no_status", [0x01] = "-reserved-",
+ [0x02] = "evt_long_packet", [0x03] = "evt_missing_ack",
+ [0x04] = "evt_underrun", [0x05] = "evt_overrun",
+ [0x06] = "evt_descriptor_read", [0x07] = "evt_data_read",
+ [0x08] = "evt_data_write", [0x09] = "evt_bus_reset",
+ [0x0a] = "evt_timeout", [0x0b] = "evt_tcode_err",
+ [0x0c] = "-reserved-", [0x0d] = "-reserved-",
+ [0x0e] = "evt_unknown", [0x0f] = "evt_flushed",
+ [0x10] = "-reserved-", [0x11] = "ack_complete",
+ [0x12] = "ack_pending ", [0x13] = "-reserved-",
+ [0x14] = "ack_busy_X", [0x15] = "ack_busy_A",
+ [0x16] = "ack_busy_B", [0x17] = "-reserved-",
+ [0x18] = "-reserved-", [0x19] = "-reserved-",
+ [0x1a] = "-reserved-", [0x1b] = "ack_tardy",
+ [0x1c] = "-reserved-", [0x1d] = "ack_data_error",
+ [0x1e] = "ack_type_error", [0x1f] = "-reserved-",
+ [0x20] = "pending/cancelled",
+ };
+ u32 ctl;
+
+ ctl = reg_read(ohci, CONTROL_SET(regs));
+ if (ctl & CONTEXT_DEAD)
+ ohci_err(ohci, "DMA context %s has stopped, error code: %s\n",
+ name, evts[ctl & 0x1f]);
+}
+
+static void handle_dead_contexts(struct fw_ohci *ohci)
+{
+ unsigned int i;
+ char name[8];
+
+ detect_dead_context(ohci, "ATReq", OHCI1394_AsReqTrContextBase);
+ detect_dead_context(ohci, "ATRsp", OHCI1394_AsRspTrContextBase);
+ detect_dead_context(ohci, "ARReq", OHCI1394_AsReqRcvContextBase);
+ detect_dead_context(ohci, "ARRsp", OHCI1394_AsRspRcvContextBase);
+ for (i = 0; i < 32; ++i) {
+ if (!(ohci->it_context_support & (1 << i)))
+ continue;
+ sprintf(name, "IT%u", i);
+ detect_dead_context(ohci, name, OHCI1394_IsoXmitContextBase(i));
+ }
+ for (i = 0; i < 32; ++i) {
+ if (!(ohci->ir_context_support & (1 << i)))
+ continue;
+ sprintf(name, "IR%u", i);
+ detect_dead_context(ohci, name, OHCI1394_IsoRcvContextBase(i));
+ }
+ /* TODO: maybe try to flush and restart the dead contexts */
+}
+
+static u32 cycle_timer_ticks(u32 cycle_timer)
+{
+ u32 ticks;
+
+ ticks = cycle_timer & 0xfff;
+ ticks += 3072 * ((cycle_timer >> 12) & 0x1fff);
+ ticks += (3072 * 8000) * (cycle_timer >> 25);
+
+ return ticks;
+}
+
+/*
+ * Some controllers exhibit one or more of the following bugs when updating the
+ * iso cycle timer register:
+ * - When the lowest six bits are wrapping around to zero, a read that happens
+ * at the same time will return garbage in the lowest ten bits.
+ * - When the cycleOffset field wraps around to zero, the cycleCount field is
+ * not incremented for about 60 ns.
+ * - Occasionally, the entire register reads zero.
+ *
+ * To catch these, we read the register three times and ensure that the
+ * difference between each two consecutive reads is approximately the same, i.e.
+ * less than twice the other. Furthermore, any negative difference indicates an
+ * error. (A PCI read should take at least 20 ticks of the 24.576 MHz timer to
+ * execute, so we have enough precision to compute the ratio of the differences.)
+ */
+static u32 get_cycle_time(struct fw_ohci *ohci)
+{
+ u32 c0, c1, c2;
+ u32 t0, t1, t2;
+ s32 diff01, diff12;
+ int i;
+
+ if (has_reboot_by_cycle_timer_read_quirk(ohci))
+ return 0;
+
+ c2 = reg_read(ohci, OHCI1394_IsochronousCycleTimer);
+
+ if (ohci->quirks & QUIRK_CYCLE_TIMER) {
+ i = 0;
+ c1 = c2;
+ c2 = reg_read(ohci, OHCI1394_IsochronousCycleTimer);
+ do {
+ c0 = c1;
+ c1 = c2;
+ c2 = reg_read(ohci, OHCI1394_IsochronousCycleTimer);
+ t0 = cycle_timer_ticks(c0);
+ t1 = cycle_timer_ticks(c1);
+ t2 = cycle_timer_ticks(c2);
+ diff01 = t1 - t0;
+ diff12 = t2 - t1;
+ } while ((diff01 <= 0 || diff12 <= 0 ||
+ diff01 / diff12 >= 2 || diff12 / diff01 >= 2)
+ && i++ < 20);
+ }
+
+ return c2;
+}
+
+/*
+ * This function has to be called at least every 64 seconds. The bus_time
+ * field stores not only the upper 25 bits of the BUS_TIME register but also
+ * the most significant bit of the cycle timer in bit 6 so that we can detect
+ * changes in this bit.
+ */
+static u32 update_bus_time(struct fw_ohci *ohci)
+{
+ u32 cycle_time_seconds = get_cycle_time(ohci) >> 25;
+
+ if (unlikely(!ohci->bus_time_running)) {
+ reg_write(ohci, OHCI1394_IntMaskSet, OHCI1394_cycle64Seconds);
+ ohci->bus_time = (lower_32_bits(ktime_get_seconds()) & ~0x7f) |
+ (cycle_time_seconds & 0x40);
+ ohci->bus_time_running = true;
+ }
+
+ if ((ohci->bus_time & 0x40) != (cycle_time_seconds & 0x40))
+ ohci->bus_time += 0x40;
+
+ return ohci->bus_time | cycle_time_seconds;
+}
+
+static int get_status_for_port(struct fw_ohci *ohci, int port_index,
+ enum phy_packet_self_id_port_status *status)
+{
+ int reg;
+
+ scoped_guard(mutex, &ohci->phy_reg_mutex) {
+ reg = write_phy_reg(ohci, 7, port_index);
+ if (reg < 0)
+ return reg;
+
+ reg = read_phy_reg(ohci, 8);
+ if (reg < 0)
+ return reg;
+ }
+
+ switch (reg & 0x0f) {
+ case 0x06:
+ // is child node (connected to parent node)
+ *status = PHY_PACKET_SELF_ID_PORT_STATUS_PARENT;
+ break;
+ case 0x0e:
+ // is parent node (connected to child node)
+ *status = PHY_PACKET_SELF_ID_PORT_STATUS_CHILD;
+ break;
+ default:
+ // not connected
+ *status = PHY_PACKET_SELF_ID_PORT_STATUS_NCONN;
+ break;
+ }
+
+ return 0;
+}
+
+static int get_self_id_pos(struct fw_ohci *ohci, u32 self_id,
+ int self_id_count)
+{
+ unsigned int left_phy_id = phy_packet_self_id_get_phy_id(self_id);
+ int i;
+
+ for (i = 0; i < self_id_count; i++) {
+ u32 entry = ohci->self_id_buffer[i];
+ unsigned int right_phy_id = phy_packet_self_id_get_phy_id(entry);
+
+ if (left_phy_id == right_phy_id)
+ return -1;
+ if (left_phy_id < right_phy_id)
+ return i;
+ }
+ return i;
+}
+
+static int detect_initiated_reset(struct fw_ohci *ohci, bool *is_initiated_reset)
+{
+ int reg;
+
+ guard(mutex)(&ohci->phy_reg_mutex);
+
+ // Select page 7
+ reg = write_phy_reg(ohci, 7, 0xe0);
+ if (reg < 0)
+ return reg;
+
+ reg = read_phy_reg(ohci, 8);
+ if (reg < 0)
+ return reg;
+
+ // set PMODE bit
+ reg |= 0x40;
+ reg = write_phy_reg(ohci, 8, reg);
+ if (reg < 0)
+ return reg;
+
+ // read register 12
+ reg = read_phy_reg(ohci, 12);
+ if (reg < 0)
+ return reg;
+
+ // bit 3 indicates "initiated reset"
+ *is_initiated_reset = !!((reg & 0x08) == 0x08);
+
+ return 0;
+}
+
+/*
+ * TI TSB82AA2B and TSB12LV26 do not receive the selfID of a locally
+ * attached TSB41BA3D phy; see http://www.ti.com/litv/pdf/sllz059.
+ * Construct the selfID from phy register contents.
+ */
+static int find_and_insert_self_id(struct fw_ohci *ohci, int self_id_count)
+{
+ int reg, i, pos, err;
+ bool is_initiated_reset;
+ u32 self_id = 0;
+
+ // link active 1, speed 3, bridge 0, contender 1, more packets 0.
+ phy_packet_set_packet_identifier(&self_id, PHY_PACKET_PACKET_IDENTIFIER_SELF_ID);
+ phy_packet_self_id_zero_set_link_active(&self_id, true);
+ phy_packet_self_id_zero_set_scode(&self_id, SCODE_800);
+ phy_packet_self_id_zero_set_contender(&self_id, true);
+
+ reg = reg_read(ohci, OHCI1394_NodeID);
+ if (!(reg & OHCI1394_NodeID_idValid)) {
+ ohci_notice(ohci,
+ "node ID not valid, new bus reset in progress\n");
+ return -EBUSY;
+ }
+ phy_packet_self_id_set_phy_id(&self_id, reg & 0x3f);
+
+ reg = ohci_read_phy_reg(&ohci->card, 4);
+ if (reg < 0)
+ return reg;
+ phy_packet_self_id_zero_set_power_class(&self_id, reg & 0x07);
+
+ reg = ohci_read_phy_reg(&ohci->card, 1);
+ if (reg < 0)
+ return reg;
+ phy_packet_self_id_zero_set_gap_count(&self_id, reg & 0x3f);
+
+ for (i = 0; i < 3; i++) {
+ enum phy_packet_self_id_port_status status;
+
+ err = get_status_for_port(ohci, i, &status);
+ if (err < 0)
+ return err;
+
+ self_id_sequence_set_port_status(&self_id, 1, i, status);
+ }
+
+ err = detect_initiated_reset(ohci, &is_initiated_reset);
+ if (err < 0)
+ return err;
+ phy_packet_self_id_zero_set_initiated_reset(&self_id, is_initiated_reset);
+
+ pos = get_self_id_pos(ohci, self_id, self_id_count);
+ if (pos >= 0) {
+ memmove(&(ohci->self_id_buffer[pos+1]),
+ &(ohci->self_id_buffer[pos]),
+ (self_id_count - pos) * sizeof(*ohci->self_id_buffer));
+ ohci->self_id_buffer[pos] = self_id;
+ self_id_count++;
+ }
+ return self_id_count;
+}
+
+static irqreturn_t handle_selfid_complete_event(int irq, void *data)
+{
+ struct fw_ohci *ohci = data;
+ int self_id_count, generation, new_generation, i, j;
+ u32 reg, quadlet;
+ void *free_rom = NULL;
+ dma_addr_t free_rom_bus = 0;
+ bool is_new_root;
+
+ reg = reg_read(ohci, OHCI1394_NodeID);
+ if (!(reg & OHCI1394_NodeID_idValid)) {
+ ohci_notice(ohci,
+ "node ID not valid, new bus reset in progress\n");
+ goto end;
+ }
+ if ((reg & OHCI1394_NodeID_nodeNumber) == 63) {
+ ohci_notice(ohci, "malconfigured bus\n");
+ goto end;
+ }
+ ohci->node_id = reg & (OHCI1394_NodeID_busNumber |
+ OHCI1394_NodeID_nodeNumber);
+
+ is_new_root = (reg & OHCI1394_NodeID_root) != 0;
+ if (!(ohci->is_root && is_new_root))
+ reg_write(ohci, OHCI1394_LinkControlSet,
+ OHCI1394_LinkControl_cycleMaster);
+ ohci->is_root = is_new_root;
+
+ reg = reg_read(ohci, OHCI1394_SelfIDCount);
+ if (ohci1394_self_id_count_is_error(reg)) {
+ ohci_notice(ohci, "self ID receive error\n");
+ goto end;
+ }
+
+ trace_self_id_complete(ohci->card.index, reg, ohci->self_id, has_be_header_quirk(ohci));
+
+ /*
+ * The count in the SelfIDCount register is the number of
+ * bytes in the self ID receive buffer. Since we also receive
+ * the inverted quadlets and a header quadlet, we shift one
+ * bit extra to get the actual number of self IDs.
+ */
+ self_id_count = ohci1394_self_id_count_get_size(reg) >> 1;
+
+ if (self_id_count > 252) {
+ ohci_notice(ohci, "bad selfIDSize (%08x)\n", reg);
+ goto end;
+ }
+
+ quadlet = cond_le32_to_cpu(ohci->self_id[0], has_be_header_quirk(ohci));
+ generation = ohci1394_self_id_receive_q0_get_generation(quadlet);
+ rmb();
+
+ for (i = 1, j = 0; j < self_id_count; i += 2, j++) {
+ u32 id = cond_le32_to_cpu(ohci->self_id[i], has_be_header_quirk(ohci));
+ u32 id2 = cond_le32_to_cpu(ohci->self_id[i + 1], has_be_header_quirk(ohci));
+
+ if (id != ~id2) {
+ /*
+ * If the invalid data looks like a cycle start packet,
+ * it's likely to be the result of the cycle master
+ * having a wrong gap count. In this case, the self IDs
+ * so far are valid and should be processed so that the
+ * bus manager can then correct the gap count.
+ */
+ if (id == 0xffff008f) {
+ ohci_notice(ohci, "ignoring spurious self IDs\n");
+ self_id_count = j;
+ break;
+ }
+
+ ohci_notice(ohci, "bad self ID %d/%d (%08x != ~%08x)\n",
+ j, self_id_count, id, id2);
+ goto end;
+ }
+ ohci->self_id_buffer[j] = id;
+ }
+
+ if (ohci->quirks & QUIRK_TI_SLLZ059) {
+ self_id_count = find_and_insert_self_id(ohci, self_id_count);
+ if (self_id_count < 0) {
+ ohci_notice(ohci,
+ "could not construct local self ID\n");
+ goto end;
+ }
+ }
+
+ if (self_id_count == 0) {
+ ohci_notice(ohci, "no self IDs\n");
+ goto end;
+ }
+ rmb();
+
+ /*
+ * Check the consistency of the self IDs we just read. The
+ * problem we face is that a new bus reset can start while we
+ * read out the self IDs from the DMA buffer. If this happens,
+ * the DMA buffer will be overwritten with new self IDs and we
+ * will read out inconsistent data. The OHCI specification
+ * (section 11.2) recommends a technique similar to
+ * linux/seqlock.h, where we remember the generation of the
+ * self IDs in the buffer before reading them out and compare
+ * it to the current generation after reading them out. If
+ * the two generations match we know we have a consistent set
+ * of self IDs.
+ */
+
+ reg = reg_read(ohci, OHCI1394_SelfIDCount);
+ new_generation = ohci1394_self_id_count_get_generation(reg);
+ if (new_generation != generation) {
+ ohci_notice(ohci, "new bus reset, discarding self ids\n");
+ goto end;
+ }
+
+ // FIXME: Document how the locking works.
+ scoped_guard(spinlock_irq, &ohci->lock) {
+ ohci->generation = -1; // prevent AT packet queueing
+ context_stop(&ohci->at_request_ctx.context);
+ context_stop(&ohci->at_response_ctx.context);
+ }
+
+ /*
+ * Per OHCI 1.2 draft, clause 7.2.3.3, hardware may leave unsent
+ * packets in the AT queues and software needs to drain them.
+ * Some OHCI 1.1 controllers (JMicron) apparently require this too.
+ */
+ at_context_flush(&ohci->at_request_ctx);
+ at_context_flush(&ohci->at_response_ctx);
+
+ scoped_guard(spinlock_irq, &ohci->lock) {
+ ohci->generation = generation;
+ reg_write(ohci, OHCI1394_IntEventClear, OHCI1394_busReset);
+ reg_write(ohci, OHCI1394_IntMaskSet, OHCI1394_busReset);
+
+ if (ohci->quirks & QUIRK_RESET_PACKET)
+ ohci->request_generation = generation;
+
+ // This next bit is unrelated to the AT context stuff but we have to do it under the
+ // spinlock also. If a new config rom was set up before this reset, the old one is
+ // now no longer in use and we can free it. Update the config rom pointers to point
+ // to the current config rom and clear the next_config_rom pointer so a new update
+ // can take place.
+ if (ohci->next_config_rom != NULL) {
+ if (ohci->next_config_rom != ohci->config_rom) {
+ free_rom = ohci->config_rom;
+ free_rom_bus = ohci->config_rom_bus;
+ }
+ ohci->config_rom = ohci->next_config_rom;
+ ohci->config_rom_bus = ohci->next_config_rom_bus;
+ ohci->next_config_rom = NULL;
+
+ // Restore config_rom image and manually update config_rom registers.
+ // Writing the header quadlet will indicate that the config rom is ready,
+ // so we do that last.
+ reg_write(ohci, OHCI1394_BusOptions, be32_to_cpu(ohci->config_rom[2]));
+ ohci->config_rom[0] = ohci->next_header;
+ reg_write(ohci, OHCI1394_ConfigROMhdr, be32_to_cpu(ohci->next_header));
+ }
+
+ if (param_remote_dma) {
+ reg_write(ohci, OHCI1394_PhyReqFilterHiSet, ~0);
+ reg_write(ohci, OHCI1394_PhyReqFilterLoSet, ~0);
+ }
+ }
+
+ if (free_rom)
+ dmam_free_coherent(ohci->card.device, CONFIG_ROM_SIZE, free_rom, free_rom_bus);
+
+ fw_core_handle_bus_reset(&ohci->card, ohci->node_id, generation,
+ self_id_count, ohci->self_id_buffer,
+ ohci->csr_state_setclear_abdicate);
+ ohci->csr_state_setclear_abdicate = false;
+end:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t irq_handler(int irq, void *data)
+{
+ struct fw_ohci *ohci = data;
+ u32 event, iso_event;
+ int i;
+
+ event = reg_read(ohci, OHCI1394_IntEventClear);
+
+ if (!event || !~event)
+ return IRQ_NONE;
+
+ /*
+ * busReset and postedWriteErr events must not be cleared yet
+ * (OHCI 1.1 clauses 7.2.3.2 and 13.2.8.1)
+ */
+ reg_write(ohci, OHCI1394_IntEventClear,
+ event & ~(OHCI1394_busReset | OHCI1394_postedWriteErr));
+ trace_irqs(ohci->card.index, event);
+
+ // The flag is masked again at handle_selfid_complete_event() scheduled by selfID event.
+ if (event & OHCI1394_busReset)
+ reg_write(ohci, OHCI1394_IntMaskClear, OHCI1394_busReset);
+
+ if (event & OHCI1394_RQPkt)
+ queue_work(ohci->card.async_wq, &ohci->ar_request_ctx.work);
+
+ if (event & OHCI1394_RSPkt)
+ queue_work(ohci->card.async_wq, &ohci->ar_response_ctx.work);
+
+ if (event & OHCI1394_reqTxComplete)
+ queue_work(ohci->card.async_wq, &ohci->at_request_ctx.work);
+
+ if (event & OHCI1394_respTxComplete)
+ queue_work(ohci->card.async_wq, &ohci->at_response_ctx.work);
+
+ if (event & OHCI1394_isochRx) {
+ iso_event = reg_read(ohci, OHCI1394_IsoRecvIntEventClear);
+ reg_write(ohci, OHCI1394_IsoRecvIntEventClear, iso_event);
+
+ while (iso_event) {
+ i = ffs(iso_event) - 1;
+ fw_iso_context_schedule_flush_completions(&ohci->ir_context_list[i].base);
+ iso_event &= ~(1 << i);
+ }
+ }
+
+ if (event & OHCI1394_isochTx) {
+ iso_event = reg_read(ohci, OHCI1394_IsoXmitIntEventClear);
+ reg_write(ohci, OHCI1394_IsoXmitIntEventClear, iso_event);
+
+ while (iso_event) {
+ i = ffs(iso_event) - 1;
+ fw_iso_context_schedule_flush_completions(&ohci->it_context_list[i].base);
+ iso_event &= ~(1 << i);
+ }
+ }
+
+ if (unlikely(event & OHCI1394_regAccessFail))
+ ohci_err(ohci, "register access failure\n");
+
+ if (unlikely(event & OHCI1394_postedWriteErr)) {
+ reg_read(ohci, OHCI1394_PostedWriteAddressHi);
+ reg_read(ohci, OHCI1394_PostedWriteAddressLo);
+ reg_write(ohci, OHCI1394_IntEventClear,
+ OHCI1394_postedWriteErr);
+ dev_err_ratelimited(ohci->card.device, "PCI posted write error\n");
+ }
+
+ if (unlikely(event & OHCI1394_cycleTooLong)) {
+ dev_notice_ratelimited(ohci->card.device, "isochronous cycle too long\n");
+ reg_write(ohci, OHCI1394_LinkControlSet,
+ OHCI1394_LinkControl_cycleMaster);
+ }
+
+ if (unlikely(event & OHCI1394_cycleInconsistent)) {
+ /*
+ * We need to clear this event bit in order to make
+ * cycleMatch isochronous I/O work. In theory we should
+ * stop active cycleMatch iso contexts now and restart
+ * them at least two cycles later. (FIXME?)
+ */
+ dev_notice_ratelimited(ohci->card.device, "isochronous cycle inconsistent\n");
+ }
+
+ if (unlikely(event & OHCI1394_unrecoverableError))
+ handle_dead_contexts(ohci);
+
+ if (event & OHCI1394_cycle64Seconds) {
+ guard(spinlock)(&ohci->lock);
+ update_bus_time(ohci);
+ } else
+ flush_writes(ohci);
+
+ if (event & OHCI1394_selfIDComplete)
+ return IRQ_WAKE_THREAD;
+ else
+ return IRQ_HANDLED;
+}
+
+static int software_reset(struct fw_ohci *ohci)
+{
+ u32 val;
+ int i;
+
+ reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_softReset);
+ for (i = 0; i < 500; i++) {
+ val = reg_read(ohci, OHCI1394_HCControlSet);
+ if (!~val)
+ return -ENODEV; /* Card was ejected. */
+
+ if (!(val & OHCI1394_HCControl_softReset))
+ return 0;
+
+ msleep(1);
+ }
+
+ return -EBUSY;
+}
+
+static void copy_config_rom(__be32 *dest, const __be32 *src, size_t length)
+{
+ size_t size = length * 4;
+
+ memcpy(dest, src, size);
+ if (size < CONFIG_ROM_SIZE)
+ memset(&dest[length], 0, CONFIG_ROM_SIZE - size);
+}
+
+static int configure_1394a_enhancements(struct fw_ohci *ohci)
+{
+ bool enable_1394a;
+ int ret, clear, set, offset;
+
+ /* Check if the driver should configure link and PHY. */
+ if (!(reg_read(ohci, OHCI1394_HCControlSet) &
+ OHCI1394_HCControl_programPhyEnable))
+ return 0;
+
+ /* Paranoia: check whether the PHY supports 1394a, too. */
+ enable_1394a = false;
+ ret = read_phy_reg(ohci, 2);
+ if (ret < 0)
+ return ret;
+ if ((ret & PHY_EXTENDED_REGISTERS) == PHY_EXTENDED_REGISTERS) {
+ ret = read_paged_phy_reg(ohci, 1, 8);
+ if (ret < 0)
+ return ret;
+ if (ret >= 1)
+ enable_1394a = true;
+ }
+
+ if (ohci->quirks & QUIRK_NO_1394A)
+ enable_1394a = false;
+
+ /* Configure PHY and link consistently. */
+ if (enable_1394a) {
+ clear = 0;
+ set = PHY_ENABLE_ACCEL | PHY_ENABLE_MULTI;
+ } else {
+ clear = PHY_ENABLE_ACCEL | PHY_ENABLE_MULTI;
+ set = 0;
+ }
+ ret = update_phy_reg(ohci, 5, clear, set);
+ if (ret < 0)
+ return ret;
+
+ if (enable_1394a)
+ offset = OHCI1394_HCControlSet;
+ else
+ offset = OHCI1394_HCControlClear;
+ reg_write(ohci, offset, OHCI1394_HCControl_aPhyEnhanceEnable);
+
+ /* Clean up: configuration has been taken care of. */
+ reg_write(ohci, OHCI1394_HCControlClear,
+ OHCI1394_HCControl_programPhyEnable);
+
+ return 0;
+}
+
+static int probe_tsb41ba3d(struct fw_ohci *ohci)
+{
+ /* TI vendor ID = 0x080028, TSB41BA3D product ID = 0x833005 (sic) */
+ static const u8 id[] = { 0x08, 0x00, 0x28, 0x83, 0x30, 0x05, };
+ int reg, i;
+
+ reg = read_phy_reg(ohci, 2);
+ if (reg < 0)
+ return reg;
+ if ((reg & PHY_EXTENDED_REGISTERS) != PHY_EXTENDED_REGISTERS)
+ return 0;
+
+ for (i = ARRAY_SIZE(id) - 1; i >= 0; i--) {
+ reg = read_paged_phy_reg(ohci, 1, i + 10);
+ if (reg < 0)
+ return reg;
+ if (reg != id[i])
+ return 0;
+ }
+ return 1;
+}
+
+static int ohci_enable(struct fw_card *card,
+ const __be32 *config_rom, size_t length)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+ u32 lps, version, irqs;
+ int i, ret;
+
+ ret = software_reset(ohci);
+ if (ret < 0) {
+ ohci_err(ohci, "failed to reset ohci card\n");
+ return ret;
+ }
+
+ /*
+ * Now enable LPS, which we need in order to start accessing
+ * most of the registers. In fact, on some cards (ALI M5251),
+ * accessing registers in the SClk domain without LPS enabled
+ * will lock up the machine. Wait 50msec to make sure we have
+ * full link enabled. However, with some cards (well, at least
+ * a JMicron PCIe card), we have to try again sometimes.
+ *
+ * TI TSB82AA2 + TSB81BA3(A) cards signal LPS enabled early but
+ * cannot actually use the phy at that time. These need tens of
+ * millisecods pause between LPS write and first phy access too.
+ */
+
+ reg_write(ohci, OHCI1394_HCControlSet,
+ OHCI1394_HCControl_LPS |
+ OHCI1394_HCControl_postedWriteEnable);
+ flush_writes(ohci);
+
+ for (lps = 0, i = 0; !lps && i < 3; i++) {
+ msleep(50);
+ lps = reg_read(ohci, OHCI1394_HCControlSet) &
+ OHCI1394_HCControl_LPS;
+ }
+
+ if (!lps) {
+ ohci_err(ohci, "failed to set Link Power Status\n");
+ return -EIO;
+ }
+
+ if (ohci->quirks & QUIRK_TI_SLLZ059) {
+ ret = probe_tsb41ba3d(ohci);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ ohci_notice(ohci, "local TSB41BA3D phy\n");
+ else
+ ohci->quirks &= ~QUIRK_TI_SLLZ059;
+ }
+
+ reg_write(ohci, OHCI1394_HCControlClear,
+ OHCI1394_HCControl_noByteSwapData);
+
+ reg_write(ohci, OHCI1394_SelfIDBuffer, ohci->self_id_bus);
+ reg_write(ohci, OHCI1394_LinkControlSet,
+ OHCI1394_LinkControl_cycleTimerEnable |
+ OHCI1394_LinkControl_cycleMaster);
+
+ reg_write(ohci, OHCI1394_ATRetries,
+ OHCI1394_MAX_AT_REQ_RETRIES |
+ (OHCI1394_MAX_AT_RESP_RETRIES << 4) |
+ (OHCI1394_MAX_PHYS_RESP_RETRIES << 8) |
+ (200 << 16));
+
+ ohci->bus_time_running = false;
+
+ for (i = 0; i < 32; i++)
+ if (ohci->ir_context_support & (1 << i))
+ reg_write(ohci, OHCI1394_IsoRcvContextControlClear(i),
+ IR_CONTEXT_MULTI_CHANNEL_MODE);
+
+ version = reg_read(ohci, OHCI1394_Version) & 0x00ff00ff;
+ if (version >= OHCI_VERSION_1_1) {
+ reg_write(ohci, OHCI1394_InitialChannelsAvailableHi,
+ 0xfffffffe);
+ card->broadcast_channel_auto_allocated = true;
+ }
+
+ /* Get implemented bits of the priority arbitration request counter. */
+ reg_write(ohci, OHCI1394_FairnessControl, 0x3f);
+ ohci->pri_req_max = reg_read(ohci, OHCI1394_FairnessControl) & 0x3f;
+ reg_write(ohci, OHCI1394_FairnessControl, 0);
+ card->priority_budget_implemented = ohci->pri_req_max != 0;
+
+ reg_write(ohci, OHCI1394_PhyUpperBound, FW_MAX_PHYSICAL_RANGE >> 16);
+ reg_write(ohci, OHCI1394_IntEventClear, ~0);
+ reg_write(ohci, OHCI1394_IntMaskClear, ~0);
+
+ ret = configure_1394a_enhancements(ohci);
+ if (ret < 0)
+ return ret;
+
+ /* Activate link_on bit and contender bit in our self ID packets.*/
+ ret = ohci_update_phy_reg(card, 4, 0, PHY_LINK_ACTIVE | PHY_CONTENDER);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * When the link is not yet enabled, the atomic config rom
+ * update mechanism described below in ohci_set_config_rom()
+ * is not active. We have to update ConfigRomHeader and
+ * BusOptions manually, and the write to ConfigROMmap takes
+ * effect immediately. We tie this to the enabling of the
+ * link, so we have a valid config rom before enabling - the
+ * OHCI requires that ConfigROMhdr and BusOptions have valid
+ * values before enabling.
+ *
+ * However, when the ConfigROMmap is written, some controllers
+ * always read back quadlets 0 and 2 from the config rom to
+ * the ConfigRomHeader and BusOptions registers on bus reset.
+ * They shouldn't do that in this initial case where the link
+ * isn't enabled. This means we have to use the same
+ * workaround here, setting the bus header to 0 and then write
+ * the right values in the bus reset work item.
+ */
+
+ if (config_rom) {
+ ohci->next_config_rom = dmam_alloc_coherent(ohci->card.device, CONFIG_ROM_SIZE,
+ &ohci->next_config_rom_bus, GFP_KERNEL);
+ if (ohci->next_config_rom == NULL)
+ return -ENOMEM;
+
+ copy_config_rom(ohci->next_config_rom, config_rom, length);
+ } else {
+ /*
+ * In the suspend case, config_rom is NULL, which
+ * means that we just reuse the old config rom.
+ */
+ ohci->next_config_rom = ohci->config_rom;
+ ohci->next_config_rom_bus = ohci->config_rom_bus;
+ }
+
+ ohci->next_header = ohci->next_config_rom[0];
+ ohci->next_config_rom[0] = 0;
+ reg_write(ohci, OHCI1394_ConfigROMhdr, 0);
+ reg_write(ohci, OHCI1394_BusOptions,
+ be32_to_cpu(ohci->next_config_rom[2]));
+ reg_write(ohci, OHCI1394_ConfigROMmap, ohci->next_config_rom_bus);
+
+ reg_write(ohci, OHCI1394_AsReqFilterHiSet, 0x80000000);
+
+ irqs = OHCI1394_reqTxComplete | OHCI1394_respTxComplete |
+ OHCI1394_RQPkt | OHCI1394_RSPkt |
+ OHCI1394_isochTx | OHCI1394_isochRx |
+ OHCI1394_postedWriteErr |
+ OHCI1394_selfIDComplete |
+ OHCI1394_regAccessFail |
+ OHCI1394_cycleInconsistent |
+ OHCI1394_unrecoverableError |
+ OHCI1394_cycleTooLong |
+ OHCI1394_masterIntEnable |
+ OHCI1394_busReset;
+ reg_write(ohci, OHCI1394_IntMaskSet, irqs);
+
+ reg_write(ohci, OHCI1394_HCControlSet,
+ OHCI1394_HCControl_linkEnable |
+ OHCI1394_HCControl_BIBimageValid);
+
+ reg_write(ohci, OHCI1394_LinkControlSet,
+ OHCI1394_LinkControl_rcvSelfID |
+ OHCI1394_LinkControl_rcvPhyPkt);
+
+ ar_context_run(&ohci->ar_request_ctx);
+ ar_context_run(&ohci->ar_response_ctx);
+
+ flush_writes(ohci);
+
+ /* We are ready to go, reset bus to finish initialization. */
+ fw_schedule_bus_reset(&ohci->card, false, true);
+
+ return 0;
+}
+
+static void ohci_disable(struct fw_card *card)
+{
+ struct pci_dev *pdev = to_pci_dev(card->device);
+ struct fw_ohci *ohci = pci_get_drvdata(pdev);
+ int i, irq = pci_irq_vector(pdev, 0);
+
+ // If the removal is happening from the suspend state, LPS won't be enabled and host
+ // registers (eg., IntMaskClear) won't be accessible.
+ if (!(reg_read(ohci, OHCI1394_HCControlSet) & OHCI1394_HCControl_LPS))
+ return;
+
+ reg_write(ohci, OHCI1394_IntMaskClear, ~0);
+ flush_writes(ohci);
+
+ if (irq >= 0)
+ synchronize_irq(irq);
+
+ flush_work(&ohci->ar_request_ctx.work);
+ flush_work(&ohci->ar_response_ctx.work);
+ flush_work(&ohci->at_request_ctx.work);
+ flush_work(&ohci->at_response_ctx.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);
+ }
+ for (i = 0; i < ohci->n_it; ++i) {
+ if (!(ohci->it_context_mask & BIT(i)))
+ flush_work(&ohci->it_context_list[i].base.work);
+ }
+
+ at_context_flush(&ohci->at_request_ctx);
+ at_context_flush(&ohci->at_response_ctx);
+}
+
+static int ohci_set_config_rom(struct fw_card *card,
+ const __be32 *config_rom, size_t length)
+{
+ struct fw_ohci *ohci;
+ __be32 *next_config_rom;
+ dma_addr_t next_config_rom_bus;
+
+ ohci = fw_ohci(card);
+
+ /*
+ * When the OHCI controller is enabled, the config rom update
+ * mechanism is a bit tricky, but easy enough to use. See
+ * section 5.5.6 in the OHCI specification.
+ *
+ * The OHCI controller caches the new config rom address in a
+ * shadow register (ConfigROMmapNext) and needs a bus reset
+ * for the changes to take place. When the bus reset is
+ * detected, the controller loads the new values for the
+ * ConfigRomHeader and BusOptions registers from the specified
+ * config rom and loads ConfigROMmap from the ConfigROMmapNext
+ * shadow register. All automatically and atomically.
+ *
+ * Now, there's a twist to this story. The automatic load of
+ * ConfigRomHeader and BusOptions doesn't honor the
+ * noByteSwapData bit, so with a be32 config rom, the
+ * controller will load be32 values in to these registers
+ * during the atomic update, even on little endian
+ * architectures. The workaround we use is to put a 0 in the
+ * header quadlet; 0 is endian agnostic and means that the
+ * config rom isn't ready yet. In the bus reset work item we
+ * then set up the real values for the two registers.
+ *
+ * We use ohci->lock to avoid racing with the code that sets
+ * ohci->next_config_rom to NULL (see handle_selfid_complete_event).
+ */
+
+ next_config_rom = dmam_alloc_coherent(ohci->card.device, CONFIG_ROM_SIZE,
+ &next_config_rom_bus, GFP_KERNEL);
+ if (next_config_rom == NULL)
+ return -ENOMEM;
+
+ scoped_guard(spinlock_irq, &ohci->lock) {
+ // If there is not an already pending config_rom update, push our new allocation
+ // into the ohci->next_config_rom and then mark the local variable as null so that
+ // we won't deallocate the new buffer.
+ //
+ // OTOH, if there is a pending config_rom update, just use that buffer with the new
+ // config_rom data, and let this routine free the unused DMA allocation.
+ if (ohci->next_config_rom == NULL) {
+ ohci->next_config_rom = next_config_rom;
+ ohci->next_config_rom_bus = next_config_rom_bus;
+ next_config_rom = NULL;
+ }
+
+ copy_config_rom(ohci->next_config_rom, config_rom, length);
+
+ ohci->next_header = config_rom[0];
+ ohci->next_config_rom[0] = 0;
+
+ reg_write(ohci, OHCI1394_ConfigROMmap, ohci->next_config_rom_bus);
+ }
+
+ /* If we didn't use the DMA allocation, delete it. */
+ if (next_config_rom != NULL) {
+ dmam_free_coherent(ohci->card.device, CONFIG_ROM_SIZE, next_config_rom,
+ next_config_rom_bus);
+ }
+
+ /*
+ * Now initiate a bus reset to have the changes take
+ * effect. We clean up the old config rom memory and DMA
+ * mappings in the bus reset work item, since the OHCI
+ * controller could need to access it before the bus reset
+ * takes effect.
+ */
+
+ fw_schedule_bus_reset(&ohci->card, true, true);
+
+ return 0;
+}
+
+static void ohci_send_request(struct fw_card *card, struct fw_packet *packet)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+
+ at_context_transmit(&ohci->at_request_ctx, packet);
+}
+
+static void ohci_send_response(struct fw_card *card, struct fw_packet *packet)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+
+ at_context_transmit(&ohci->at_response_ctx, packet);
+}
+
+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;
+ int ret = -ENOENT;
+
+ // 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;
+
+ if (packet->payload_mapped)
+ dma_unmap_single(ohci->card.device, packet->payload_bus,
+ packet->payload_length, DMA_TO_DEVICE);
+
+ driver_data->packet = NULL;
+ packet->ack = RCODE_CANCELLED;
+
+ // Timestamping on behalf of the hardware.
+ packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
+
+ packet->callback(packet, &ohci->card, packet->ack);
+ ret = 0;
+ out:
+ enable_work(&ctx->work);
+
+ return ret;
+}
+
+static int ohci_enable_phys_dma(struct fw_card *card,
+ int node_id, int generation)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+ int n, ret = 0;
+
+ if (param_remote_dma)
+ return 0;
+
+ /*
+ * FIXME: Make sure this bitmask is cleared when we clear the busReset
+ * interrupt bit. Clear physReqResourceAllBuses on bus reset.
+ */
+
+ guard(spinlock_irqsave)(&ohci->lock);
+
+ if (ohci->generation != generation)
+ return -ESTALE;
+
+ /*
+ * Note, if the node ID contains a non-local bus ID, physical DMA is
+ * enabled for _all_ nodes on remote buses.
+ */
+
+ n = (node_id & 0xffc0) == LOCAL_BUS ? node_id & 0x3f : 63;
+ if (n < 32)
+ reg_write(ohci, OHCI1394_PhyReqFilterLoSet, 1 << n);
+ else
+ reg_write(ohci, OHCI1394_PhyReqFilterHiSet, 1 << (n - 32));
+
+ flush_writes(ohci);
+
+ return ret;
+}
+
+static u32 ohci_read_csr(struct fw_card *card, int csr_offset)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+ u32 value;
+
+ switch (csr_offset) {
+ case CSR_STATE_CLEAR:
+ case CSR_STATE_SET:
+ if (ohci->is_root &&
+ (reg_read(ohci, OHCI1394_LinkControlSet) &
+ OHCI1394_LinkControl_cycleMaster))
+ value = CSR_STATE_BIT_CMSTR;
+ else
+ value = 0;
+ if (ohci->csr_state_setclear_abdicate)
+ value |= CSR_STATE_BIT_ABDICATE;
+
+ return value;
+
+ case CSR_NODE_IDS:
+ return reg_read(ohci, OHCI1394_NodeID) << 16;
+
+ case CSR_CYCLE_TIME:
+ return get_cycle_time(ohci);
+
+ case CSR_BUS_TIME:
+ {
+ // We might be called just after the cycle timer has wrapped around but just before
+ // the cycle64Seconds handler, so we better check here, too, if the bus time needs
+ // to be updated.
+
+ guard(spinlock_irqsave)(&ohci->lock);
+ return update_bus_time(ohci);
+ }
+ case CSR_BUSY_TIMEOUT:
+ value = reg_read(ohci, OHCI1394_ATRetries);
+ return (value >> 4) & 0x0ffff00f;
+
+ case CSR_PRIORITY_BUDGET:
+ return (reg_read(ohci, OHCI1394_FairnessControl) & 0x3f) |
+ (ohci->pri_req_max << 8);
+
+ default:
+ WARN_ON(1);
+ return 0;
+ }
+}
+
+static void ohci_write_csr(struct fw_card *card, int csr_offset, u32 value)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+
+ switch (csr_offset) {
+ case CSR_STATE_CLEAR:
+ if ((value & CSR_STATE_BIT_CMSTR) && ohci->is_root) {
+ reg_write(ohci, OHCI1394_LinkControlClear,
+ OHCI1394_LinkControl_cycleMaster);
+ flush_writes(ohci);
+ }
+ if (value & CSR_STATE_BIT_ABDICATE)
+ ohci->csr_state_setclear_abdicate = false;
+ break;
+
+ case CSR_STATE_SET:
+ if ((value & CSR_STATE_BIT_CMSTR) && ohci->is_root) {
+ reg_write(ohci, OHCI1394_LinkControlSet,
+ OHCI1394_LinkControl_cycleMaster);
+ flush_writes(ohci);
+ }
+ if (value & CSR_STATE_BIT_ABDICATE)
+ ohci->csr_state_setclear_abdicate = true;
+ break;
+
+ case CSR_NODE_IDS:
+ reg_write(ohci, OHCI1394_NodeID, value >> 16);
+ flush_writes(ohci);
+ break;
+
+ case CSR_CYCLE_TIME:
+ reg_write(ohci, OHCI1394_IsochronousCycleTimer, value);
+ reg_write(ohci, OHCI1394_IntEventSet,
+ OHCI1394_cycleInconsistent);
+ flush_writes(ohci);
+ break;
+
+ case CSR_BUS_TIME:
+ {
+ guard(spinlock_irqsave)(&ohci->lock);
+ ohci->bus_time = (update_bus_time(ohci) & 0x40) | (value & ~0x7f);
+ break;
+ }
+ case CSR_BUSY_TIMEOUT:
+ value = (value & 0xf) | ((value & 0xf) << 4) |
+ ((value & 0xf) << 8) | ((value & 0x0ffff000) << 4);
+ reg_write(ohci, OHCI1394_ATRetries, value);
+ flush_writes(ohci);
+ break;
+
+ case CSR_PRIORITY_BUDGET:
+ reg_write(ohci, OHCI1394_FairnessControl, value & 0x3f);
+ flush_writes(ohci);
+ break;
+
+ default:
+ WARN_ON(1);
+ break;
+ }
+}
+
+static void flush_iso_completions(struct iso_context *ctx, enum fw_iso_context_completions_cause cause)
+{
+ trace_isoc_inbound_single_completions(&ctx->base, ctx->sc.last_timestamp, cause,
+ ctx->sc.header, ctx->sc.header_length);
+ trace_isoc_outbound_completions(&ctx->base, ctx->sc.last_timestamp, cause, ctx->sc.header,
+ ctx->sc.header_length);
+
+ ctx->base.callback.sc(&ctx->base, ctx->sc.last_timestamp, ctx->sc.header_length,
+ ctx->sc.header, ctx->base.callback_data);
+ ctx->sc.header_length = 0;
+}
+
+static void copy_iso_headers(struct iso_context *ctx, const u32 *dma_hdr)
+{
+ u32 *ctx_hdr;
+
+ if (ctx->sc.header_length + ctx->base.header_size > ctx->base.header_storage_size) {
+ if (ctx->base.flags & FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS)
+ return;
+ flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW);
+ }
+
+ ctx_hdr = ctx->sc.header + ctx->sc.header_length;
+ ctx->sc.last_timestamp = (u16)le32_to_cpu((__force __le32)dma_hdr[0]);
+
+ /*
+ * The two iso header quadlets are byteswapped to little
+ * endian by the controller, but we want to present them
+ * as big endian for consistency with the bus endianness.
+ */
+ if (ctx->base.header_size > 0)
+ ctx_hdr[0] = swab32(dma_hdr[1]); /* iso packet header */
+ if (ctx->base.header_size > 4)
+ ctx_hdr[1] = swab32(dma_hdr[0]); /* timestamp */
+ if (ctx->base.header_size > 8)
+ memcpy(&ctx_hdr[2], &dma_hdr[2], ctx->base.header_size - 8);
+ ctx->sc.header_length += ctx->base.header_size;
+}
+
+static int handle_ir_packet_per_buffer(struct context *context,
+ struct descriptor *d,
+ struct descriptor *last)
+{
+ struct iso_context *ctx =
+ container_of(context, struct iso_context, context);
+ struct descriptor *pd;
+ u32 buffer_dma;
+
+ for (pd = d; pd <= last; pd++)
+ if (pd->transfer_status)
+ break;
+ if (pd > last)
+ /* Descriptor(s) not done yet, stop iteration */
+ return 0;
+
+ while (!(d->control & cpu_to_le16(DESCRIPTOR_BRANCH_ALWAYS))) {
+ d++;
+ buffer_dma = le32_to_cpu(d->data_address);
+ dma_sync_single_range_for_cpu(context->ohci->card.device,
+ buffer_dma & PAGE_MASK,
+ buffer_dma & ~PAGE_MASK,
+ le16_to_cpu(d->req_count),
+ DMA_FROM_DEVICE);
+ }
+
+ copy_iso_headers(ctx, (u32 *) (last + 1));
+
+ if (last->control & cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS))
+ flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_INTERRUPT);
+
+ return 1;
+}
+
+/* d == last because each descriptor block is only a single descriptor. */
+static int handle_ir_buffer_fill(struct context *context,
+ struct descriptor *d,
+ struct descriptor *last)
+{
+ struct iso_context *ctx =
+ container_of(context, struct iso_context, context);
+ unsigned int req_count, res_count, completed;
+ u32 buffer_dma;
+
+ req_count = le16_to_cpu(last->req_count);
+ res_count = le16_to_cpu(READ_ONCE(last->res_count));
+ completed = req_count - res_count;
+ buffer_dma = le32_to_cpu(last->data_address);
+
+ if (completed > 0) {
+ ctx->mc.buffer_bus = buffer_dma;
+ ctx->mc.completed = completed;
+ }
+
+ if (res_count != 0)
+ /* Descriptor(s) not done yet, stop iteration */
+ return 0;
+
+ dma_sync_single_range_for_cpu(context->ohci->card.device,
+ buffer_dma & PAGE_MASK,
+ buffer_dma & ~PAGE_MASK,
+ completed, DMA_FROM_DEVICE);
+
+ if (last->control & cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS)) {
+ trace_isoc_inbound_multiple_completions(&ctx->base, completed,
+ FW_ISO_CONTEXT_COMPLETIONS_CAUSE_INTERRUPT);
+
+ ctx->base.callback.mc(&ctx->base,
+ buffer_dma + completed,
+ ctx->base.callback_data);
+ ctx->mc.completed = 0;
+ }
+
+ return 1;
+}
+
+static void flush_ir_buffer_fill(struct iso_context *ctx)
+{
+ dma_sync_single_range_for_cpu(ctx->context.ohci->card.device,
+ ctx->mc.buffer_bus & PAGE_MASK,
+ ctx->mc.buffer_bus & ~PAGE_MASK,
+ ctx->mc.completed, DMA_FROM_DEVICE);
+
+ trace_isoc_inbound_multiple_completions(&ctx->base, ctx->mc.completed,
+ FW_ISO_CONTEXT_COMPLETIONS_CAUSE_FLUSH);
+
+ ctx->base.callback.mc(&ctx->base, ctx->mc.buffer_bus + ctx->mc.completed,
+ ctx->base.callback_data);
+ ctx->mc.completed = 0;
+}
+
+static inline void sync_it_packet_for_cpu(struct context *context,
+ struct descriptor *pd)
+{
+ __le16 control;
+ u32 buffer_dma;
+
+ /* only packets beginning with OUTPUT_MORE* have data buffers */
+ if (pd->control & cpu_to_le16(DESCRIPTOR_BRANCH_ALWAYS))
+ return;
+
+ /* skip over the OUTPUT_MORE_IMMEDIATE descriptor */
+ pd += 2;
+
+ /*
+ * If the packet has a header, the first OUTPUT_MORE/LAST descriptor's
+ * data buffer is in the context program's coherent page and must not
+ * be synced.
+ */
+ if ((le32_to_cpu(pd->data_address) & PAGE_MASK) ==
+ (context->current_bus & PAGE_MASK)) {
+ if (pd->control & cpu_to_le16(DESCRIPTOR_BRANCH_ALWAYS))
+ return;
+ pd++;
+ }
+
+ do {
+ buffer_dma = le32_to_cpu(pd->data_address);
+ dma_sync_single_range_for_cpu(context->ohci->card.device,
+ buffer_dma & PAGE_MASK,
+ buffer_dma & ~PAGE_MASK,
+ le16_to_cpu(pd->req_count),
+ DMA_TO_DEVICE);
+ control = pd->control;
+ pd++;
+ } while (!(control & cpu_to_le16(DESCRIPTOR_BRANCH_ALWAYS)));
+}
+
+static int handle_it_packet(struct context *context,
+ struct descriptor *d,
+ struct descriptor *last)
+{
+ struct iso_context *ctx =
+ container_of(context, struct iso_context, context);
+ struct descriptor *pd;
+ __be32 *ctx_hdr;
+
+ for (pd = d; pd <= last; pd++)
+ if (pd->transfer_status)
+ break;
+ if (pd > last)
+ /* Descriptor(s) not done yet, stop iteration */
+ return 0;
+
+ sync_it_packet_for_cpu(context, d);
+
+ if (ctx->sc.header_length + 4 > ctx->base.header_storage_size) {
+ if (ctx->base.flags & FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS)
+ return 1;
+ flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW);
+ }
+
+ ctx_hdr = ctx->sc.header + ctx->sc.header_length;
+ ctx->sc.last_timestamp = le16_to_cpu(last->res_count);
+ /* Present this value as big-endian to match the receive code */
+ *ctx_hdr = cpu_to_be32((le16_to_cpu(pd->transfer_status) << 16) |
+ le16_to_cpu(pd->res_count));
+ ctx->sc.header_length += 4;
+
+ if (last->control & cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS))
+ flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_INTERRUPT);
+
+ return 1;
+}
+
+static void set_multichannel_mask(struct fw_ohci *ohci, u64 channels)
+{
+ u32 hi = channels >> 32, lo = channels;
+
+ reg_write(ohci, OHCI1394_IRMultiChanMaskHiClear, ~hi);
+ reg_write(ohci, OHCI1394_IRMultiChanMaskLoClear, ~lo);
+ reg_write(ohci, OHCI1394_IRMultiChanMaskHiSet, hi);
+ reg_write(ohci, OHCI1394_IRMultiChanMaskLoSet, lo);
+ ohci->mc_channels = channels;
+}
+
+static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, int type, int channel,
+ size_t header_size, size_t header_storage_size)
+{
+ struct fw_ohci *ohci = fw_ohci(card);
+ void *header __free(kvfree) = NULL;
+ struct iso_context *ctx;
+ descriptor_callback_t callback;
+ u64 *channels;
+ u32 *mask, regs;
+ int index, ret = -EBUSY;
+
+ scoped_guard(spinlock_irq, &ohci->lock) {
+ switch (type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ mask = &ohci->it_context_mask;
+ callback = handle_it_packet;
+ index = ffs(*mask) - 1;
+ if (index >= 0) {
+ *mask &= ~(1 << index);
+ regs = OHCI1394_IsoXmitContextBase(index);
+ ctx = &ohci->it_context_list[index];
+ }
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE:
+ channels = &ohci->ir_context_channels;
+ mask = &ohci->ir_context_mask;
+ callback = handle_ir_packet_per_buffer;
+ index = *channels & 1ULL << channel ? ffs(*mask) - 1 : -1;
+ if (index >= 0) {
+ *channels &= ~(1ULL << channel);
+ *mask &= ~(1 << index);
+ regs = OHCI1394_IsoRcvContextBase(index);
+ ctx = &ohci->ir_context_list[index];
+ }
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ mask = &ohci->ir_context_mask;
+ callback = handle_ir_buffer_fill;
+ index = !ohci->mc_allocated ? ffs(*mask) - 1 : -1;
+ if (index >= 0) {
+ ohci->mc_allocated = true;
+ *mask &= ~(1 << index);
+ regs = OHCI1394_IsoRcvContextBase(index);
+ ctx = &ohci->ir_context_list[index];
+ }
+ break;
+
+ default:
+ index = -1;
+ ret = -ENOSYS;
+ }
+
+ if (index < 0)
+ return ERR_PTR(ret);
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) {
+ ctx->sc.header_length = 0;
+ header = kvmalloc(header_storage_size, GFP_KERNEL);
+ if (!header) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
+
+ ret = context_init(&ctx->context, ohci, regs, callback);
+ if (ret < 0)
+ goto out;
+ fw_iso_context_init_work(&ctx->base, ohci_isoc_context_work);
+
+ if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) {
+ ctx->sc.header = no_free_ptr(header);
+ } else {
+ set_multichannel_mask(ohci, 0);
+ ctx->mc.completed = 0;
+ }
+
+ return &ctx->base;
+ out:
+ scoped_guard(spinlock_irq, &ohci->lock) {
+ switch (type) {
+ case FW_ISO_CONTEXT_RECEIVE:
+ *channels |= 1ULL << channel;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ ohci->mc_allocated = false;
+ break;
+ }
+ *mask |= 1 << index;
+ }
+
+ return ERR_PTR(ret);
+}
+
+static int ohci_start_iso(struct fw_iso_context *base,
+ s32 cycle, u32 sync, u32 tags)
+{
+ struct iso_context *ctx = container_of(base, struct iso_context, base);
+ struct fw_ohci *ohci = ctx->context.ohci;
+ u32 control = IR_CONTEXT_ISOCH_HEADER, match;
+ int index;
+
+ /* the controller cannot start without any queued packets */
+ if (ctx->context.last->branch_address == 0)
+ return -ENODATA;
+
+ switch (ctx->base.type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ index = ctx - ohci->it_context_list;
+ match = 0;
+ if (cycle >= 0)
+ match = IT_CONTEXT_CYCLE_MATCH_ENABLE |
+ (cycle & 0x7fff) << 16;
+
+ reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 1 << index);
+ reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1 << index);
+ context_run(&ctx->context, match);
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ control |= IR_CONTEXT_BUFFER_FILL|IR_CONTEXT_MULTI_CHANNEL_MODE;
+ fallthrough;
+ case FW_ISO_CONTEXT_RECEIVE:
+ index = ctx - ohci->ir_context_list;
+ match = (tags << 28) | (sync << 8) | ctx->base.channel;
+ if (cycle >= 0) {
+ match |= (cycle & 0x07fff) << 12;
+ control |= IR_CONTEXT_CYCLE_MATCH_ENABLE;
+ }
+
+ reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 1 << index);
+ reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 1 << index);
+ reg_write(ohci, CONTEXT_MATCH(ctx->context.regs), match);
+ context_run(&ctx->context, control);
+
+ ctx->sync = sync;
+ ctx->tags = tags;
+
+ break;
+ }
+
+ return 0;
+}
+
+static int ohci_stop_iso(struct fw_iso_context *base)
+{
+ struct fw_ohci *ohci = fw_ohci(base->card);
+ struct iso_context *ctx = container_of(base, struct iso_context, base);
+ int index;
+
+ switch (ctx->base.type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ index = ctx - ohci->it_context_list;
+ reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 1 << index);
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE:
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ index = ctx - ohci->ir_context_list;
+ reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 1 << index);
+ break;
+ }
+ flush_writes(ohci);
+ context_stop(&ctx->context);
+
+ return 0;
+}
+
+static void ohci_free_iso_context(struct fw_iso_context *base)
+{
+ struct fw_ohci *ohci = fw_ohci(base->card);
+ struct iso_context *ctx = container_of(base, struct iso_context, base);
+ int index;
+
+ ohci_stop_iso(base);
+ context_release(&ctx->context);
+
+ if (base->type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) {
+ kvfree(ctx->sc.header);
+ ctx->sc.header = NULL;
+ }
+
+ guard(spinlock_irqsave)(&ohci->lock);
+
+ switch (base->type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ index = ctx - ohci->it_context_list;
+ ohci->it_context_mask |= 1 << index;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE:
+ index = ctx - ohci->ir_context_list;
+ ohci->ir_context_mask |= 1 << index;
+ ohci->ir_context_channels |= 1ULL << base->channel;
+ break;
+
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ index = ctx - ohci->ir_context_list;
+ ohci->ir_context_mask |= 1 << index;
+ ohci->ir_context_channels |= ohci->mc_channels;
+ ohci->mc_channels = 0;
+ ohci->mc_allocated = false;
+ break;
+ }
+}
+
+static int ohci_set_iso_channels(struct fw_iso_context *base, u64 *channels)
+{
+ struct fw_ohci *ohci = fw_ohci(base->card);
+
+ switch (base->type) {
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ {
+ guard(spinlock_irqsave)(&ohci->lock);
+
+ // Don't allow multichannel to grab other contexts' channels.
+ if (~ohci->ir_context_channels & ~ohci->mc_channels & *channels) {
+ *channels = ohci->ir_context_channels;
+ return -EBUSY;
+ } else {
+ set_multichannel_mask(ohci, *channels);
+ return 0;
+ }
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static void __maybe_unused ohci_resume_iso_dma(struct fw_ohci *ohci)
+{
+ int i;
+ struct iso_context *ctx;
+
+ for (i = 0 ; i < ohci->n_ir ; i++) {
+ ctx = &ohci->ir_context_list[i];
+ if (ctx->context.running)
+ ohci_start_iso(&ctx->base, 0, ctx->sync, ctx->tags);
+ }
+
+ for (i = 0 ; i < ohci->n_it ; i++) {
+ ctx = &ohci->it_context_list[i];
+ if (ctx->context.running)
+ ohci_start_iso(&ctx->base, 0, ctx->sync, ctx->tags);
+ }
+}
+
+static int queue_iso_transmit(struct iso_context *ctx,
+ struct fw_iso_packet *packet,
+ struct fw_iso_buffer *buffer,
+ unsigned long payload)
+{
+ struct descriptor *d, *last, *pd;
+ struct fw_iso_packet *p;
+ __le32 *header;
+ dma_addr_t d_bus;
+ u32 z, header_z, payload_z, irq;
+ u32 payload_index, payload_end_index, next_page_index;
+ int page, end_page, i, length, offset;
+
+ p = packet;
+ payload_index = payload;
+
+ if (p->skip)
+ z = 1;
+ else
+ z = 2;
+ if (p->header_length > 0)
+ z++;
+
+ /* Determine the first page the payload isn't contained in. */
+ end_page = PAGE_ALIGN(payload_index + p->payload_length) >> PAGE_SHIFT;
+ if (p->payload_length > 0)
+ payload_z = end_page - (payload_index >> PAGE_SHIFT);
+ else
+ payload_z = 0;
+
+ z += payload_z;
+
+ /* Get header size in number of descriptors. */
+ header_z = DIV_ROUND_UP(p->header_length, sizeof(*d));
+
+ d = context_get_descriptors(&ctx->context, z + header_z, &d_bus);
+ if (d == NULL)
+ return -ENOMEM;
+
+ if (!p->skip) {
+ d[0].control = cpu_to_le16(DESCRIPTOR_KEY_IMMEDIATE);
+ d[0].req_count = cpu_to_le16(8);
+ /*
+ * Link the skip address to this descriptor itself. This causes
+ * a context to skip a cycle whenever lost cycles or FIFO
+ * overruns occur, without dropping the data. The application
+ * should then decide whether this is an error condition or not.
+ * FIXME: Make the context's cycle-lost behaviour configurable?
+ */
+ d[0].branch_address = cpu_to_le32(d_bus | z);
+
+ header = (__le32 *) &d[1];
+
+ ohci1394_it_data_set_speed(header, ctx->base.speed);
+ ohci1394_it_data_set_tag(header, p->tag);
+ ohci1394_it_data_set_channel(header, ctx->base.channel);
+ ohci1394_it_data_set_tcode(header, TCODE_STREAM_DATA);
+ ohci1394_it_data_set_sync(header, p->sy);
+
+ ohci1394_it_data_set_data_length(header, p->header_length + p->payload_length);
+ }
+
+ if (p->header_length > 0) {
+ d[2].req_count = cpu_to_le16(p->header_length);
+ d[2].data_address = cpu_to_le32(d_bus + z * sizeof(*d));
+ memcpy(&d[z], p->header, p->header_length);
+ }
+
+ pd = d + z - payload_z;
+ payload_end_index = payload_index + p->payload_length;
+ for (i = 0; i < payload_z; i++) {
+ page = payload_index >> PAGE_SHIFT;
+ offset = payload_index & ~PAGE_MASK;
+ next_page_index = (page + 1) << PAGE_SHIFT;
+ length =
+ min(next_page_index, payload_end_index) - payload_index;
+ pd[i].req_count = cpu_to_le16(length);
+
+ dma_addr_t dma_addr = buffer->dma_addrs[page];
+ pd[i].data_address = cpu_to_le32(dma_addr + offset);
+
+ dma_sync_single_range_for_device(ctx->context.ohci->card.device,
+ dma_addr, offset, length,
+ DMA_TO_DEVICE);
+
+ payload_index += length;
+ }
+
+ if (p->interrupt)
+ irq = DESCRIPTOR_IRQ_ALWAYS;
+ else
+ irq = DESCRIPTOR_NO_IRQ;
+
+ last = z == 2 ? d : d + z - 1;
+ last->control |= cpu_to_le16(DESCRIPTOR_OUTPUT_LAST |
+ DESCRIPTOR_STATUS |
+ DESCRIPTOR_BRANCH_ALWAYS |
+ irq);
+
+ context_append(&ctx->context, d, z, header_z);
+
+ return 0;
+}
+
+static int queue_iso_packet_per_buffer(struct iso_context *ctx,
+ struct fw_iso_packet *packet,
+ struct fw_iso_buffer *buffer,
+ unsigned long payload)
+{
+ struct device *device = ctx->context.ohci->card.device;
+ struct descriptor *d, *pd;
+ dma_addr_t d_bus;
+ u32 z, header_z, rest;
+ int i, j, length;
+ int page, offset, packet_count, header_size, payload_per_buffer;
+
+ /*
+ * The OHCI controller puts the isochronous header and trailer in the
+ * buffer, so we need at least 8 bytes.
+ */
+ packet_count = packet->header_length / ctx->base.header_size;
+ header_size = max(ctx->base.header_size, (size_t)8);
+
+ /* Get header size in number of descriptors. */
+ header_z = DIV_ROUND_UP(header_size, sizeof(*d));
+ page = payload >> PAGE_SHIFT;
+ offset = payload & ~PAGE_MASK;
+ payload_per_buffer = packet->payload_length / packet_count;
+
+ for (i = 0; i < packet_count; i++) {
+ /* d points to the header descriptor */
+ z = DIV_ROUND_UP(payload_per_buffer + offset, PAGE_SIZE) + 1;
+ d = context_get_descriptors(&ctx->context,
+ z + header_z, &d_bus);
+ if (d == NULL)
+ return -ENOMEM;
+
+ d->control = cpu_to_le16(DESCRIPTOR_STATUS |
+ DESCRIPTOR_INPUT_MORE);
+ if (packet->skip && i == 0)
+ d->control |= cpu_to_le16(DESCRIPTOR_WAIT);
+ d->req_count = cpu_to_le16(header_size);
+ d->res_count = d->req_count;
+ d->transfer_status = 0;
+ d->data_address = cpu_to_le32(d_bus + (z * sizeof(*d)));
+
+ rest = payload_per_buffer;
+ pd = d;
+ for (j = 1; j < z; j++) {
+ pd++;
+ pd->control = cpu_to_le16(DESCRIPTOR_STATUS |
+ DESCRIPTOR_INPUT_MORE);
+
+ if (offset + rest < PAGE_SIZE)
+ length = rest;
+ else
+ length = PAGE_SIZE - offset;
+ pd->req_count = cpu_to_le16(length);
+ pd->res_count = pd->req_count;
+ pd->transfer_status = 0;
+
+ dma_addr_t dma_addr = buffer->dma_addrs[page];
+ pd->data_address = cpu_to_le32(dma_addr + offset);
+
+ dma_sync_single_range_for_device(device, dma_addr,
+ offset, length,
+ DMA_FROM_DEVICE);
+
+ offset = (offset + length) & ~PAGE_MASK;
+ rest -= length;
+ if (offset == 0)
+ page++;
+ }
+ pd->control = cpu_to_le16(DESCRIPTOR_STATUS |
+ DESCRIPTOR_INPUT_LAST |
+ DESCRIPTOR_BRANCH_ALWAYS);
+ if (packet->interrupt && i == packet_count - 1)
+ pd->control |= cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS);
+
+ context_append(&ctx->context, d, z, header_z);
+ }
+
+ return 0;
+}
+
+static int queue_iso_buffer_fill(struct iso_context *ctx,
+ struct fw_iso_packet *packet,
+ struct fw_iso_buffer *buffer,
+ unsigned long payload)
+{
+ struct descriptor *d;
+ dma_addr_t d_bus;
+ int page, offset, rest, z, i, length;
+
+ page = payload >> PAGE_SHIFT;
+ offset = payload & ~PAGE_MASK;
+ rest = packet->payload_length;
+
+ /* We need one descriptor for each page in the buffer. */
+ z = DIV_ROUND_UP(offset + rest, PAGE_SIZE);
+
+ if (WARN_ON(offset & 3 || rest & 3 || page + z > buffer->page_count))
+ return -EFAULT;
+
+ for (i = 0; i < z; i++) {
+ d = context_get_descriptors(&ctx->context, 1, &d_bus);
+ if (d == NULL)
+ return -ENOMEM;
+
+ d->control = cpu_to_le16(DESCRIPTOR_INPUT_MORE |
+ DESCRIPTOR_BRANCH_ALWAYS);
+ if (packet->skip && i == 0)
+ d->control |= cpu_to_le16(DESCRIPTOR_WAIT);
+ if (packet->interrupt && i == z - 1)
+ d->control |= cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS);
+
+ if (offset + rest < PAGE_SIZE)
+ length = rest;
+ else
+ length = PAGE_SIZE - offset;
+ d->req_count = cpu_to_le16(length);
+ d->res_count = d->req_count;
+ d->transfer_status = 0;
+
+ dma_addr_t dma_addr = buffer->dma_addrs[page];
+ d->data_address = cpu_to_le32(dma_addr + offset);
+
+ dma_sync_single_range_for_device(ctx->context.ohci->card.device,
+ dma_addr, offset, length,
+ DMA_FROM_DEVICE);
+
+ rest -= length;
+ offset = 0;
+ page++;
+
+ context_append(&ctx->context, d, 1, 0);
+ }
+
+ return 0;
+}
+
+static int ohci_queue_iso(struct fw_iso_context *base,
+ struct fw_iso_packet *packet,
+ struct fw_iso_buffer *buffer,
+ unsigned long payload)
+{
+ struct iso_context *ctx = container_of(base, struct iso_context, base);
+
+ guard(spinlock_irqsave)(&ctx->context.ohci->lock);
+
+ switch (base->type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ return queue_iso_transmit(ctx, packet, buffer, payload);
+ case FW_ISO_CONTEXT_RECEIVE:
+ return queue_iso_packet_per_buffer(ctx, packet, buffer, payload);
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ return queue_iso_buffer_fill(ctx, packet, buffer, payload);
+ default:
+ return -ENOSYS;
+ }
+}
+
+static void ohci_flush_queue_iso(struct fw_iso_context *base)
+{
+ struct context *ctx =
+ &container_of(base, struct iso_context, base)->context;
+
+ reg_write(ctx->ohci, CONTROL_SET(ctx->regs), CONTEXT_WAKE);
+}
+
+static int ohci_flush_iso_completions(struct fw_iso_context *base)
+{
+ struct iso_context *ctx = container_of(base, struct iso_context, base);
+ int ret = 0;
+
+ if (!test_and_set_bit_lock(0, &ctx->flushing_completions)) {
+ ohci_isoc_context_work(&base->work);
+
+ switch (base->type) {
+ case FW_ISO_CONTEXT_TRANSMIT:
+ case FW_ISO_CONTEXT_RECEIVE:
+ if (ctx->sc.header_length != 0)
+ flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_FLUSH);
+ break;
+ case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
+ if (ctx->mc.completed != 0)
+ flush_ir_buffer_fill(ctx);
+ break;
+ default:
+ ret = -ENOSYS;
+ }
+
+ clear_bit_unlock(0, &ctx->flushing_completions);
+ smp_mb__after_atomic();
+ }
+
+ return ret;
+}
+
+static const struct fw_card_driver ohci_driver = {
+ .enable = ohci_enable,
+ .disable = ohci_disable,
+ .read_phy_reg = ohci_read_phy_reg,
+ .update_phy_reg = ohci_update_phy_reg,
+ .set_config_rom = ohci_set_config_rom,
+ .send_request = ohci_send_request,
+ .send_response = ohci_send_response,
+ .cancel_packet = ohci_cancel_packet,
+ .enable_phys_dma = ohci_enable_phys_dma,
+ .read_csr = ohci_read_csr,
+ .write_csr = ohci_write_csr,
+
+ .allocate_iso_context = ohci_allocate_iso_context,
+ .free_iso_context = ohci_free_iso_context,
+ .set_iso_channels = ohci_set_iso_channels,
+ .queue_iso = ohci_queue_iso,
+ .flush_queue_iso = ohci_flush_queue_iso,
+ .flush_iso_completions = ohci_flush_iso_completions,
+ .start_iso = ohci_start_iso,
+ .stop_iso = ohci_stop_iso,
+};
+
+#ifdef CONFIG_PPC_PMAC
+static void pmac_ohci_on(struct pci_dev *dev)
+{
+ if (machine_is(powermac)) {
+ struct device_node *ofn = pci_device_to_OF_node(dev);
+
+ if (ofn) {
+ pmac_call_feature(PMAC_FTR_1394_CABLE_POWER, ofn, 0, 1);
+ pmac_call_feature(PMAC_FTR_1394_ENABLE, ofn, 0, 1);
+ }
+ }
+}
+
+static void pmac_ohci_off(struct pci_dev *dev)
+{
+ if (machine_is(powermac)) {
+ struct device_node *ofn = pci_device_to_OF_node(dev);
+
+ if (ofn) {
+ pmac_call_feature(PMAC_FTR_1394_ENABLE, ofn, 0, 0);
+ pmac_call_feature(PMAC_FTR_1394_CABLE_POWER, ofn, 0, 0);
+ }
+ }
+}
+#else
+static inline void pmac_ohci_on(struct pci_dev *dev) {}
+static inline void pmac_ohci_off(struct pci_dev *dev) {}
+#endif /* CONFIG_PPC_PMAC */
+
+static void release_ohci(struct device *dev, void *data)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct fw_ohci *ohci = pci_get_drvdata(pdev);
+
+ pmac_ohci_off(pdev);
+
+ ar_context_release(&ohci->ar_response_ctx);
+ ar_context_release(&ohci->ar_request_ctx);
+
+ dev_notice(dev, "removed fw-ohci device\n");
+}
+
+static int pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ struct fw_ohci *ohci;
+ u32 bus_options, max_receive, link_speed, version;
+ u64 guid;
+ int i, flags, irq, err;
+
+ if (dev->vendor == PCI_VENDOR_ID_PINNACLE_SYSTEMS) {
+ dev_err(&dev->dev, "Pinnacle MovieBoard is not yet supported\n");
+ return -ENOSYS;
+ }
+
+ ohci = devres_alloc(release_ohci, sizeof(*ohci), GFP_KERNEL);
+ if (ohci == NULL)
+ return -ENOMEM;
+ fw_card_initialize(&ohci->card, &ohci_driver, &dev->dev);
+ pci_set_drvdata(dev, ohci);
+ pmac_ohci_on(dev);
+ devres_add(&dev->dev, ohci);
+
+ err = pcim_enable_device(dev);
+ if (err) {
+ dev_err(&dev->dev, "failed to enable OHCI hardware\n");
+ return err;
+ }
+
+ pci_set_master(dev);
+ pci_write_config_dword(dev, OHCI1394_PCI_HCI_Control, 0);
+
+ spin_lock_init(&ohci->lock);
+ mutex_init(&ohci->phy_reg_mutex);
+
+ if (!(pci_resource_flags(dev, 0) & IORESOURCE_MEM) ||
+ pci_resource_len(dev, 0) < OHCI1394_REGISTER_SIZE) {
+ ohci_err(ohci, "invalid MMIO resource\n");
+ return -ENXIO;
+ }
+
+ ohci->registers = pcim_iomap_region(dev, 0, ohci_driver_name);
+ if (IS_ERR(ohci->registers)) {
+ ohci_err(ohci, "request and map MMIO resource unavailable\n");
+ return -ENXIO;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ohci_quirks); i++)
+ if ((ohci_quirks[i].vendor == dev->vendor) &&
+ (ohci_quirks[i].device == (unsigned short)PCI_ANY_ID ||
+ ohci_quirks[i].device == dev->device) &&
+ (ohci_quirks[i].revision == (unsigned short)PCI_ANY_ID ||
+ ohci_quirks[i].revision >= dev->revision)) {
+ ohci->quirks = ohci_quirks[i].flags;
+ break;
+ }
+ if (param_quirks)
+ ohci->quirks = param_quirks;
+
+ if (detect_vt630x_with_asm1083_on_amd_ryzen_machine(dev))
+ ohci->quirks |= QUIRK_REBOOT_BY_CYCLE_TIMER_READ;
+
+ /*
+ * Because dma_alloc_coherent() allocates at least one page,
+ * we save space by using a common buffer for the AR request/
+ * response descriptors and the self IDs buffer.
+ */
+ BUILD_BUG_ON(AR_BUFFERS * sizeof(struct descriptor) > PAGE_SIZE/4);
+ BUILD_BUG_ON(SELF_ID_BUF_SIZE > PAGE_SIZE/2);
+ ohci->misc_buffer = dmam_alloc_coherent(&dev->dev, PAGE_SIZE, &ohci->misc_buffer_bus,
+ GFP_KERNEL);
+ if (!ohci->misc_buffer)
+ return -ENOMEM;
+
+ err = ar_context_init(&ohci->ar_request_ctx, ohci, 0,
+ OHCI1394_AsReqRcvContextControlSet);
+ if (err < 0)
+ return err;
+
+ err = ar_context_init(&ohci->ar_response_ctx, ohci, PAGE_SIZE/4,
+ OHCI1394_AsRspRcvContextControlSet);
+ if (err < 0)
+ return err;
+
+ err = context_init(&ohci->at_request_ctx.context, ohci,
+ OHCI1394_AsReqTrContextControlSet, handle_at_packet);
+ if (err < 0)
+ return err;
+ INIT_WORK(&ohci->at_request_ctx.work, ohci_at_context_work);
+
+ err = context_init(&ohci->at_response_ctx.context, ohci,
+ OHCI1394_AsRspTrContextControlSet, handle_at_packet);
+ if (err < 0)
+ return err;
+ INIT_WORK(&ohci->at_response_ctx.work, ohci_at_context_work);
+
+ reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, ~0);
+ ohci->ir_context_channels = ~0ULL;
+ ohci->ir_context_support = reg_read(ohci, OHCI1394_IsoRecvIntMaskSet);
+ reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, ~0);
+ ohci->ir_context_mask = ohci->ir_context_support;
+ ohci->n_ir = hweight32(ohci->ir_context_mask);
+ ohci->ir_context_list = devm_kcalloc(&dev->dev, ohci->n_ir, sizeof(struct iso_context), GFP_KERNEL);
+ if (!ohci->ir_context_list)
+ return -ENOMEM;
+
+ reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, ~0);
+ ohci->it_context_support = reg_read(ohci, OHCI1394_IsoXmitIntMaskSet);
+ /* JMicron JMB38x often shows 0 at first read, just ignore it */
+ if (!ohci->it_context_support) {
+ ohci_notice(ohci, "overriding IsoXmitIntMask\n");
+ ohci->it_context_support = 0xf;
+ }
+ reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, ~0);
+ ohci->it_context_mask = ohci->it_context_support;
+ ohci->n_it = hweight32(ohci->it_context_mask);
+ ohci->it_context_list = devm_kcalloc(&dev->dev, ohci->n_it, sizeof(struct iso_context), GFP_KERNEL);
+ if (!ohci->it_context_list)
+ return -ENOMEM;
+
+ ohci->self_id = ohci->misc_buffer + PAGE_SIZE/2;
+ ohci->self_id_bus = ohci->misc_buffer_bus + PAGE_SIZE/2;
+
+ bus_options = reg_read(ohci, OHCI1394_BusOptions);
+ max_receive = (bus_options >> 12) & 0xf;
+ link_speed = bus_options & 0x7;
+ guid = ((u64) reg_read(ohci, OHCI1394_GUIDHi) << 32) |
+ reg_read(ohci, OHCI1394_GUIDLo);
+
+ flags = PCI_IRQ_INTX;
+ if (!(ohci->quirks & QUIRK_NO_MSI))
+ flags |= PCI_IRQ_MSI;
+ err = pci_alloc_irq_vectors(dev, 1, 1, flags);
+ if (err < 0)
+ return err;
+ irq = pci_irq_vector(dev, 0);
+ if (irq < 0) {
+ err = irq;
+ goto fail_msi;
+ }
+
+ // IRQF_ONESHOT is not applied so that any events are handled in the hardIRQ handler during
+ // invoking the threaded IRQ handler for SelfIDComplete event.
+ err = request_threaded_irq(irq, irq_handler, handle_selfid_complete_event,
+ pci_dev_msi_enabled(dev) ? 0 : IRQF_SHARED, ohci_driver_name,
+ ohci);
+ if (err < 0) {
+ ohci_err(ohci, "failed to allocate interrupt %d\n", irq);
+ goto fail_msi;
+ }
+
+ err = fw_card_add(&ohci->card, max_receive, link_speed, guid, ohci->n_it + ohci->n_ir);
+ if (err)
+ goto fail_irq;
+
+ version = reg_read(ohci, OHCI1394_Version) & 0x00ff00ff;
+ ohci_notice(ohci,
+ "added OHCI v%x.%x device as card %d, "
+ "%d IR + %d IT contexts, quirks 0x%x%s\n",
+ version >> 16, version & 0xff, ohci->card.index,
+ ohci->n_ir, ohci->n_it, ohci->quirks,
+ reg_read(ohci, OHCI1394_PhyUpperBound) ?
+ ", physUB" : "");
+
+ return 0;
+
+ fail_irq:
+ free_irq(irq, ohci);
+ fail_msi:
+ pci_free_irq_vectors(dev);
+
+ return err;
+}
+
+static void pci_remove(struct pci_dev *dev)
+{
+ struct fw_ohci *ohci = pci_get_drvdata(dev);
+ int irq;
+
+ fw_core_remove_card(&ohci->card);
+
+ software_reset(ohci);
+
+ irq = pci_irq_vector(dev, 0);
+ if (irq >= 0)
+ free_irq(irq, ohci);
+ pci_free_irq_vectors(dev);
+
+ dev_notice(&dev->dev, "removing fw-ohci device\n");
+}
+
+static int __maybe_unused pci_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct fw_ohci *ohci = pci_get_drvdata(pdev);
+
+ software_reset(ohci);
+ pmac_ohci_off(pdev);
+
+ return 0;
+}
+
+
+static int __maybe_unused pci_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct fw_ohci *ohci = pci_get_drvdata(pdev);
+ int err;
+
+ pmac_ohci_on(pdev);
+
+ /* Some systems don't setup GUID register on resume from ram */
+ if (!reg_read(ohci, OHCI1394_GUIDLo) &&
+ !reg_read(ohci, OHCI1394_GUIDHi)) {
+ reg_write(ohci, OHCI1394_GUIDLo, (u32)ohci->card.guid);
+ reg_write(ohci, OHCI1394_GUIDHi, (u32)(ohci->card.guid >> 32));
+ }
+
+ err = ohci_enable(&ohci->card, NULL, 0);
+ if (err)
+ return err;
+
+ ohci_resume_iso_dma(ohci);
+
+ return 0;
+}
+
+static const struct pci_device_id pci_table[] = {
+ { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_FIREWIRE_OHCI, ~0) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, pci_table);
+
+static 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,
+};
+
+static int __init fw_ohci_init(void)
+{
+ return pci_register_driver(&fw_ohci_pci_driver);
+}
+
+static void __exit fw_ohci_cleanup(void)
+{
+ pci_unregister_driver(&fw_ohci_pci_driver);
+}
+
+module_init(fw_ohci_init);
+module_exit(fw_ohci_cleanup);
+
+MODULE_AUTHOR("Kristian Hoegsberg <krh@bitplanet.net>");
+MODULE_DESCRIPTION("Driver for PCI OHCI IEEE1394 controllers");
+MODULE_LICENSE("GPL");
+
+/* Provide a module alias so root-on-sbp2 initrds don't break. */
+MODULE_ALIAS("ohci1394");
diff --git a/drivers/firewire/ohci.h b/drivers/firewire/ohci.h
new file mode 100644
index 000000000..218666cfe
--- /dev/null
+++ b/drivers/firewire/ohci.h
@@ -0,0 +1,398 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _FIREWIRE_OHCI_H
+#define _FIREWIRE_OHCI_H
+
+/* OHCI register map */
+
+#define OHCI1394_Version 0x000
+#define OHCI1394_GUID_ROM 0x004
+#define OHCI1394_ATRetries 0x008
+#define OHCI1394_CSRData 0x00C
+#define OHCI1394_CSRCompareData 0x010
+#define OHCI1394_CSRControl 0x014
+#define OHCI1394_ConfigROMhdr 0x018
+#define OHCI1394_BusID 0x01C
+#define OHCI1394_BusOptions 0x020
+#define OHCI1394_GUIDHi 0x024
+#define OHCI1394_GUIDLo 0x028
+#define OHCI1394_ConfigROMmap 0x034
+#define OHCI1394_PostedWriteAddressLo 0x038
+#define OHCI1394_PostedWriteAddressHi 0x03C
+#define OHCI1394_VendorID 0x040
+#define OHCI1394_HCControlSet 0x050
+#define OHCI1394_HCControlClear 0x054
+#define OHCI1394_HCControl_BIBimageValid 0x80000000
+#define OHCI1394_HCControl_noByteSwapData 0x40000000
+#define OHCI1394_HCControl_programPhyEnable 0x00800000
+#define OHCI1394_HCControl_aPhyEnhanceEnable 0x00400000
+#define OHCI1394_HCControl_LPS 0x00080000
+#define OHCI1394_HCControl_postedWriteEnable 0x00040000
+#define OHCI1394_HCControl_linkEnable 0x00020000
+#define OHCI1394_HCControl_softReset 0x00010000
+#define OHCI1394_SelfIDBuffer 0x064
+#define OHCI1394_SelfIDCount 0x068
+#define OHCI1394_IRMultiChanMaskHiSet 0x070
+#define OHCI1394_IRMultiChanMaskHiClear 0x074
+#define OHCI1394_IRMultiChanMaskLoSet 0x078
+#define OHCI1394_IRMultiChanMaskLoClear 0x07C
+#define OHCI1394_IntEventSet 0x080
+#define OHCI1394_IntEventClear 0x084
+#define OHCI1394_IntMaskSet 0x088
+#define OHCI1394_IntMaskClear 0x08C
+#define OHCI1394_IsoXmitIntEventSet 0x090
+#define OHCI1394_IsoXmitIntEventClear 0x094
+#define OHCI1394_IsoXmitIntMaskSet 0x098
+#define OHCI1394_IsoXmitIntMaskClear 0x09C
+#define OHCI1394_IsoRecvIntEventSet 0x0A0
+#define OHCI1394_IsoRecvIntEventClear 0x0A4
+#define OHCI1394_IsoRecvIntMaskSet 0x0A8
+#define OHCI1394_IsoRecvIntMaskClear 0x0AC
+#define OHCI1394_InitialBandwidthAvailable 0x0B0
+#define OHCI1394_InitialChannelsAvailableHi 0x0B4
+#define OHCI1394_InitialChannelsAvailableLo 0x0B8
+#define OHCI1394_FairnessControl 0x0DC
+#define OHCI1394_LinkControlSet 0x0E0
+#define OHCI1394_LinkControlClear 0x0E4
+#define OHCI1394_LinkControl_rcvSelfID (1 << 9)
+#define OHCI1394_LinkControl_rcvPhyPkt (1 << 10)
+#define OHCI1394_LinkControl_cycleTimerEnable (1 << 20)
+#define OHCI1394_LinkControl_cycleMaster (1 << 21)
+#define OHCI1394_LinkControl_cycleSource (1 << 22)
+#define OHCI1394_NodeID 0x0E8
+#define OHCI1394_NodeID_idValid 0x80000000
+#define OHCI1394_NodeID_root 0x40000000
+#define OHCI1394_NodeID_nodeNumber 0x0000003f
+#define OHCI1394_NodeID_busNumber 0x0000ffc0
+#define OHCI1394_PhyControl 0x0EC
+#define OHCI1394_PhyControl_Read(addr) (((addr) << 8) | 0x00008000)
+#define OHCI1394_PhyControl_ReadDone 0x80000000
+#define OHCI1394_PhyControl_ReadData(r) (((r) & 0x00ff0000) >> 16)
+#define OHCI1394_PhyControl_Write(addr, data) (((addr) << 8) | (data) | 0x00004000)
+#define OHCI1394_PhyControl_WritePending 0x00004000
+#define OHCI1394_IsochronousCycleTimer 0x0F0
+#define OHCI1394_AsReqFilterHiSet 0x100
+#define OHCI1394_AsReqFilterHiClear 0x104
+#define OHCI1394_AsReqFilterLoSet 0x108
+#define OHCI1394_AsReqFilterLoClear 0x10C
+#define OHCI1394_PhyReqFilterHiSet 0x110
+#define OHCI1394_PhyReqFilterHiClear 0x114
+#define OHCI1394_PhyReqFilterLoSet 0x118
+#define OHCI1394_PhyReqFilterLoClear 0x11C
+#define OHCI1394_PhyUpperBound 0x120
+
+#define OHCI1394_AsReqTrContextBase 0x180
+#define OHCI1394_AsReqTrContextControlSet 0x180
+#define OHCI1394_AsReqTrContextControlClear 0x184
+#define OHCI1394_AsReqTrCommandPtr 0x18C
+
+#define OHCI1394_AsRspTrContextBase 0x1A0
+#define OHCI1394_AsRspTrContextControlSet 0x1A0
+#define OHCI1394_AsRspTrContextControlClear 0x1A4
+#define OHCI1394_AsRspTrCommandPtr 0x1AC
+
+#define OHCI1394_AsReqRcvContextBase 0x1C0
+#define OHCI1394_AsReqRcvContextControlSet 0x1C0
+#define OHCI1394_AsReqRcvContextControlClear 0x1C4
+#define OHCI1394_AsReqRcvCommandPtr 0x1CC
+
+#define OHCI1394_AsRspRcvContextBase 0x1E0
+#define OHCI1394_AsRspRcvContextControlSet 0x1E0
+#define OHCI1394_AsRspRcvContextControlClear 0x1E4
+#define OHCI1394_AsRspRcvCommandPtr 0x1EC
+
+/* Isochronous transmit registers */
+#define OHCI1394_IsoXmitContextBase(n) (0x200 + 16 * (n))
+#define OHCI1394_IsoXmitContextControlSet(n) (0x200 + 16 * (n))
+#define OHCI1394_IsoXmitContextControlClear(n) (0x204 + 16 * (n))
+#define OHCI1394_IsoXmitCommandPtr(n) (0x20C + 16 * (n))
+
+/* Isochronous receive registers */
+#define OHCI1394_IsoRcvContextBase(n) (0x400 + 32 * (n))
+#define OHCI1394_IsoRcvContextControlSet(n) (0x400 + 32 * (n))
+#define OHCI1394_IsoRcvContextControlClear(n) (0x404 + 32 * (n))
+#define OHCI1394_IsoRcvCommandPtr(n) (0x40C + 32 * (n))
+#define OHCI1394_IsoRcvContextMatch(n) (0x410 + 32 * (n))
+
+/* Interrupts Mask/Events */
+#define OHCI1394_reqTxComplete 0x00000001
+#define OHCI1394_respTxComplete 0x00000002
+#define OHCI1394_ARRQ 0x00000004
+#define OHCI1394_ARRS 0x00000008
+#define OHCI1394_RQPkt 0x00000010
+#define OHCI1394_RSPkt 0x00000020
+#define OHCI1394_isochTx 0x00000040
+#define OHCI1394_isochRx 0x00000080
+#define OHCI1394_postedWriteErr 0x00000100
+#define OHCI1394_lockRespErr 0x00000200
+#define OHCI1394_selfIDComplete 0x00010000
+#define OHCI1394_busReset 0x00020000
+#define OHCI1394_regAccessFail 0x00040000
+#define OHCI1394_phy 0x00080000
+#define OHCI1394_cycleSynch 0x00100000
+#define OHCI1394_cycle64Seconds 0x00200000
+#define OHCI1394_cycleLost 0x00400000
+#define OHCI1394_cycleInconsistent 0x00800000
+#define OHCI1394_unrecoverableError 0x01000000
+#define OHCI1394_cycleTooLong 0x02000000
+#define OHCI1394_phyRegRcvd 0x04000000
+#define OHCI1394_masterIntEnable 0x80000000
+
+#define OHCI1394_evt_no_status 0x0
+#define OHCI1394_evt_long_packet 0x2
+#define OHCI1394_evt_missing_ack 0x3
+#define OHCI1394_evt_underrun 0x4
+#define OHCI1394_evt_overrun 0x5
+#define OHCI1394_evt_descriptor_read 0x6
+#define OHCI1394_evt_data_read 0x7
+#define OHCI1394_evt_data_write 0x8
+#define OHCI1394_evt_bus_reset 0x9
+#define OHCI1394_evt_timeout 0xa
+#define OHCI1394_evt_tcode_err 0xb
+#define OHCI1394_evt_reserved_b 0xc
+#define OHCI1394_evt_reserved_c 0xd
+#define OHCI1394_evt_unknown 0xe
+#define OHCI1394_evt_flushed 0xf
+
+
+// Asynchronous Transmit DMA.
+//
+// The content of first two quadlets of data for AT DMA is different from the header for IEEE 1394
+// asynchronous packet.
+
+#define OHCI1394_AT_DATA_Q0_srcBusID_MASK 0x00800000
+#define OHCI1394_AT_DATA_Q0_srcBusID_SHIFT 23
+#define OHCI1394_AT_DATA_Q0_spd_MASK 0x00070000
+#define OHCI1394_AT_DATA_Q0_spd_SHIFT 16
+#define OHCI1394_AT_DATA_Q0_tLabel_MASK 0x0000fc00
+#define OHCI1394_AT_DATA_Q0_tLabel_SHIFT 10
+#define OHCI1394_AT_DATA_Q0_rt_MASK 0x00000300
+#define OHCI1394_AT_DATA_Q0_rt_SHIFT 8
+#define OHCI1394_AT_DATA_Q0_tCode_MASK 0x000000f0
+#define OHCI1394_AT_DATA_Q0_tCode_SHIFT 4
+#define OHCI1394_AT_DATA_Q1_destinationId_MASK 0xffff0000
+#define OHCI1394_AT_DATA_Q1_destinationId_SHIFT 16
+#define OHCI1394_AT_DATA_Q1_destinationOffsetHigh_MASK 0x0000ffff
+#define OHCI1394_AT_DATA_Q1_destinationOffsetHigh_SHIFT 0
+#define OHCI1394_AT_DATA_Q1_rCode_MASK 0x0000f000
+#define OHCI1394_AT_DATA_Q1_rCode_SHIFT 12
+
+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);
+}
+
+static inline void ohci1394_at_data_set_src_bus_id(__le32 *data, bool src_bus_id)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_AT_DATA_Q0_srcBusID_MASK);
+ data[0] |= cpu_to_le32((src_bus_id << OHCI1394_AT_DATA_Q0_srcBusID_SHIFT) & OHCI1394_AT_DATA_Q0_srcBusID_MASK);
+}
+
+static inline unsigned int ohci1394_at_data_get_speed(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_AT_DATA_Q0_spd_MASK) >> OHCI1394_AT_DATA_Q0_spd_SHIFT;
+}
+
+static inline void ohci1394_at_data_set_speed(__le32 *data, unsigned int scode)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_AT_DATA_Q0_spd_MASK);
+ data[0] |= cpu_to_le32((scode << OHCI1394_AT_DATA_Q0_spd_SHIFT) & OHCI1394_AT_DATA_Q0_spd_MASK);
+}
+
+static inline unsigned int ohci1394_at_data_get_tlabel(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_AT_DATA_Q0_tLabel_MASK) >> OHCI1394_AT_DATA_Q0_tLabel_SHIFT;
+}
+
+static inline void ohci1394_at_data_set_tlabel(__le32 *data, unsigned int tlabel)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_AT_DATA_Q0_tLabel_MASK);
+ data[0] |= cpu_to_le32((tlabel << OHCI1394_AT_DATA_Q0_tLabel_SHIFT) & OHCI1394_AT_DATA_Q0_tLabel_MASK);
+}
+
+static inline unsigned int ohci1394_at_data_get_retry(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_AT_DATA_Q0_rt_MASK) >> OHCI1394_AT_DATA_Q0_rt_SHIFT;
+}
+
+static inline void ohci1394_at_data_set_retry(__le32 *data, unsigned int retry)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_AT_DATA_Q0_rt_MASK);
+ data[0] |= cpu_to_le32((retry << OHCI1394_AT_DATA_Q0_rt_SHIFT) & OHCI1394_AT_DATA_Q0_rt_MASK);
+}
+
+static inline unsigned int ohci1394_at_data_get_tcode(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_AT_DATA_Q0_tCode_MASK) >> OHCI1394_AT_DATA_Q0_tCode_SHIFT;
+}
+
+static inline void ohci1394_at_data_set_tcode(__le32 *data, unsigned int tcode)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_AT_DATA_Q0_tCode_MASK);
+ data[0] |= cpu_to_le32((tcode << OHCI1394_AT_DATA_Q0_tCode_SHIFT) & OHCI1394_AT_DATA_Q0_tCode_MASK);
+}
+
+static inline unsigned int ohci1394_at_data_get_destination_id(const __le32 *data)
+{
+ return (le32_to_cpu(data[1]) & OHCI1394_AT_DATA_Q1_destinationId_MASK) >> OHCI1394_AT_DATA_Q1_destinationId_SHIFT;
+}
+
+static inline void ohci1394_at_data_set_destination_id(__le32 *data, unsigned int destination_id)
+{
+ data[1] &= cpu_to_le32(~OHCI1394_AT_DATA_Q1_destinationId_MASK);
+ data[1] |= cpu_to_le32((destination_id << OHCI1394_AT_DATA_Q1_destinationId_SHIFT) & OHCI1394_AT_DATA_Q1_destinationId_MASK);
+}
+
+static inline u64 ohci1394_at_data_get_destination_offset(const __le32 *data)
+{
+ u64 hi = (u64)((le32_to_cpu(data[1]) & OHCI1394_AT_DATA_Q1_destinationOffsetHigh_MASK) >> OHCI1394_AT_DATA_Q1_destinationOffsetHigh_SHIFT);
+ u64 lo = (u64)le32_to_cpu(data[2]);
+ return (hi << 32) | lo;
+}
+
+static inline void ohci1394_at_data_set_destination_offset(__le32 *data, u64 offset)
+{
+ u32 hi = (u32)(offset >> 32);
+ u32 lo = (u32)(offset & 0x00000000ffffffff);
+ data[1] &= cpu_to_le32(~OHCI1394_AT_DATA_Q1_destinationOffsetHigh_MASK);
+ data[1] |= cpu_to_le32((hi << OHCI1394_AT_DATA_Q1_destinationOffsetHigh_SHIFT) & OHCI1394_AT_DATA_Q1_destinationOffsetHigh_MASK);
+ data[2] = cpu_to_le32(lo);
+}
+
+static inline unsigned int ohci1394_at_data_get_rcode(const __le32 *data)
+{
+ return (le32_to_cpu(data[1]) & OHCI1394_AT_DATA_Q1_rCode_MASK) >> OHCI1394_AT_DATA_Q1_rCode_SHIFT;
+}
+
+static inline void ohci1394_at_data_set_rcode(__le32 *data, unsigned int rcode)
+{
+ data[1] &= cpu_to_le32(~OHCI1394_AT_DATA_Q1_rCode_MASK);
+ data[1] |= cpu_to_le32((rcode << OHCI1394_AT_DATA_Q1_rCode_SHIFT) & OHCI1394_AT_DATA_Q1_rCode_MASK);
+}
+
+// Isochronous Transmit DMA.
+//
+// The content of first two quadlets of data for IT DMA is different from the header for IEEE 1394
+// isochronous packet.
+
+#define OHCI1394_IT_DATA_Q0_spd_MASK 0x00070000
+#define OHCI1394_IT_DATA_Q0_spd_SHIFT 16
+#define OHCI1394_IT_DATA_Q0_tag_MASK 0x0000c000
+#define OHCI1394_IT_DATA_Q0_tag_SHIFT 14
+#define OHCI1394_IT_DATA_Q0_chanNum_MASK 0x00003f00
+#define OHCI1394_IT_DATA_Q0_chanNum_SHIFT 8
+#define OHCI1394_IT_DATA_Q0_tcode_MASK 0x000000f0
+#define OHCI1394_IT_DATA_Q0_tcode_SHIFT 4
+#define OHCI1394_IT_DATA_Q0_sy_MASK 0x0000000f
+#define OHCI1394_IT_DATA_Q0_sy_SHIFT 0
+#define OHCI1394_IT_DATA_Q1_dataLength_MASK 0xffff0000
+#define OHCI1394_IT_DATA_Q1_dataLength_SHIFT 16
+
+static inline unsigned int ohci1394_it_data_get_speed(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_IT_DATA_Q0_spd_MASK) >> OHCI1394_IT_DATA_Q0_spd_SHIFT;
+}
+
+static inline void ohci1394_it_data_set_speed(__le32 *data, unsigned int scode)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_IT_DATA_Q0_spd_MASK);
+ data[0] |= cpu_to_le32((scode << OHCI1394_IT_DATA_Q0_spd_SHIFT) & OHCI1394_IT_DATA_Q0_spd_MASK);
+}
+
+static inline unsigned int ohci1394_it_data_get_tag(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_IT_DATA_Q0_tag_MASK) >> OHCI1394_IT_DATA_Q0_tag_SHIFT;
+}
+
+static inline void ohci1394_it_data_set_tag(__le32 *data, unsigned int tag)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_IT_DATA_Q0_tag_MASK);
+ data[0] |= cpu_to_le32((tag << OHCI1394_IT_DATA_Q0_tag_SHIFT) & OHCI1394_IT_DATA_Q0_tag_MASK);
+}
+
+static inline unsigned int ohci1394_it_data_get_channel(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_IT_DATA_Q0_chanNum_MASK) >> OHCI1394_IT_DATA_Q0_chanNum_SHIFT;
+}
+
+static inline void ohci1394_it_data_set_channel(__le32 *data, unsigned int channel)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_IT_DATA_Q0_chanNum_MASK);
+ data[0] |= cpu_to_le32((channel << OHCI1394_IT_DATA_Q0_chanNum_SHIFT) & OHCI1394_IT_DATA_Q0_chanNum_MASK);
+}
+
+static inline unsigned int ohci1394_it_data_get_tcode(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_IT_DATA_Q0_tcode_MASK) >> OHCI1394_IT_DATA_Q0_tcode_SHIFT;
+}
+
+static inline void ohci1394_it_data_set_tcode(__le32 *data, unsigned int tcode)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_IT_DATA_Q0_tcode_MASK);
+ data[0] |= cpu_to_le32((tcode << OHCI1394_IT_DATA_Q0_tcode_SHIFT) & OHCI1394_IT_DATA_Q0_tcode_MASK);
+}
+
+static inline unsigned int ohci1394_it_data_get_sync(const __le32 *data)
+{
+ return (le32_to_cpu(data[0]) & OHCI1394_IT_DATA_Q0_sy_MASK) >> OHCI1394_IT_DATA_Q0_sy_SHIFT;
+}
+
+static inline void ohci1394_it_data_set_sync(__le32 *data, unsigned int sync)
+{
+ data[0] &= cpu_to_le32(~OHCI1394_IT_DATA_Q0_sy_MASK);
+ data[0] |= cpu_to_le32((sync << OHCI1394_IT_DATA_Q0_sy_SHIFT) & OHCI1394_IT_DATA_Q0_sy_MASK);
+}
+
+static inline unsigned int ohci1394_it_data_get_data_length(const __le32 *data)
+{
+ return (le32_to_cpu(data[1]) & OHCI1394_IT_DATA_Q1_dataLength_MASK) >> OHCI1394_IT_DATA_Q1_dataLength_SHIFT;
+}
+
+static inline void ohci1394_it_data_set_data_length(__le32 *data, unsigned int data_length)
+{
+ data[1] &= cpu_to_le32(~OHCI1394_IT_DATA_Q1_dataLength_MASK);
+ data[1] |= cpu_to_le32((data_length << OHCI1394_IT_DATA_Q1_dataLength_SHIFT) & OHCI1394_IT_DATA_Q1_dataLength_MASK);
+}
+
+// Self-ID DMA.
+
+#define OHCI1394_SelfIDCount_selfIDError_MASK 0x80000000
+#define OHCI1394_SelfIDCount_selfIDError_SHIFT 31
+#define OHCI1394_SelfIDCount_selfIDGeneration_MASK 0x00ff0000
+#define OHCI1394_SelfIDCount_selfIDGeneration_SHIFT 16
+#define OHCI1394_SelfIDCount_selfIDSize_MASK 0x000007fc
+#define OHCI1394_SelfIDCount_selfIDSize_SHIFT 2
+
+static inline bool ohci1394_self_id_count_is_error(u32 value)
+{
+ return !!((value & OHCI1394_SelfIDCount_selfIDError_MASK) >> OHCI1394_SelfIDCount_selfIDError_SHIFT);
+}
+
+static inline u8 ohci1394_self_id_count_get_generation(u32 value)
+{
+ return (value & OHCI1394_SelfIDCount_selfIDGeneration_MASK) >> OHCI1394_SelfIDCount_selfIDGeneration_SHIFT;
+}
+
+// In 1394 OHCI specification, the maximum size of self ID stream is 504 quadlets
+// (= 63 devices * 4 self ID packets * 2 quadlets). The selfIDSize field accommodates it and its
+// additional first quadlet, since the field is 9 bits (0x1ff = 511).
+static inline u32 ohci1394_self_id_count_get_size(u32 value)
+{
+ return (value & OHCI1394_SelfIDCount_selfIDSize_MASK) >> OHCI1394_SelfIDCount_selfIDSize_SHIFT;
+}
+
+#define OHCI1394_SELF_ID_RECEIVE_Q0_GENERATION_MASK 0x00ff0000
+#define OHCI1394_SELF_ID_RECEIVE_Q0_GENERATION_SHIFT 16
+#define OHCI1394_SELF_ID_RECEIVE_Q0_TIMESTAMP_MASK 0x0000ffff
+#define OHCI1394_SELF_ID_RECEIVE_Q0_TIMESTAMP_SHIFT 0
+
+static inline u8 ohci1394_self_id_receive_q0_get_generation(u32 quadlet0)
+{
+ return (quadlet0 & OHCI1394_SELF_ID_RECEIVE_Q0_GENERATION_MASK) >> OHCI1394_SELF_ID_RECEIVE_Q0_GENERATION_SHIFT;
+}
+
+static inline u16 ohci1394_self_id_receive_q0_get_timestamp(u32 quadlet0)
+{
+ return (quadlet0 & OHCI1394_SELF_ID_RECEIVE_Q0_TIMESTAMP_MASK) >> OHCI1394_SELF_ID_RECEIVE_Q0_TIMESTAMP_SHIFT;
+}
+
+#endif /* _FIREWIRE_OHCI_H */
diff --git a/drivers/firewire/packet-header-definitions.h b/drivers/firewire/packet-header-definitions.h
new file mode 100644
index 000000000..87a5a3184
--- /dev/null
+++ b/drivers/firewire/packet-header-definitions.h
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// packet-header-definitions.h - The definitions of header fields for IEEE 1394 packet.
+//
+// Copyright (c) 2024 Takashi Sakamoto
+
+#ifndef _FIREWIRE_PACKET_HEADER_DEFINITIONS_H
+#define _FIREWIRE_PACKET_HEADER_DEFINITIONS_H
+
+#include <linux/types.h>
+
+#define ASYNC_HEADER_QUADLET_COUNT 4
+
+#define ASYNC_HEADER_Q0_DESTINATION_SHIFT 16
+#define ASYNC_HEADER_Q0_DESTINATION_MASK 0xffff0000
+#define ASYNC_HEADER_Q0_TLABEL_SHIFT 10
+#define ASYNC_HEADER_Q0_TLABEL_MASK 0x0000fc00
+#define ASYNC_HEADER_Q0_RETRY_SHIFT 8
+#define ASYNC_HEADER_Q0_RETRY_MASK 0x00000300
+#define ASYNC_HEADER_Q0_TCODE_SHIFT 4
+#define ASYNC_HEADER_Q0_TCODE_MASK 0x000000f0
+#define ASYNC_HEADER_Q0_PRIORITY_SHIFT 0
+#define ASYNC_HEADER_Q0_PRIORITY_MASK 0x0000000f
+#define ASYNC_HEADER_Q1_SOURCE_SHIFT 16
+#define ASYNC_HEADER_Q1_SOURCE_MASK 0xffff0000
+#define ASYNC_HEADER_Q1_RCODE_SHIFT 12
+#define ASYNC_HEADER_Q1_RCODE_MASK 0x0000f000
+#define ASYNC_HEADER_Q1_RCODE_SHIFT 12
+#define ASYNC_HEADER_Q1_RCODE_MASK 0x0000f000
+#define ASYNC_HEADER_Q1_OFFSET_HIGH_SHIFT 0
+#define ASYNC_HEADER_Q1_OFFSET_HIGH_MASK 0x0000ffff
+#define ASYNC_HEADER_Q3_DATA_LENGTH_SHIFT 16
+#define ASYNC_HEADER_Q3_DATA_LENGTH_MASK 0xffff0000
+#define ASYNC_HEADER_Q3_EXTENDED_TCODE_SHIFT 0
+#define ASYNC_HEADER_Q3_EXTENDED_TCODE_MASK 0x0000ffff
+
+static inline unsigned int async_header_get_destination(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[0] & ASYNC_HEADER_Q0_DESTINATION_MASK) >> ASYNC_HEADER_Q0_DESTINATION_SHIFT;
+}
+
+static inline unsigned int async_header_get_tlabel(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[0] & ASYNC_HEADER_Q0_TLABEL_MASK) >> ASYNC_HEADER_Q0_TLABEL_SHIFT;
+}
+
+static inline unsigned int async_header_get_retry(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[0] & ASYNC_HEADER_Q0_RETRY_MASK) >> ASYNC_HEADER_Q0_RETRY_SHIFT;
+}
+
+static inline unsigned int async_header_get_tcode(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[0] & ASYNC_HEADER_Q0_TCODE_MASK) >> ASYNC_HEADER_Q0_TCODE_SHIFT;
+}
+
+static inline unsigned int async_header_get_priority(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[0] & ASYNC_HEADER_Q0_PRIORITY_MASK) >> ASYNC_HEADER_Q0_PRIORITY_SHIFT;
+}
+
+static inline unsigned int async_header_get_source(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[1] & ASYNC_HEADER_Q1_SOURCE_MASK) >> ASYNC_HEADER_Q1_SOURCE_SHIFT;
+}
+
+static inline unsigned int async_header_get_rcode(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[1] & ASYNC_HEADER_Q1_RCODE_MASK) >> ASYNC_HEADER_Q1_RCODE_SHIFT;
+}
+
+static inline u64 async_header_get_offset(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ u32 hi = (header[1] & ASYNC_HEADER_Q1_OFFSET_HIGH_MASK) >> ASYNC_HEADER_Q1_OFFSET_HIGH_SHIFT;
+ return (((u64)hi) << 32) | ((u64)header[2]);
+}
+
+static inline u32 async_header_get_quadlet_data(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return header[3];
+}
+
+static inline unsigned int async_header_get_data_length(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[3] & ASYNC_HEADER_Q3_DATA_LENGTH_MASK) >> ASYNC_HEADER_Q3_DATA_LENGTH_SHIFT;
+}
+
+static inline unsigned int async_header_get_extended_tcode(const u32 header[ASYNC_HEADER_QUADLET_COUNT])
+{
+ return (header[3] & ASYNC_HEADER_Q3_EXTENDED_TCODE_MASK) >> ASYNC_HEADER_Q3_EXTENDED_TCODE_SHIFT;
+}
+
+static inline void async_header_set_destination(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int destination)
+{
+ header[0] &= ~ASYNC_HEADER_Q0_DESTINATION_MASK;
+ header[0] |= (((u32)destination) << ASYNC_HEADER_Q0_DESTINATION_SHIFT) & ASYNC_HEADER_Q0_DESTINATION_MASK;
+}
+
+static inline void async_header_set_tlabel(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int tlabel)
+{
+ header[0] &= ~ASYNC_HEADER_Q0_TLABEL_MASK;
+ header[0] |= (((u32)tlabel) << ASYNC_HEADER_Q0_TLABEL_SHIFT) & ASYNC_HEADER_Q0_TLABEL_MASK;
+}
+
+static inline void async_header_set_retry(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int retry)
+{
+ header[0] &= ~ASYNC_HEADER_Q0_RETRY_MASK;
+ header[0] |= (((u32)retry) << ASYNC_HEADER_Q0_RETRY_SHIFT) & ASYNC_HEADER_Q0_RETRY_MASK;
+}
+
+static inline void async_header_set_tcode(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int tcode)
+{
+ header[0] &= ~ASYNC_HEADER_Q0_TCODE_MASK;
+ header[0] |= (((u32)tcode) << ASYNC_HEADER_Q0_TCODE_SHIFT) & ASYNC_HEADER_Q0_TCODE_MASK;
+}
+
+static inline void async_header_set_priority(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int priority)
+{
+ header[0] &= ~ASYNC_HEADER_Q0_PRIORITY_MASK;
+ header[0] |= (((u32)priority) << ASYNC_HEADER_Q0_PRIORITY_SHIFT) & ASYNC_HEADER_Q0_PRIORITY_MASK;
+}
+
+
+static inline void async_header_set_source(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int source)
+{
+ header[1] &= ~ASYNC_HEADER_Q1_SOURCE_MASK;
+ header[1] |= (((u32)source) << ASYNC_HEADER_Q1_SOURCE_SHIFT) & ASYNC_HEADER_Q1_SOURCE_MASK;
+}
+
+static inline void async_header_set_rcode(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int rcode)
+{
+ header[1] &= ~ASYNC_HEADER_Q1_RCODE_MASK;
+ header[1] |= (((u32)rcode) << ASYNC_HEADER_Q1_RCODE_SHIFT) & ASYNC_HEADER_Q1_RCODE_MASK;
+}
+
+static inline void async_header_set_offset(u32 header[ASYNC_HEADER_QUADLET_COUNT], u64 offset)
+{
+ u32 hi = (u32)(offset >> 32);
+ header[1] &= ~ASYNC_HEADER_Q1_OFFSET_HIGH_MASK;
+ header[1] |= (hi << ASYNC_HEADER_Q1_OFFSET_HIGH_SHIFT) & ASYNC_HEADER_Q1_OFFSET_HIGH_MASK;
+ header[2] = (u32)(offset & 0x00000000ffffffff);
+}
+
+static inline void async_header_set_quadlet_data(u32 header[ASYNC_HEADER_QUADLET_COUNT], u32 quadlet_data)
+{
+ header[3] = quadlet_data;
+}
+
+static inline void async_header_set_data_length(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int data_length)
+{
+ header[3] &= ~ASYNC_HEADER_Q3_DATA_LENGTH_MASK;
+ header[3] |= (((u32)data_length) << ASYNC_HEADER_Q3_DATA_LENGTH_SHIFT) & ASYNC_HEADER_Q3_DATA_LENGTH_MASK;
+}
+
+static inline void async_header_set_extended_tcode(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int extended_tcode)
+{
+ header[3] &= ~ASYNC_HEADER_Q3_EXTENDED_TCODE_MASK;
+ header[3] |= (((u32)extended_tcode) << ASYNC_HEADER_Q3_EXTENDED_TCODE_SHIFT) & ASYNC_HEADER_Q3_EXTENDED_TCODE_MASK;
+}
+
+#define ISOC_HEADER_DATA_LENGTH_SHIFT 16
+#define ISOC_HEADER_DATA_LENGTH_MASK 0xffff0000
+#define ISOC_HEADER_TAG_SHIFT 14
+#define ISOC_HEADER_TAG_MASK 0x0000c000
+#define ISOC_HEADER_CHANNEL_SHIFT 8
+#define ISOC_HEADER_CHANNEL_MASK 0x00003f00
+#define ISOC_HEADER_TCODE_SHIFT 4
+#define ISOC_HEADER_TCODE_MASK 0x000000f0
+#define ISOC_HEADER_SY_SHIFT 0
+#define ISOC_HEADER_SY_MASK 0x0000000f
+
+static inline unsigned int isoc_header_get_data_length(u32 header)
+{
+ return (header & ISOC_HEADER_DATA_LENGTH_MASK) >> ISOC_HEADER_DATA_LENGTH_SHIFT;
+}
+
+static inline unsigned int isoc_header_get_tag(u32 header)
+{
+ return (header & ISOC_HEADER_TAG_MASK) >> ISOC_HEADER_TAG_SHIFT;
+}
+
+static inline unsigned int isoc_header_get_channel(u32 header)
+{
+ return (header & ISOC_HEADER_CHANNEL_MASK) >> ISOC_HEADER_CHANNEL_SHIFT;
+}
+
+static inline unsigned int isoc_header_get_tcode(u32 header)
+{
+ return (header & ISOC_HEADER_TCODE_MASK) >> ISOC_HEADER_TCODE_SHIFT;
+}
+
+static inline unsigned int isoc_header_get_sy(u32 header)
+{
+ return (header & ISOC_HEADER_SY_MASK) >> ISOC_HEADER_SY_SHIFT;
+}
+
+static inline void isoc_header_set_data_length(u32 *header, unsigned int data_length)
+{
+ *header &= ~ISOC_HEADER_DATA_LENGTH_MASK;
+ *header |= (((u32)data_length) << ISOC_HEADER_DATA_LENGTH_SHIFT) & ISOC_HEADER_DATA_LENGTH_MASK;
+}
+
+static inline void isoc_header_set_tag(u32 *header, unsigned int tag)
+{
+ *header &= ~ISOC_HEADER_TAG_MASK;
+ *header |= (((u32)tag) << ISOC_HEADER_TAG_SHIFT) & ISOC_HEADER_TAG_MASK;
+}
+
+static inline void isoc_header_set_channel(u32 *header, unsigned int channel)
+{
+ *header &= ~ISOC_HEADER_CHANNEL_MASK;
+ *header |= (((u32)channel) << ISOC_HEADER_CHANNEL_SHIFT) & ISOC_HEADER_CHANNEL_MASK;
+}
+
+static inline void isoc_header_set_tcode(u32 *header, unsigned int tcode)
+{
+ *header &= ~ISOC_HEADER_TCODE_MASK;
+ *header |= (((u32)tcode) << ISOC_HEADER_TCODE_SHIFT) & ISOC_HEADER_TCODE_MASK;
+}
+
+static inline void isoc_header_set_sy(u32 *header, unsigned int sy)
+{
+ *header &= ~ISOC_HEADER_SY_MASK;
+ *header |= (((u32)sy) << ISOC_HEADER_SY_SHIFT) & ISOC_HEADER_SY_MASK;
+}
+
+#endif // _FIREWIRE_PACKET_HEADER_DEFINITIONS_H
diff --git a/drivers/firewire/packet-serdes-test.c b/drivers/firewire/packet-serdes-test.c
new file mode 100644
index 000000000..62ba43375
--- /dev/null
+++ b/drivers/firewire/packet-serdes-test.c
@@ -0,0 +1,917 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// packet-serdes-test.c - An application of Kunit to check serialization/deserialization of packets
+// defined by IEEE 1394.
+//
+// Copyright (c) 2024 Takashi Sakamoto
+
+#include <kunit/test.h>
+
+#include <linux/firewire-constants.h>
+
+#include "packet-header-definitions.h"
+#include "phy-packet-definitions.h"
+
+static void serialize_async_header_common(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id)
+{
+ async_header_set_destination(header, dst_id);
+ async_header_set_tlabel(header, tlabel);
+ async_header_set_retry(header, retry);
+ async_header_set_tcode(header, tcode);
+ async_header_set_priority(header, priority);
+ async_header_set_source(header, src_id);
+}
+
+static void serialize_async_header_request(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id, u64 offset)
+{
+ serialize_async_header_common(header, dst_id, tlabel, retry, tcode, priority, src_id);
+ async_header_set_offset(header, offset);
+}
+
+static void serialize_async_header_quadlet_request(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id,
+ u64 offset)
+{
+ serialize_async_header_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset);
+}
+
+static void serialize_async_header_block_request(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id,
+ u64 offset, unsigned int data_length,
+ unsigned int extended_tcode)
+{
+ serialize_async_header_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset);
+ async_header_set_data_length(header, data_length);
+ async_header_set_extended_tcode(header, extended_tcode);
+}
+
+static void serialize_async_header_response(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id,
+ unsigned int rcode)
+{
+ serialize_async_header_common(header, dst_id, tlabel, retry, tcode, priority, src_id);
+ async_header_set_rcode(header, rcode);
+}
+
+static void serialize_async_header_quadlet_response(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id,
+ unsigned int rcode)
+{
+ serialize_async_header_response(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ rcode);
+}
+
+static void serialize_async_header_block_response(u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int dst_id, unsigned int tlabel,
+ unsigned int retry, unsigned int tcode,
+ unsigned int priority, unsigned int src_id,
+ unsigned int rcode, unsigned int data_length,
+ unsigned int extended_tcode)
+{
+ serialize_async_header_response(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ rcode);
+ async_header_set_data_length(header, data_length);
+ async_header_set_extended_tcode(header, extended_tcode);
+}
+
+static void deserialize_async_header_common(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id)
+{
+ *dst_id = async_header_get_destination(header);
+ *tlabel = async_header_get_tlabel(header);
+ *retry = async_header_get_retry(header);
+ *tcode = async_header_get_tcode(header);
+ *priority = async_header_get_priority(header);
+ *src_id = async_header_get_source(header);
+}
+
+static void deserialize_async_header_request(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id,
+ u64 *offset)
+{
+ deserialize_async_header_common(header, dst_id, tlabel, retry, tcode, priority, src_id);
+ *offset = async_header_get_offset(header);
+}
+
+static void deserialize_async_header_quadlet_request(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id,
+ u64 *offset)
+{
+ deserialize_async_header_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset);
+}
+
+static void deserialize_async_header_block_request(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id,
+ u64 *offset,
+ unsigned int *data_length,
+ unsigned int *extended_tcode)
+{
+ deserialize_async_header_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset);
+ *data_length = async_header_get_data_length(header);
+ *extended_tcode = async_header_get_extended_tcode(header);
+}
+
+static void deserialize_async_header_response(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id,
+ unsigned int *rcode)
+{
+ deserialize_async_header_common(header, dst_id, tlabel, retry, tcode, priority, src_id);
+ *rcode = async_header_get_rcode(header);
+}
+
+static void deserialize_async_header_quadlet_response(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id,
+ unsigned int *rcode)
+{
+ deserialize_async_header_response(header, dst_id, tlabel, retry, tcode, priority, src_id, rcode);
+}
+
+static void deserialize_async_header_block_response(const u32 header[ASYNC_HEADER_QUADLET_COUNT],
+ unsigned int *dst_id, unsigned int *tlabel,
+ unsigned int *retry, unsigned int *tcode,
+ unsigned int *priority, unsigned int *src_id,
+ unsigned int *rcode, unsigned int *data_length,
+ unsigned int *extended_tcode)
+{
+ deserialize_async_header_response(header, dst_id, tlabel, retry, tcode, priority, src_id, rcode);
+ *data_length = async_header_get_data_length(header);
+ *extended_tcode = async_header_get_extended_tcode(header);
+}
+
+static void serialize_isoc_header(u32 *header, unsigned int data_length, unsigned int tag,
+ unsigned int channel, unsigned int tcode, unsigned int sy)
+{
+ isoc_header_set_data_length(header, data_length);
+ isoc_header_set_tag(header, tag);
+ isoc_header_set_channel(header, channel);
+ isoc_header_set_tcode(header, tcode);
+ isoc_header_set_sy(header, sy);
+}
+
+static void deserialize_isoc_header(u32 header, unsigned int *data_length, unsigned int *tag,
+ unsigned int *channel, unsigned int *tcode, unsigned int *sy)
+{
+ *data_length = isoc_header_get_data_length(header);
+ *tag = isoc_header_get_tag(header);
+ *channel = isoc_header_get_channel(header);
+ *tcode = isoc_header_get_tcode(header);
+ *sy = isoc_header_get_sy(header);
+}
+
+static void serialize_phy_packet_self_id_zero(u32 *quadlet, unsigned int packet_identifier,
+ unsigned int phy_id, bool extended,
+ bool link_is_active, unsigned int gap_count,
+ unsigned int scode, bool is_contender,
+ unsigned int power_class, bool is_initiated_reset,
+ bool has_more_packets)
+{
+ phy_packet_set_packet_identifier(quadlet, packet_identifier);
+ phy_packet_self_id_set_phy_id(quadlet, phy_id);
+ phy_packet_self_id_set_extended(quadlet, extended);
+ phy_packet_self_id_zero_set_link_active(quadlet, link_is_active);
+ phy_packet_self_id_zero_set_gap_count(quadlet, gap_count);
+ phy_packet_self_id_zero_set_scode(quadlet, scode);
+ phy_packet_self_id_zero_set_contender(quadlet, is_contender);
+ phy_packet_self_id_zero_set_power_class(quadlet, power_class);
+ phy_packet_self_id_zero_set_initiated_reset(quadlet, is_initiated_reset);
+ phy_packet_self_id_set_more_packets(quadlet, has_more_packets);
+}
+
+static void deserialize_phy_packet_self_id_zero(u32 quadlet, unsigned int *packet_identifier,
+ unsigned int *phy_id, bool *extended,
+ bool *link_is_active, unsigned int *gap_count,
+ unsigned int *scode, bool *is_contender,
+ unsigned int *power_class,
+ bool *is_initiated_reset, bool *has_more_packets)
+{
+ *packet_identifier = phy_packet_get_packet_identifier(quadlet);
+ *phy_id = phy_packet_self_id_get_phy_id(quadlet);
+ *extended = phy_packet_self_id_get_extended(quadlet);
+ *link_is_active = phy_packet_self_id_zero_get_link_active(quadlet);
+ *gap_count = phy_packet_self_id_zero_get_gap_count(quadlet);
+ *scode = phy_packet_self_id_zero_get_scode(quadlet);
+ *is_contender = phy_packet_self_id_zero_get_contender(quadlet);
+ *power_class = phy_packet_self_id_zero_get_power_class(quadlet);
+ *is_initiated_reset = phy_packet_self_id_zero_get_initiated_reset(quadlet);
+ *has_more_packets = phy_packet_self_id_get_more_packets(quadlet);
+}
+
+static void serialize_phy_packet_self_id_extended(u32 *quadlet, unsigned int packet_identifier,
+ unsigned int phy_id, bool extended,
+ unsigned int sequence, bool has_more_packets)
+{
+ phy_packet_set_packet_identifier(quadlet, packet_identifier);
+ phy_packet_self_id_set_phy_id(quadlet, phy_id);
+ phy_packet_self_id_set_extended(quadlet, extended);
+ phy_packet_self_id_extended_set_sequence(quadlet, sequence);
+ phy_packet_self_id_set_more_packets(quadlet, has_more_packets);
+}
+
+static void deserialize_phy_packet_self_id_extended(u32 quadlet, unsigned int *packet_identifier,
+ unsigned int *phy_id, bool *extended,
+ unsigned int *sequence, bool *has_more_packets)
+{
+ *packet_identifier = phy_packet_get_packet_identifier(quadlet);
+ *phy_id = phy_packet_self_id_get_phy_id(quadlet);
+ *extended = phy_packet_self_id_get_extended(quadlet);
+ *sequence = phy_packet_self_id_extended_get_sequence(quadlet);
+ *has_more_packets = phy_packet_self_id_get_more_packets(quadlet);
+}
+
+static void serialize_phy_packet_phy_config(u32 *quadlet, unsigned int packet_identifier,
+ unsigned int root_id, bool has_force_root_node,
+ bool has_gap_count_optimization, unsigned int gap_count)
+{
+ phy_packet_set_packet_identifier(quadlet, packet_identifier);
+ phy_packet_phy_config_set_root_id(quadlet, root_id);
+ phy_packet_phy_config_set_force_root_node(quadlet, has_force_root_node);
+ phy_packet_phy_config_set_gap_count_optimization(quadlet, has_gap_count_optimization);
+ phy_packet_phy_config_set_gap_count(quadlet, gap_count);
+}
+
+static void deserialize_phy_packet_phy_config(u32 quadlet, unsigned int *packet_identifier,
+ unsigned int *root_id, bool *has_force_root_node,
+ bool *has_gap_count_optimization,
+ unsigned int *gap_count)
+{
+ *packet_identifier = phy_packet_get_packet_identifier(quadlet);
+ *root_id = phy_packet_phy_config_get_root_id(quadlet);
+ *has_force_root_node = phy_packet_phy_config_get_force_root_node(quadlet);
+ *has_gap_count_optimization = phy_packet_phy_config_get_gap_count_optimization(quadlet);
+ *gap_count = phy_packet_phy_config_get_gap_count(quadlet);
+}
+
+static void test_async_header_write_quadlet_request(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc05100,
+ 0xffc1ffff,
+ 0xf0000234,
+ 0x1f0000c0,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ u64 offset;
+ u32 quadlet_data;
+
+ deserialize_async_header_quadlet_request(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &offset);
+ quadlet_data = async_header_get_quadlet_data(expected);
+
+ KUNIT_EXPECT_EQ(test, 0xffc0, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x14, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_WRITE_QUADLET_REQUEST, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc1, src_id);
+ KUNIT_EXPECT_EQ(test, 0xfffff0000234, offset);
+ KUNIT_EXPECT_EQ(test, 0x1f0000c0, quadlet_data);
+
+ serialize_async_header_quadlet_request(header, dst_id, tlabel, retry, tcode, priority,
+ src_id, offset);
+ async_header_set_quadlet_data(header, quadlet_data);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_write_block_request(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc06510,
+ 0xffc1ecc0,
+ 0x00000000,
+ 0x00180000,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ u64 offset;
+ unsigned int data_length;
+ unsigned int extended_tcode;
+
+ deserialize_async_header_block_request(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &offset, &data_length,
+ &extended_tcode);
+
+ KUNIT_EXPECT_EQ(test, 0xffc0, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x19, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_WRITE_BLOCK_REQUEST, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc1, src_id);
+ KUNIT_EXPECT_EQ(test, 0xecc000000000, offset);
+ KUNIT_EXPECT_EQ(test, 0x0018, data_length);
+ KUNIT_EXPECT_EQ(test, 0x0000, extended_tcode);
+
+ serialize_async_header_block_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset, data_length, extended_tcode);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_write_response(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc15120,
+ 0xffc00000,
+ 0x00000000,
+ 0x00000000,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ unsigned int rcode;
+
+ deserialize_async_header_quadlet_response(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &rcode);
+
+ KUNIT_EXPECT_EQ(test, 0xffc1, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x14, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_WRITE_RESPONSE, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc0, src_id);
+ KUNIT_EXPECT_EQ(test, RCODE_COMPLETE, rcode);
+
+ serialize_async_header_quadlet_response(header, dst_id, tlabel, retry, tcode, priority,
+ src_id, rcode);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected) - sizeof(expected[0]));
+}
+
+static void test_async_header_read_quadlet_request(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc0f140,
+ 0xffc1ffff,
+ 0xf0000984,
+ 0x00000000,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ u64 offset;
+
+ deserialize_async_header_quadlet_request(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &offset);
+
+ KUNIT_EXPECT_EQ(test, 0xffc0, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x3c, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_READ_QUADLET_REQUEST, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc1, src_id);
+ KUNIT_EXPECT_EQ(test, 0xfffff0000984, offset);
+
+ serialize_async_header_quadlet_request(header, dst_id, tlabel, retry, tcode, priority,
+ src_id, offset);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_read_quadlet_response(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc1f160,
+ 0xffc00000,
+ 0x00000000,
+ 0x00000180,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ unsigned int rcode;
+ u32 quadlet_data;
+
+ deserialize_async_header_quadlet_response(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &rcode);
+ quadlet_data = async_header_get_quadlet_data(expected);
+
+ KUNIT_EXPECT_EQ(test, 0xffc1, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x3c, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_READ_QUADLET_RESPONSE, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc0, src_id);
+ KUNIT_EXPECT_EQ(test, RCODE_COMPLETE, rcode);
+ KUNIT_EXPECT_EQ(test, 0x00000180, quadlet_data);
+
+ serialize_async_header_quadlet_response(header, dst_id, tlabel, retry, tcode, priority,
+ src_id, rcode);
+ async_header_set_quadlet_data(header, quadlet_data);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_read_block_request(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc0e150,
+ 0xffc1ffff,
+ 0xf0000400,
+ 0x00200000,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ u64 offset;
+ unsigned int data_length;
+ unsigned int extended_tcode;
+
+ deserialize_async_header_block_request(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &offset, &data_length,
+ &extended_tcode);
+
+ KUNIT_EXPECT_EQ(test, 0xffc0, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x38, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_READ_BLOCK_REQUEST, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc1, src_id);
+ KUNIT_EXPECT_EQ(test, 0xfffff0000400, offset);
+ KUNIT_EXPECT_EQ(test, 0x0020, data_length);
+ KUNIT_EXPECT_EQ(test, 0x0000, extended_tcode);
+
+ serialize_async_header_block_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset, data_length, extended_tcode);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_read_block_response(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc1e170,
+ 0xffc00000,
+ 0x00000000,
+ 0x00200000,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ unsigned int rcode;
+ unsigned int data_length;
+ unsigned int extended_tcode;
+
+ deserialize_async_header_block_response(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &rcode, &data_length,
+ &extended_tcode);
+
+ KUNIT_EXPECT_EQ(test, 0xffc1, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x38, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_READ_BLOCK_RESPONSE, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc0, src_id);
+ KUNIT_EXPECT_EQ(test, RCODE_COMPLETE, rcode);
+ KUNIT_EXPECT_EQ(test, 0x0020, data_length);
+ KUNIT_EXPECT_EQ(test, 0x0000, extended_tcode);
+
+ serialize_async_header_block_response(header, dst_id, tlabel, retry, tcode, priority,
+ src_id, rcode, data_length, extended_tcode);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_lock_request(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc02d90,
+ 0xffc1ffff,
+ 0xf0000984,
+ 0x00080002,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ u64 offset;
+ unsigned int data_length;
+ unsigned int extended_tcode;
+
+ deserialize_async_header_block_request(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &offset, &data_length,
+ &extended_tcode);
+
+ KUNIT_EXPECT_EQ(test, 0xffc0, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x0b, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_LOCK_REQUEST, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc1, src_id);
+ KUNIT_EXPECT_EQ(test, 0xfffff0000984, offset);
+ KUNIT_EXPECT_EQ(test, 0x0008, data_length);
+ KUNIT_EXPECT_EQ(test, EXTCODE_COMPARE_SWAP, extended_tcode);
+
+ serialize_async_header_block_request(header, dst_id, tlabel, retry, tcode, priority, src_id,
+ offset, data_length, extended_tcode);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_async_header_lock_response(struct kunit *test)
+{
+ static const u32 expected[ASYNC_HEADER_QUADLET_COUNT] = {
+ 0xffc12db0,
+ 0xffc00000,
+ 0x00000000,
+ 0x00040002,
+ };
+ u32 header[ASYNC_HEADER_QUADLET_COUNT] = {0, 0, 0, 0};
+
+ unsigned int dst_id;
+ unsigned int tlabel;
+ unsigned int retry;
+ unsigned int tcode;
+ unsigned int priority;
+ unsigned int src_id;
+ unsigned int rcode;
+ unsigned int data_length;
+ unsigned int extended_tcode;
+
+ deserialize_async_header_block_response(expected, &dst_id, &tlabel, &retry, &tcode,
+ &priority, &src_id, &rcode, &data_length,
+ &extended_tcode);
+
+ KUNIT_EXPECT_EQ(test, 0xffc1, dst_id);
+ KUNIT_EXPECT_EQ(test, 0x0b, tlabel);
+ KUNIT_EXPECT_EQ(test, 0x01, retry);
+ KUNIT_EXPECT_EQ(test, TCODE_LOCK_RESPONSE, tcode);
+ KUNIT_EXPECT_EQ(test, 0x00, priority);
+ KUNIT_EXPECT_EQ(test, 0xffc0, src_id);
+ KUNIT_EXPECT_EQ(test, RCODE_COMPLETE, rcode);
+ KUNIT_EXPECT_EQ(test, 0x0004, data_length);
+ KUNIT_EXPECT_EQ(test, EXTCODE_COMPARE_SWAP, extended_tcode);
+
+ serialize_async_header_block_response(header, dst_id, tlabel, retry, tcode, priority,
+ src_id, rcode, data_length, extended_tcode);
+
+ KUNIT_EXPECT_MEMEQ(test, header, expected, sizeof(expected));
+}
+
+static void test_isoc_header(struct kunit *test)
+{
+ const u32 expected = 0x00d08dec;
+ u32 header = 0;
+
+ unsigned int data_length;
+ unsigned int tag;
+ unsigned int channel;
+ unsigned int tcode;
+ unsigned int sy;
+
+ deserialize_isoc_header(expected, &data_length, &tag, &channel, &tcode, &sy);
+
+ KUNIT_EXPECT_EQ(test, 0xd0, data_length);
+ KUNIT_EXPECT_EQ(test, 0x02, tag);
+ KUNIT_EXPECT_EQ(test, 0x0d, channel);
+ KUNIT_EXPECT_EQ(test, 0x0e, tcode);
+ KUNIT_EXPECT_EQ(test, 0x0c, sy);
+
+ serialize_isoc_header(&header, data_length, tag, channel, tcode, sy);
+
+ KUNIT_EXPECT_EQ(test, header, expected);
+}
+
+static void test_phy_packet_self_id_zero_case0(struct kunit *test)
+{
+ // TSB41AB1/2 with 1 port.
+ const u32 expected[] = {0x80458c80};
+ u32 quadlets[] = {0};
+
+ unsigned int packet_identifier;
+ unsigned int phy_id;
+ bool extended;
+ bool link_is_active;
+ unsigned int gap_count;
+ unsigned int scode;
+ bool is_contender;
+ unsigned int power_class;
+ enum phy_packet_self_id_port_status port_status[3];
+ bool is_initiated_reset;
+ bool has_more_packets;
+ unsigned int port_index;
+
+ deserialize_phy_packet_self_id_zero(expected[0], &packet_identifier, &phy_id, &extended,
+ &link_is_active, &gap_count, &scode, &is_contender,
+ &power_class, &is_initiated_reset, &has_more_packets);
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_PACKET_IDENTIFIER_SELF_ID, packet_identifier);
+ KUNIT_EXPECT_EQ(test, 0, phy_id);
+ KUNIT_EXPECT_FALSE(test, extended);
+ KUNIT_EXPECT_TRUE(test, link_is_active);
+ KUNIT_EXPECT_EQ(test, 0x05, gap_count);
+ KUNIT_EXPECT_EQ(test, SCODE_400, scode);
+ KUNIT_EXPECT_TRUE(test, is_contender);
+ KUNIT_EXPECT_EQ(test, 0x4, power_class);
+ KUNIT_EXPECT_FALSE(test, is_initiated_reset);
+ KUNIT_EXPECT_FALSE(test, has_more_packets);
+
+ serialize_phy_packet_self_id_zero(quadlets, packet_identifier, phy_id, extended,
+ link_is_active, gap_count, scode, is_contender,
+ power_class, is_initiated_reset, has_more_packets);
+
+ for (port_index = 0; port_index < ARRAY_SIZE(port_status); ++port_index) {
+ port_status[port_index] =
+ self_id_sequence_get_port_status(expected, ARRAY_SIZE(expected), port_index);
+ }
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[0]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[1]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[2]);
+
+ for (port_index = 0; port_index < ARRAY_SIZE(port_status); ++port_index) {
+ self_id_sequence_set_port_status(quadlets, ARRAY_SIZE(quadlets), port_index,
+ port_status[port_index]);
+ }
+
+ KUNIT_EXPECT_MEMEQ(test, quadlets, expected, sizeof(expected));
+}
+
+static void test_phy_packet_self_id_zero_case1(struct kunit *test)
+{
+ // XIO2213 and TSB81BA3E with 3 ports.
+ const u32 expected[] = {0x817fcc5e};
+ u32 quadlets[] = {0};
+
+ unsigned int packet_identifier;
+ unsigned int phy_id;
+ bool extended;
+ bool link_is_active;
+ unsigned int gap_count;
+ unsigned int scode;
+ bool is_contender;
+ unsigned int power_class;
+ enum phy_packet_self_id_port_status port_status[3];
+ bool is_initiated_reset;
+ bool has_more_packets;
+ unsigned int port_index;
+
+ deserialize_phy_packet_self_id_zero(expected[0], &packet_identifier, &phy_id, &extended,
+ &link_is_active, &gap_count, &scode, &is_contender,
+ &power_class, &is_initiated_reset, &has_more_packets);
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_PACKET_IDENTIFIER_SELF_ID, packet_identifier);
+ KUNIT_EXPECT_EQ(test, 1, phy_id);
+ KUNIT_EXPECT_FALSE(test, extended);
+ KUNIT_EXPECT_TRUE(test, link_is_active);
+ KUNIT_EXPECT_EQ(test, 0x3f, gap_count);
+ KUNIT_EXPECT_EQ(test, SCODE_800, scode);
+ KUNIT_EXPECT_TRUE(test, is_contender);
+ KUNIT_EXPECT_EQ(test, 0x4, power_class);
+ KUNIT_EXPECT_TRUE(test, is_initiated_reset);
+ KUNIT_EXPECT_FALSE(test, has_more_packets);
+
+ serialize_phy_packet_self_id_zero(quadlets, packet_identifier, phy_id, extended,
+ link_is_active, gap_count, scode, is_contender,
+ power_class, is_initiated_reset, has_more_packets);
+
+ for (port_index = 0; port_index < ARRAY_SIZE(port_status); ++port_index) {
+ port_status[port_index] =
+ self_id_sequence_get_port_status(expected, ARRAY_SIZE(expected), port_index);
+ }
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[0]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[1]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[2]);
+
+ for (port_index = 0; port_index < ARRAY_SIZE(port_status); ++port_index) {
+ self_id_sequence_set_port_status(quadlets, ARRAY_SIZE(quadlets), port_index,
+ port_status[port_index]);
+ }
+
+ KUNIT_EXPECT_MEMEQ(test, quadlets, expected, sizeof(expected));
+}
+
+static void test_phy_packet_self_id_zero_and_one(struct kunit *test)
+{
+ // TSB41LV06A with 6 ports.
+ const u32 expected[] = {
+ 0x803f8459,
+ 0x80815000,
+ };
+ u32 quadlets[] = {0, 0};
+
+ unsigned int packet_identifier;
+ unsigned int phy_id;
+ bool extended;
+ bool link_is_active;
+ unsigned int gap_count;
+ unsigned int scode;
+ bool is_contender;
+ unsigned int power_class;
+ enum phy_packet_self_id_port_status port_status[11];
+ bool is_initiated_reset;
+ bool has_more_packets;
+
+ unsigned int sequence;
+ unsigned int port_index;
+
+ deserialize_phy_packet_self_id_zero(expected[0], &packet_identifier, &phy_id, &extended,
+ &link_is_active, &gap_count, &scode, &is_contender,
+ &power_class, &is_initiated_reset, &has_more_packets);
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_PACKET_IDENTIFIER_SELF_ID, packet_identifier);
+ KUNIT_EXPECT_EQ(test, 0, phy_id);
+ KUNIT_EXPECT_FALSE(test, extended);
+ KUNIT_EXPECT_FALSE(test, link_is_active);
+ KUNIT_EXPECT_EQ(test, 0x3f, gap_count);
+ KUNIT_EXPECT_EQ(test, SCODE_400, scode);
+ KUNIT_EXPECT_FALSE(test, is_contender);
+ KUNIT_EXPECT_EQ(test, 0x4, power_class);
+ KUNIT_EXPECT_FALSE(test, is_initiated_reset);
+ KUNIT_EXPECT_TRUE(test, has_more_packets);
+
+ serialize_phy_packet_self_id_zero(quadlets, packet_identifier, phy_id, extended,
+ link_is_active, gap_count, scode, is_contender,
+ power_class, is_initiated_reset, has_more_packets);
+
+ deserialize_phy_packet_self_id_extended(expected[1], &packet_identifier, &phy_id, &extended,
+ &sequence, &has_more_packets);
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_PACKET_IDENTIFIER_SELF_ID, packet_identifier);
+ KUNIT_EXPECT_EQ(test, 0, phy_id);
+ KUNIT_EXPECT_TRUE(test, extended);
+ KUNIT_EXPECT_EQ(test, 0, sequence);
+ KUNIT_EXPECT_FALSE(test, has_more_packets);
+
+ serialize_phy_packet_self_id_extended(&quadlets[1], packet_identifier, phy_id, extended,
+ sequence, has_more_packets);
+
+
+ for (port_index = 0; port_index < ARRAY_SIZE(port_status); ++port_index) {
+ port_status[port_index] =
+ self_id_sequence_get_port_status(expected, ARRAY_SIZE(expected), port_index);
+ }
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[0]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[1]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[2]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[3]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[4]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[5]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[6]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[7]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[8]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[9]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[10]);
+
+ for (port_index = 0; port_index < ARRAY_SIZE(port_status); ++port_index) {
+ self_id_sequence_set_port_status(quadlets, ARRAY_SIZE(quadlets), port_index,
+ port_status[port_index]);
+ }
+
+ KUNIT_EXPECT_MEMEQ(test, quadlets, expected, sizeof(expected));
+}
+
+static void test_phy_packet_phy_config_force_root_node(struct kunit *test)
+{
+ const u32 expected = 0x02800000;
+ u32 quadlet = 0;
+
+ unsigned int packet_identifier;
+ unsigned int root_id;
+ bool has_force_root_node;
+ bool has_gap_count_optimization;
+ unsigned int gap_count;
+
+ deserialize_phy_packet_phy_config(expected, &packet_identifier, &root_id,
+ &has_force_root_node, &has_gap_count_optimization,
+ &gap_count);
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_PACKET_IDENTIFIER_PHY_CONFIG, packet_identifier);
+ KUNIT_EXPECT_EQ(test, 0x02, root_id);
+ KUNIT_EXPECT_TRUE(test, has_force_root_node);
+ KUNIT_EXPECT_FALSE(test, has_gap_count_optimization);
+ KUNIT_EXPECT_EQ(test, 0, gap_count);
+
+ serialize_phy_packet_phy_config(&quadlet, packet_identifier, root_id, has_force_root_node,
+ has_gap_count_optimization, gap_count);
+
+ KUNIT_EXPECT_EQ(test, quadlet, expected);
+}
+
+static void test_phy_packet_phy_config_gap_count_optimization(struct kunit *test)
+{
+ const u32 expected = 0x034f0000;
+ u32 quadlet = 0;
+
+ unsigned int packet_identifier;
+ unsigned int root_id;
+ bool has_force_root_node;
+ bool has_gap_count_optimization;
+ unsigned int gap_count;
+
+ deserialize_phy_packet_phy_config(expected, &packet_identifier, &root_id,
+ &has_force_root_node, &has_gap_count_optimization,
+ &gap_count);
+
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_PACKET_IDENTIFIER_PHY_CONFIG, packet_identifier);
+ KUNIT_EXPECT_EQ(test, 0x03, root_id);
+ KUNIT_EXPECT_FALSE(test, has_force_root_node);
+ KUNIT_EXPECT_TRUE(test, has_gap_count_optimization);
+ KUNIT_EXPECT_EQ(test, 0x0f, gap_count);
+
+ serialize_phy_packet_phy_config(&quadlet, packet_identifier, root_id, has_force_root_node,
+ has_gap_count_optimization, gap_count);
+
+ KUNIT_EXPECT_EQ(test, quadlet, expected);
+}
+
+static struct kunit_case packet_serdes_test_cases[] = {
+ KUNIT_CASE(test_async_header_write_quadlet_request),
+ KUNIT_CASE(test_async_header_write_block_request),
+ KUNIT_CASE(test_async_header_write_response),
+ KUNIT_CASE(test_async_header_read_quadlet_request),
+ KUNIT_CASE(test_async_header_read_quadlet_response),
+ KUNIT_CASE(test_async_header_read_block_request),
+ KUNIT_CASE(test_async_header_read_block_response),
+ KUNIT_CASE(test_async_header_lock_request),
+ KUNIT_CASE(test_async_header_lock_response),
+ KUNIT_CASE(test_isoc_header),
+ KUNIT_CASE(test_phy_packet_self_id_zero_case0),
+ KUNIT_CASE(test_phy_packet_self_id_zero_case1),
+ KUNIT_CASE(test_phy_packet_self_id_zero_and_one),
+ KUNIT_CASE(test_phy_packet_phy_config_force_root_node),
+ KUNIT_CASE(test_phy_packet_phy_config_gap_count_optimization),
+ {}
+};
+
+static struct kunit_suite packet_serdes_test_suite = {
+ .name = "firewire-packet-serdes",
+ .test_cases = packet_serdes_test_cases,
+};
+kunit_test_suite(packet_serdes_test_suite);
+
+MODULE_DESCRIPTION("FireWire packet serialization/deserialization unit test suite");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firewire/phy-packet-definitions.h b/drivers/firewire/phy-packet-definitions.h
new file mode 100644
index 000000000..03c7c6067
--- /dev/null
+++ b/drivers/firewire/phy-packet-definitions.h
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// phy-packet-definitions.h - The definitions of phy packet for IEEE 1394.
+//
+// Copyright (c) 2024 Takashi Sakamoto
+
+#ifndef _FIREWIRE_PHY_PACKET_DEFINITIONS_H
+#define _FIREWIRE_PHY_PACKET_DEFINITIONS_H
+
+#define PACKET_IDENTIFIER_MASK 0xc0000000
+#define PACKET_IDENTIFIER_SHIFT 30
+
+static inline unsigned int phy_packet_get_packet_identifier(u32 quadlet)
+{
+ return (quadlet & PACKET_IDENTIFIER_MASK) >> PACKET_IDENTIFIER_SHIFT;
+}
+
+static inline void phy_packet_set_packet_identifier(u32 *quadlet, unsigned int packet_identifier)
+{
+ *quadlet &= ~PACKET_IDENTIFIER_MASK;
+ *quadlet |= (packet_identifier << PACKET_IDENTIFIER_SHIFT) & PACKET_IDENTIFIER_MASK;
+}
+
+#define PHY_PACKET_PACKET_IDENTIFIER_PHY_CONFIG 0
+
+#define PHY_CONFIG_ROOT_ID_MASK 0x3f000000
+#define PHY_CONFIG_ROOT_ID_SHIFT 24
+#define PHY_CONFIG_FORCE_ROOT_NODE_MASK 0x00800000
+#define PHY_CONFIG_FORCE_ROOT_NODE_SHIFT 23
+#define PHY_CONFIG_GAP_COUNT_OPTIMIZATION_MASK 0x00400000
+#define PHY_CONFIG_GAP_COUNT_OPTIMIZATION_SHIFT 22
+#define PHY_CONFIG_GAP_COUNT_MASK 0x003f0000
+#define PHY_CONFIG_GAP_COUNT_SHIFT 16
+
+static inline unsigned int phy_packet_phy_config_get_root_id(u32 quadlet)
+{
+ return (quadlet & PHY_CONFIG_ROOT_ID_MASK) >> PHY_CONFIG_ROOT_ID_SHIFT;
+}
+
+static inline void phy_packet_phy_config_set_root_id(u32 *quadlet, unsigned int root_id)
+{
+ *quadlet &= ~PHY_CONFIG_ROOT_ID_MASK;
+ *quadlet |= (root_id << PHY_CONFIG_ROOT_ID_SHIFT) & PHY_CONFIG_ROOT_ID_MASK;
+}
+
+static inline bool phy_packet_phy_config_get_force_root_node(u32 quadlet)
+{
+ return (quadlet & PHY_CONFIG_FORCE_ROOT_NODE_MASK) >> PHY_CONFIG_FORCE_ROOT_NODE_SHIFT;
+}
+
+static inline void phy_packet_phy_config_set_force_root_node(u32 *quadlet, bool has_force_root_node)
+{
+ *quadlet &= ~PHY_CONFIG_FORCE_ROOT_NODE_MASK;
+ *quadlet |= (has_force_root_node << PHY_CONFIG_FORCE_ROOT_NODE_SHIFT) & PHY_CONFIG_FORCE_ROOT_NODE_MASK;
+}
+
+static inline bool phy_packet_phy_config_get_gap_count_optimization(u32 quadlet)
+{
+ return (quadlet & PHY_CONFIG_GAP_COUNT_OPTIMIZATION_MASK) >> PHY_CONFIG_GAP_COUNT_OPTIMIZATION_SHIFT;
+}
+
+static inline void phy_packet_phy_config_set_gap_count_optimization(u32 *quadlet, bool has_gap_count_optimization)
+{
+ *quadlet &= ~PHY_CONFIG_GAP_COUNT_OPTIMIZATION_MASK;
+ *quadlet |= (has_gap_count_optimization << PHY_CONFIG_GAP_COUNT_OPTIMIZATION_SHIFT) & PHY_CONFIG_GAP_COUNT_OPTIMIZATION_MASK;
+}
+
+static inline unsigned int phy_packet_phy_config_get_gap_count(u32 quadlet)
+{
+ return (quadlet & PHY_CONFIG_GAP_COUNT_MASK) >> PHY_CONFIG_GAP_COUNT_SHIFT;
+}
+
+static inline void phy_packet_phy_config_set_gap_count(u32 *quadlet, unsigned int gap_count)
+{
+ *quadlet &= ~PHY_CONFIG_GAP_COUNT_MASK;
+ *quadlet |= (gap_count << PHY_CONFIG_GAP_COUNT_SHIFT) & PHY_CONFIG_GAP_COUNT_MASK;
+}
+
+#define PHY_PACKET_PACKET_IDENTIFIER_SELF_ID 2
+
+#define SELF_ID_PHY_ID_MASK 0x3f000000
+#define SELF_ID_PHY_ID_SHIFT 24
+#define SELF_ID_EXTENDED_MASK 0x00800000
+#define SELF_ID_EXTENDED_SHIFT 23
+#define SELF_ID_MORE_PACKETS_MASK 0x00000001
+#define SELF_ID_MORE_PACKETS_SHIFT 0
+
+#define SELF_ID_ZERO_LINK_ACTIVE_MASK 0x00400000
+#define SELF_ID_ZERO_LINK_ACTIVE_SHIFT 22
+#define SELF_ID_ZERO_GAP_COUNT_MASK 0x003f0000
+#define SELF_ID_ZERO_GAP_COUNT_SHIFT 16
+#define SELF_ID_ZERO_SCODE_MASK 0x0000c000
+#define SELF_ID_ZERO_SCODE_SHIFT 14
+#define SELF_ID_ZERO_CONTENDER_MASK 0x00000800
+#define SELF_ID_ZERO_CONTENDER_SHIFT 11
+#define SELF_ID_ZERO_POWER_CLASS_MASK 0x00000700
+#define SELF_ID_ZERO_POWER_CLASS_SHIFT 8
+#define SELF_ID_ZERO_INITIATED_RESET_MASK 0x00000002
+#define SELF_ID_ZERO_INITIATED_RESET_SHIFT 1
+
+#define SELF_ID_EXTENDED_SEQUENCE_MASK 0x00700000
+#define SELF_ID_EXTENDED_SEQUENCE_SHIFT 20
+
+#define SELF_ID_PORT_STATUS_MASK 0x3
+
+#define SELF_ID_SEQUENCE_MAXIMUM_QUADLET_COUNT 4
+
+static inline unsigned int phy_packet_self_id_get_phy_id(u32 quadlet)
+{
+ return (quadlet & SELF_ID_PHY_ID_MASK) >> SELF_ID_PHY_ID_SHIFT;
+}
+
+static inline void phy_packet_self_id_set_phy_id(u32 *quadlet, unsigned int phy_id)
+{
+ *quadlet &= ~SELF_ID_PHY_ID_MASK;
+ *quadlet |= (phy_id << SELF_ID_PHY_ID_SHIFT) & SELF_ID_PHY_ID_MASK;
+}
+
+static inline bool phy_packet_self_id_get_extended(u32 quadlet)
+{
+ return (quadlet & SELF_ID_EXTENDED_MASK) >> SELF_ID_EXTENDED_SHIFT;
+}
+
+static inline void phy_packet_self_id_set_extended(u32 *quadlet, bool extended)
+{
+ *quadlet &= ~SELF_ID_EXTENDED_MASK;
+ *quadlet |= (extended << SELF_ID_EXTENDED_SHIFT) & SELF_ID_EXTENDED_MASK;
+}
+
+static inline bool phy_packet_self_id_zero_get_link_active(u32 quadlet)
+{
+ return (quadlet & SELF_ID_ZERO_LINK_ACTIVE_MASK) >> SELF_ID_ZERO_LINK_ACTIVE_SHIFT;
+}
+
+static inline void phy_packet_self_id_zero_set_link_active(u32 *quadlet, bool is_active)
+{
+ *quadlet &= ~SELF_ID_ZERO_LINK_ACTIVE_MASK;
+ *quadlet |= (is_active << SELF_ID_ZERO_LINK_ACTIVE_SHIFT) & SELF_ID_ZERO_LINK_ACTIVE_MASK;
+}
+
+static inline unsigned int phy_packet_self_id_zero_get_gap_count(u32 quadlet)
+{
+ return (quadlet & SELF_ID_ZERO_GAP_COUNT_MASK) >> SELF_ID_ZERO_GAP_COUNT_SHIFT;
+}
+
+static inline void phy_packet_self_id_zero_set_gap_count(u32 *quadlet, unsigned int gap_count)
+{
+ *quadlet &= ~SELF_ID_ZERO_GAP_COUNT_MASK;
+ *quadlet |= (gap_count << SELF_ID_ZERO_GAP_COUNT_SHIFT) & SELF_ID_ZERO_GAP_COUNT_MASK;
+}
+
+static inline unsigned int phy_packet_self_id_zero_get_scode(u32 quadlet)
+{
+ return (quadlet & SELF_ID_ZERO_SCODE_MASK) >> SELF_ID_ZERO_SCODE_SHIFT;
+}
+
+static inline void phy_packet_self_id_zero_set_scode(u32 *quadlet, unsigned int speed)
+{
+ *quadlet &= ~SELF_ID_ZERO_SCODE_MASK;
+ *quadlet |= (speed << SELF_ID_ZERO_SCODE_SHIFT) & SELF_ID_ZERO_SCODE_MASK;
+}
+
+static inline bool phy_packet_self_id_zero_get_contender(u32 quadlet)
+{
+ return (quadlet & SELF_ID_ZERO_CONTENDER_MASK) >> SELF_ID_ZERO_CONTENDER_SHIFT;
+}
+
+static inline void phy_packet_self_id_zero_set_contender(u32 *quadlet, bool is_contender)
+{
+ *quadlet &= ~SELF_ID_ZERO_CONTENDER_MASK;
+ *quadlet |= (is_contender << SELF_ID_ZERO_CONTENDER_SHIFT) & SELF_ID_ZERO_CONTENDER_MASK;
+}
+
+static inline unsigned int phy_packet_self_id_zero_get_power_class(u32 quadlet)
+{
+ return (quadlet & SELF_ID_ZERO_POWER_CLASS_MASK) >> SELF_ID_ZERO_POWER_CLASS_SHIFT;
+}
+
+static inline void phy_packet_self_id_zero_set_power_class(u32 *quadlet, unsigned int power_class)
+{
+ *quadlet &= ~SELF_ID_ZERO_POWER_CLASS_MASK;
+ *quadlet |= (power_class << SELF_ID_ZERO_POWER_CLASS_SHIFT) & SELF_ID_ZERO_POWER_CLASS_MASK;
+}
+
+static inline bool phy_packet_self_id_zero_get_initiated_reset(u32 quadlet)
+{
+ return (quadlet & SELF_ID_ZERO_INITIATED_RESET_MASK) >> SELF_ID_ZERO_INITIATED_RESET_SHIFT;
+}
+
+static inline void phy_packet_self_id_zero_set_initiated_reset(u32 *quadlet, bool is_initiated_reset)
+{
+ *quadlet &= ~SELF_ID_ZERO_INITIATED_RESET_MASK;
+ *quadlet |= (is_initiated_reset << SELF_ID_ZERO_INITIATED_RESET_SHIFT) & SELF_ID_ZERO_INITIATED_RESET_MASK;
+}
+
+static inline bool phy_packet_self_id_get_more_packets(u32 quadlet)
+{
+ return (quadlet & SELF_ID_MORE_PACKETS_MASK) >> SELF_ID_MORE_PACKETS_SHIFT;
+}
+
+static inline void phy_packet_self_id_set_more_packets(u32 *quadlet, bool is_more_packets)
+{
+ *quadlet &= ~SELF_ID_MORE_PACKETS_MASK;
+ *quadlet |= (is_more_packets << SELF_ID_MORE_PACKETS_SHIFT) & SELF_ID_MORE_PACKETS_MASK;
+}
+
+static inline unsigned int phy_packet_self_id_extended_get_sequence(u32 quadlet)
+{
+ return (quadlet & SELF_ID_EXTENDED_SEQUENCE_MASK) >> SELF_ID_EXTENDED_SEQUENCE_SHIFT;
+}
+
+static inline void phy_packet_self_id_extended_set_sequence(u32 *quadlet, unsigned int sequence)
+{
+ *quadlet &= ~SELF_ID_EXTENDED_SEQUENCE_MASK;
+ *quadlet |= (sequence << SELF_ID_EXTENDED_SHIFT) & SELF_ID_EXTENDED_SEQUENCE_MASK;
+}
+
+struct self_id_sequence_enumerator {
+ const u32 *cursor;
+ unsigned int quadlet_count;
+};
+
+static inline const u32 *self_id_sequence_enumerator_next(
+ struct self_id_sequence_enumerator *enumerator, unsigned int *quadlet_count)
+{
+ const u32 *self_id_sequence, *cursor;
+ u32 quadlet;
+ unsigned int count;
+ unsigned int sequence;
+
+ if (enumerator->cursor == NULL || enumerator->quadlet_count == 0)
+ return ERR_PTR(-ENODATA);
+ cursor = enumerator->cursor;
+ count = 1;
+
+ quadlet = *cursor;
+ sequence = 0;
+ while (phy_packet_self_id_get_more_packets(quadlet)) {
+ if (count >= enumerator->quadlet_count ||
+ count >= SELF_ID_SEQUENCE_MAXIMUM_QUADLET_COUNT)
+ return ERR_PTR(-EPROTO);
+ ++cursor;
+ ++count;
+ quadlet = *cursor;
+
+ if (!phy_packet_self_id_get_extended(quadlet) ||
+ sequence != phy_packet_self_id_extended_get_sequence(quadlet))
+ return ERR_PTR(-EPROTO);
+ ++sequence;
+ }
+
+ *quadlet_count = count;
+ self_id_sequence = enumerator->cursor;
+
+ enumerator->cursor += count;
+ enumerator->quadlet_count -= count;
+
+ return self_id_sequence;
+}
+
+enum phy_packet_self_id_port_status {
+ PHY_PACKET_SELF_ID_PORT_STATUS_NONE = 0,
+ PHY_PACKET_SELF_ID_PORT_STATUS_NCONN = 1,
+ PHY_PACKET_SELF_ID_PORT_STATUS_PARENT = 2,
+ PHY_PACKET_SELF_ID_PORT_STATUS_CHILD = 3,
+};
+
+static inline unsigned int self_id_sequence_get_port_capacity(unsigned int quadlet_count)
+{
+ return quadlet_count * 8 - 5;
+}
+
+static inline enum phy_packet_self_id_port_status self_id_sequence_get_port_status(
+ const u32 *self_id_sequence, unsigned int quadlet_count, unsigned int port_index)
+{
+ unsigned int index, shift;
+
+ index = (port_index + 5) / 8;
+ shift = 16 - ((port_index + 5) % 8) * 2;
+
+ if (index < quadlet_count && index < SELF_ID_SEQUENCE_MAXIMUM_QUADLET_COUNT)
+ return (self_id_sequence[index] >> shift) & SELF_ID_PORT_STATUS_MASK;
+
+ return PHY_PACKET_SELF_ID_PORT_STATUS_NONE;
+}
+
+static inline void self_id_sequence_set_port_status(u32 *self_id_sequence, unsigned int quadlet_count,
+ unsigned int port_index,
+ enum phy_packet_self_id_port_status status)
+{
+ unsigned int index, shift;
+
+ index = (port_index + 5) / 8;
+ shift = 16 - ((port_index + 5) % 8) * 2;
+
+ if (index < quadlet_count) {
+ self_id_sequence[index] &= ~(SELF_ID_PORT_STATUS_MASK << shift);
+ self_id_sequence[index] |= status << shift;
+ }
+}
+
+#endif // _FIREWIRE_PHY_PACKET_DEFINITIONS_H
diff --git a/drivers/firewire/sbp2.c b/drivers/firewire/sbp2.c
new file mode 100644
index 000000000..31ee94ecb
--- /dev/null
+++ b/drivers/firewire/sbp2.c
@@ -0,0 +1,1622 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SBP2 driver (SCSI over IEEE1394)
+ *
+ * Copyright (C) 2005-2007 Kristian Hoegsberg <krh@bitplanet.net>
+ */
+
+/*
+ * The basic structure of this driver is based on the old storage driver,
+ * drivers/ieee1394/sbp2.c, originally written by
+ * James Goodwin <jamesg@filanet.com>
+ * with later contributions and ongoing maintenance from
+ * Ben Collins <bcollins@debian.org>,
+ * Stefan Richter <stefanr@s5r6.in-berlin.de>
+ * and many others.
+ */
+
+#include <linux/blkdev.h>
+#include <linux/bug.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/stringify.h>
+#include <linux/workqueue.h>
+
+#include <asm/byteorder.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+
+/*
+ * So far only bridges from Oxford Semiconductor are known to support
+ * concurrent logins. Depending on firmware, four or two concurrent logins
+ * are possible on OXFW911 and newer Oxsemi bridges.
+ *
+ * Concurrent logins are useful together with cluster filesystems.
+ */
+static bool sbp2_param_exclusive_login = 1;
+module_param_named(exclusive_login, sbp2_param_exclusive_login, bool, 0644);
+MODULE_PARM_DESC(exclusive_login, "Exclusive login to sbp2 device "
+ "(default = Y, use N for concurrent initiators)");
+
+/*
+ * Flags for firmware oddities
+ *
+ * - 128kB max transfer
+ * Limit transfer size. Necessary for some old bridges.
+ *
+ * - 36 byte inquiry
+ * When scsi_mod probes the device, let the inquiry command look like that
+ * from MS Windows.
+ *
+ * - skip mode page 8
+ * Suppress sending of mode_sense for mode page 8 if the device pretends to
+ * support the SCSI Primary Block commands instead of Reduced Block Commands.
+ *
+ * - fix capacity
+ * Tell sd_mod to correct the last sector number reported by read_capacity.
+ * Avoids access beyond actual disk limits on devices with an off-by-one bug.
+ * Don't use this with devices which don't have this bug.
+ *
+ * - delay inquiry
+ * Wait extra SBP2_INQUIRY_DELAY seconds after login before SCSI inquiry.
+ *
+ * - power condition
+ * Set the power condition field in the START STOP UNIT commands sent by
+ * sd_mod on suspend, resume, and shutdown (if manage_system_start_stop or
+ * manage_runtime_start_stop is on).
+ * Some disks need this to spin down or to resume properly.
+ *
+ * - override internal blacklist
+ * Instead of adding to the built-in blacklist, use only the workarounds
+ * specified in the module load parameter.
+ * Useful if a blacklist entry interfered with a non-broken device.
+ */
+#define SBP2_WORKAROUND_128K_MAX_TRANS 0x1
+#define SBP2_WORKAROUND_INQUIRY_36 0x2
+#define SBP2_WORKAROUND_MODE_SENSE_8 0x4
+#define SBP2_WORKAROUND_FIX_CAPACITY 0x8
+#define SBP2_WORKAROUND_DELAY_INQUIRY 0x10
+#define SBP2_INQUIRY_DELAY 12
+#define SBP2_WORKAROUND_POWER_CONDITION 0x20
+#define SBP2_WORKAROUND_OVERRIDE 0x100
+
+static int sbp2_param_workarounds;
+module_param_named(workarounds, sbp2_param_workarounds, int, 0644);
+MODULE_PARM_DESC(workarounds, "Work around device bugs (default = 0"
+ ", 128kB max transfer = " __stringify(SBP2_WORKAROUND_128K_MAX_TRANS)
+ ", 36 byte inquiry = " __stringify(SBP2_WORKAROUND_INQUIRY_36)
+ ", skip mode page 8 = " __stringify(SBP2_WORKAROUND_MODE_SENSE_8)
+ ", fix capacity = " __stringify(SBP2_WORKAROUND_FIX_CAPACITY)
+ ", delay inquiry = " __stringify(SBP2_WORKAROUND_DELAY_INQUIRY)
+ ", set power condition in start stop unit = "
+ __stringify(SBP2_WORKAROUND_POWER_CONDITION)
+ ", override internal blacklist = " __stringify(SBP2_WORKAROUND_OVERRIDE)
+ ", or a combination)");
+
+/*
+ * We create one struct sbp2_logical_unit per SBP-2 Logical Unit Number Entry
+ * and one struct scsi_device per sbp2_logical_unit.
+ */
+struct sbp2_logical_unit {
+ struct sbp2_target *tgt;
+ struct list_head link;
+ struct fw_address_handler address_handler;
+ struct list_head orb_list;
+
+ u64 command_block_agent_address;
+ u16 lun;
+ int login_id;
+
+ /*
+ * The generation is updated once we've logged in or reconnected
+ * to the logical unit. Thus, I/O to the device will automatically
+ * fail and get retried if it happens in a window where the device
+ * is not ready, e.g. after a bus reset but before we reconnect.
+ */
+ int generation;
+ int retries;
+ work_func_t workfn;
+ struct delayed_work work;
+ bool has_sdev;
+ bool blocked;
+};
+
+static void sbp2_queue_work(struct sbp2_logical_unit *lu, unsigned long delay)
+{
+ queue_delayed_work(fw_workqueue, &lu->work, delay);
+}
+
+/*
+ * We create one struct sbp2_target per IEEE 1212 Unit Directory
+ * and one struct Scsi_Host per sbp2_target.
+ */
+struct sbp2_target {
+ struct fw_unit *unit;
+ struct list_head lu_list;
+
+ u64 management_agent_address;
+ u64 guid;
+ int directory_id;
+ int node_id;
+ int address_high;
+ unsigned int workarounds;
+ unsigned int mgt_orb_timeout;
+ unsigned int max_payload;
+
+ spinlock_t lock;
+ int dont_block; /* counter for each logical unit */
+ int blocked; /* ditto */
+};
+
+static struct fw_device *target_parent_device(struct sbp2_target *tgt)
+{
+ return fw_parent_device(tgt->unit);
+}
+
+static const struct device *tgt_dev(const struct sbp2_target *tgt)
+{
+ return &tgt->unit->device;
+}
+
+static const struct device *lu_dev(const struct sbp2_logical_unit *lu)
+{
+ return &lu->tgt->unit->device;
+}
+
+/* Impossible login_id, to detect logout attempt before successful login */
+#define INVALID_LOGIN_ID 0x10000
+
+#define SBP2_ORB_TIMEOUT 2000U /* Timeout in ms */
+#define SBP2_ORB_NULL 0x80000000
+#define SBP2_RETRY_LIMIT 0xf /* 15 retries */
+#define SBP2_CYCLE_LIMIT (0xc8 << 12) /* 200 125us cycles */
+
+/*
+ * There is no transport protocol limit to the CDB length, but we implement
+ * a fixed length only. 16 bytes is enough for disks larger than 2 TB.
+ */
+#define SBP2_MAX_CDB_SIZE 16
+
+/*
+ * The maximum SBP-2 data buffer size is 0xffff. We quadlet-align this
+ * for compatibility with earlier versions of this driver.
+ */
+#define SBP2_MAX_SEG_SIZE 0xfffc
+
+/* Unit directory keys */
+#define SBP2_CSR_UNIT_CHARACTERISTICS 0x3a
+#define SBP2_CSR_FIRMWARE_REVISION 0x3c
+#define SBP2_CSR_LOGICAL_UNIT_NUMBER 0x14
+#define SBP2_CSR_UNIT_UNIQUE_ID 0x8d
+#define SBP2_CSR_LOGICAL_UNIT_DIRECTORY 0xd4
+
+/* Management orb opcodes */
+#define SBP2_LOGIN_REQUEST 0x0
+#define SBP2_QUERY_LOGINS_REQUEST 0x1
+#define SBP2_RECONNECT_REQUEST 0x3
+#define SBP2_SET_PASSWORD_REQUEST 0x4
+#define SBP2_LOGOUT_REQUEST 0x7
+#define SBP2_ABORT_TASK_REQUEST 0xb
+#define SBP2_ABORT_TASK_SET 0xc
+#define SBP2_LOGICAL_UNIT_RESET 0xe
+#define SBP2_TARGET_RESET_REQUEST 0xf
+
+/* Offsets for command block agent registers */
+#define SBP2_AGENT_STATE 0x00
+#define SBP2_AGENT_RESET 0x04
+#define SBP2_ORB_POINTER 0x08
+#define SBP2_DOORBELL 0x10
+#define SBP2_UNSOLICITED_STATUS_ENABLE 0x14
+
+/* Status write response codes */
+#define SBP2_STATUS_REQUEST_COMPLETE 0x0
+#define SBP2_STATUS_TRANSPORT_FAILURE 0x1
+#define SBP2_STATUS_ILLEGAL_REQUEST 0x2
+#define SBP2_STATUS_VENDOR_DEPENDENT 0x3
+
+#define STATUS_GET_ORB_HIGH(v) ((v).status & 0xffff)
+#define STATUS_GET_SBP_STATUS(v) (((v).status >> 16) & 0xff)
+#define STATUS_GET_LEN(v) (((v).status >> 24) & 0x07)
+#define STATUS_GET_DEAD(v) (((v).status >> 27) & 0x01)
+#define STATUS_GET_RESPONSE(v) (((v).status >> 28) & 0x03)
+#define STATUS_GET_SOURCE(v) (((v).status >> 30) & 0x03)
+#define STATUS_GET_ORB_LOW(v) ((v).orb_low)
+#define STATUS_GET_DATA(v) ((v).data)
+
+struct sbp2_status {
+ u32 status;
+ u32 orb_low;
+ u8 data[24];
+};
+
+struct sbp2_pointer {
+ __be32 high;
+ __be32 low;
+};
+
+struct sbp2_orb {
+ struct fw_transaction t;
+ struct kref kref;
+ dma_addr_t request_bus;
+ int rcode;
+ void (*callback)(struct sbp2_orb * orb, struct sbp2_status * status);
+ struct sbp2_logical_unit *lu;
+ struct list_head link;
+};
+
+#define MANAGEMENT_ORB_LUN(v) ((v))
+#define MANAGEMENT_ORB_FUNCTION(v) ((v) << 16)
+#define MANAGEMENT_ORB_RECONNECT(v) ((v) << 20)
+#define MANAGEMENT_ORB_EXCLUSIVE(v) ((v) ? 1 << 28 : 0)
+#define MANAGEMENT_ORB_REQUEST_FORMAT(v) ((v) << 29)
+#define MANAGEMENT_ORB_NOTIFY ((1) << 31)
+
+#define MANAGEMENT_ORB_RESPONSE_LENGTH(v) ((v))
+#define MANAGEMENT_ORB_PASSWORD_LENGTH(v) ((v) << 16)
+
+struct sbp2_management_orb {
+ struct sbp2_orb base;
+ struct {
+ struct sbp2_pointer password;
+ struct sbp2_pointer response;
+ __be32 misc;
+ __be32 length;
+ struct sbp2_pointer status_fifo;
+ } request;
+ __be32 response[4];
+ dma_addr_t response_bus;
+ struct completion done;
+ struct sbp2_status status;
+};
+
+struct sbp2_login_response {
+ __be32 misc;
+ struct sbp2_pointer command_block_agent;
+ __be32 reconnect_hold;
+};
+#define COMMAND_ORB_DATA_SIZE(v) ((v))
+#define COMMAND_ORB_PAGE_SIZE(v) ((v) << 16)
+#define COMMAND_ORB_PAGE_TABLE_PRESENT ((1) << 19)
+#define COMMAND_ORB_MAX_PAYLOAD(v) ((v) << 20)
+#define COMMAND_ORB_SPEED(v) ((v) << 24)
+#define COMMAND_ORB_DIRECTION ((1) << 27)
+#define COMMAND_ORB_REQUEST_FORMAT(v) ((v) << 29)
+#define COMMAND_ORB_NOTIFY ((1) << 31)
+
+struct sbp2_command_orb {
+ struct sbp2_orb base;
+ struct {
+ struct sbp2_pointer next;
+ struct sbp2_pointer data_descriptor;
+ __be32 misc;
+ u8 command_block[SBP2_MAX_CDB_SIZE];
+ } request;
+ struct scsi_cmnd *cmd;
+
+ struct sbp2_pointer page_table[SG_ALL] __attribute__((aligned(8)));
+ dma_addr_t page_table_bus;
+};
+
+#define SBP2_ROM_VALUE_WILDCARD ~0 /* match all */
+#define SBP2_ROM_VALUE_MISSING 0xff000000 /* not present in the unit dir. */
+
+/*
+ * List of devices with known bugs.
+ *
+ * The firmware_revision field, masked with 0xffff00, is the best
+ * indicator for the type of bridge chip of a device. It yields a few
+ * false positives but this did not break correctly behaving devices
+ * so far.
+ */
+static const struct {
+ u32 firmware_revision;
+ u32 model;
+ unsigned int workarounds;
+} sbp2_workarounds_table[] = {
+ /* DViCO Momobay CX-1 with TSB42AA9 bridge */ {
+ .firmware_revision = 0x002800,
+ .model = 0x001010,
+ .workarounds = SBP2_WORKAROUND_INQUIRY_36 |
+ SBP2_WORKAROUND_MODE_SENSE_8 |
+ SBP2_WORKAROUND_POWER_CONDITION,
+ },
+ /* DViCO Momobay FX-3A with TSB42AA9A bridge */ {
+ .firmware_revision = 0x002800,
+ .model = 0x000000,
+ .workarounds = SBP2_WORKAROUND_POWER_CONDITION,
+ },
+ /* Initio bridges, actually only needed for some older ones */ {
+ .firmware_revision = 0x000200,
+ .model = SBP2_ROM_VALUE_WILDCARD,
+ .workarounds = SBP2_WORKAROUND_INQUIRY_36,
+ },
+ /* PL-3507 bridge with Prolific firmware */ {
+ .firmware_revision = 0x012800,
+ .model = SBP2_ROM_VALUE_WILDCARD,
+ .workarounds = SBP2_WORKAROUND_POWER_CONDITION,
+ },
+ /* Symbios bridge */ {
+ .firmware_revision = 0xa0b800,
+ .model = SBP2_ROM_VALUE_WILDCARD,
+ .workarounds = SBP2_WORKAROUND_128K_MAX_TRANS,
+ },
+ /* Datafab MD2-FW2 with Symbios/LSILogic SYM13FW500 bridge */ {
+ .firmware_revision = 0x002600,
+ .model = SBP2_ROM_VALUE_WILDCARD,
+ .workarounds = SBP2_WORKAROUND_128K_MAX_TRANS,
+ },
+ /*
+ * iPod 2nd generation: needs 128k max transfer size workaround
+ * iPod 3rd generation: needs fix capacity workaround
+ */
+ {
+ .firmware_revision = 0x0a2700,
+ .model = 0x000000,
+ .workarounds = SBP2_WORKAROUND_128K_MAX_TRANS |
+ SBP2_WORKAROUND_FIX_CAPACITY,
+ },
+ /* iPod 4th generation */ {
+ .firmware_revision = 0x0a2700,
+ .model = 0x000021,
+ .workarounds = SBP2_WORKAROUND_FIX_CAPACITY,
+ },
+ /* iPod mini */ {
+ .firmware_revision = 0x0a2700,
+ .model = 0x000022,
+ .workarounds = SBP2_WORKAROUND_FIX_CAPACITY,
+ },
+ /* iPod mini */ {
+ .firmware_revision = 0x0a2700,
+ .model = 0x000023,
+ .workarounds = SBP2_WORKAROUND_FIX_CAPACITY,
+ },
+ /* iPod Photo */ {
+ .firmware_revision = 0x0a2700,
+ .model = 0x00007e,
+ .workarounds = SBP2_WORKAROUND_FIX_CAPACITY,
+ }
+};
+
+static void free_orb(struct kref *kref)
+{
+ struct sbp2_orb *orb = container_of(kref, struct sbp2_orb, kref);
+
+ kfree(orb);
+}
+
+static void sbp2_status_write(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)
+{
+ struct sbp2_logical_unit *lu = callback_data;
+ struct sbp2_orb *orb = NULL, *iter;
+ struct sbp2_status status;
+ unsigned long flags;
+
+ if (tcode != TCODE_WRITE_BLOCK_REQUEST ||
+ length < 8 || length > sizeof(status)) {
+ fw_send_response(card, request, RCODE_TYPE_ERROR);
+ return;
+ }
+
+ status.status = be32_to_cpup(payload);
+ status.orb_low = be32_to_cpup(payload + 4);
+ memset(status.data, 0, sizeof(status.data));
+ if (length > 8)
+ memcpy(status.data, payload + 8, length - 8);
+
+ if (STATUS_GET_SOURCE(status) == 2 || STATUS_GET_SOURCE(status) == 3) {
+ dev_notice(lu_dev(lu),
+ "non-ORB related status write, not handled\n");
+ fw_send_response(card, request, RCODE_COMPLETE);
+ return;
+ }
+
+ /* Lookup the orb corresponding to this status write. */
+ spin_lock_irqsave(&lu->tgt->lock, flags);
+ list_for_each_entry(iter, &lu->orb_list, link) {
+ if (STATUS_GET_ORB_HIGH(status) == 0 &&
+ STATUS_GET_ORB_LOW(status) == iter->request_bus) {
+ iter->rcode = RCODE_COMPLETE;
+ list_del(&iter->link);
+ orb = iter;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&lu->tgt->lock, flags);
+
+ if (orb) {
+ orb->callback(orb, &status);
+ kref_put(&orb->kref, free_orb); /* orb callback reference */
+ } else {
+ dev_err(lu_dev(lu), "status write for unknown ORB\n");
+ }
+
+ fw_send_response(card, request, RCODE_COMPLETE);
+}
+
+static void complete_transaction(struct fw_card *card, int rcode,
+ void *payload, size_t length, void *data)
+{
+ struct sbp2_orb *orb = data;
+ unsigned long flags;
+
+ /*
+ * This is a little tricky. We can get the status write for
+ * the orb before we get this callback. The status write
+ * handler above will assume the orb pointer transaction was
+ * successful and set the rcode to RCODE_COMPLETE for the orb.
+ * So this callback only sets the rcode if it hasn't already
+ * been set and only does the cleanup if the transaction
+ * failed and we didn't already get a status write.
+ */
+ spin_lock_irqsave(&orb->lu->tgt->lock, flags);
+
+ if (orb->rcode == -1)
+ orb->rcode = rcode;
+ if (orb->rcode != RCODE_COMPLETE) {
+ list_del(&orb->link);
+ spin_unlock_irqrestore(&orb->lu->tgt->lock, flags);
+
+ orb->callback(orb, NULL);
+ kref_put(&orb->kref, free_orb); /* orb callback reference */
+ } else {
+ spin_unlock_irqrestore(&orb->lu->tgt->lock, flags);
+ }
+
+ kref_put(&orb->kref, free_orb); /* transaction callback reference */
+}
+
+static void sbp2_send_orb(struct sbp2_orb *orb, struct sbp2_logical_unit *lu,
+ int node_id, int generation, u64 offset)
+{
+ struct fw_device *device = target_parent_device(lu->tgt);
+ struct sbp2_pointer orb_pointer;
+ unsigned long flags;
+
+ orb_pointer.high = 0;
+ orb_pointer.low = cpu_to_be32(orb->request_bus);
+
+ orb->lu = lu;
+ spin_lock_irqsave(&lu->tgt->lock, flags);
+ list_add_tail(&orb->link, &lu->orb_list);
+ spin_unlock_irqrestore(&lu->tgt->lock, flags);
+
+ kref_get(&orb->kref); /* transaction callback reference */
+ kref_get(&orb->kref); /* orb callback reference */
+
+ fw_send_request(device->card, &orb->t, TCODE_WRITE_BLOCK_REQUEST,
+ node_id, generation, device->max_speed, offset,
+ &orb_pointer, 8, complete_transaction, orb);
+}
+
+static int sbp2_cancel_orbs(struct sbp2_logical_unit *lu)
+{
+ struct fw_device *device = target_parent_device(lu->tgt);
+ struct sbp2_orb *orb, *next;
+ struct list_head list;
+ int retval = -ENOENT;
+
+ INIT_LIST_HEAD(&list);
+ spin_lock_irq(&lu->tgt->lock);
+ list_splice_init(&lu->orb_list, &list);
+ spin_unlock_irq(&lu->tgt->lock);
+
+ list_for_each_entry_safe(orb, next, &list, link) {
+ retval = 0;
+ if (fw_cancel_transaction(device->card, &orb->t) == 0)
+ continue;
+
+ orb->rcode = RCODE_CANCELLED;
+ orb->callback(orb, NULL);
+ kref_put(&orb->kref, free_orb); /* orb callback reference */
+ }
+
+ return retval;
+}
+
+static void complete_management_orb(struct sbp2_orb *base_orb,
+ struct sbp2_status *status)
+{
+ struct sbp2_management_orb *orb =
+ container_of(base_orb, struct sbp2_management_orb, base);
+
+ if (status)
+ memcpy(&orb->status, status, sizeof(*status));
+ complete(&orb->done);
+}
+
+static int sbp2_send_management_orb(struct sbp2_logical_unit *lu, int node_id,
+ int generation, int function,
+ int lun_or_login_id, void *response)
+{
+ struct fw_device *device = target_parent_device(lu->tgt);
+ struct sbp2_management_orb *orb;
+ unsigned int timeout;
+ int retval = -ENOMEM;
+
+ if (function == SBP2_LOGOUT_REQUEST && fw_device_is_shutdown(device))
+ return 0;
+
+ orb = kzalloc_obj(*orb, GFP_NOIO);
+ if (orb == NULL)
+ return -ENOMEM;
+
+ kref_init(&orb->base.kref);
+ orb->response_bus =
+ dma_map_single(device->card->device, &orb->response,
+ sizeof(orb->response), DMA_FROM_DEVICE);
+ if (dma_mapping_error(device->card->device, orb->response_bus))
+ goto fail_mapping_response;
+
+ orb->request.response.high = 0;
+ orb->request.response.low = cpu_to_be32(orb->response_bus);
+
+ orb->request.misc = cpu_to_be32(
+ MANAGEMENT_ORB_NOTIFY |
+ MANAGEMENT_ORB_FUNCTION(function) |
+ MANAGEMENT_ORB_LUN(lun_or_login_id));
+ orb->request.length = cpu_to_be32(
+ MANAGEMENT_ORB_RESPONSE_LENGTH(sizeof(orb->response)));
+
+ orb->request.status_fifo.high =
+ cpu_to_be32(lu->address_handler.offset >> 32);
+ orb->request.status_fifo.low =
+ cpu_to_be32(lu->address_handler.offset);
+
+ if (function == SBP2_LOGIN_REQUEST) {
+ /* Ask for 2^2 == 4 seconds reconnect grace period */
+ orb->request.misc |= cpu_to_be32(
+ MANAGEMENT_ORB_RECONNECT(2) |
+ MANAGEMENT_ORB_EXCLUSIVE(sbp2_param_exclusive_login));
+ timeout = lu->tgt->mgt_orb_timeout;
+ } else {
+ timeout = SBP2_ORB_TIMEOUT;
+ }
+
+ init_completion(&orb->done);
+ orb->base.callback = complete_management_orb;
+
+ orb->base.request_bus =
+ dma_map_single(device->card->device, &orb->request,
+ sizeof(orb->request), DMA_TO_DEVICE);
+ if (dma_mapping_error(device->card->device, orb->base.request_bus))
+ goto fail_mapping_request;
+
+ sbp2_send_orb(&orb->base, lu, node_id, generation,
+ lu->tgt->management_agent_address);
+
+ wait_for_completion_timeout(&orb->done, msecs_to_jiffies(timeout));
+
+ retval = -EIO;
+ if (sbp2_cancel_orbs(lu) == 0) {
+ dev_err(lu_dev(lu), "ORB reply timed out, rcode 0x%02x\n",
+ orb->base.rcode);
+ goto out;
+ }
+
+ if (orb->base.rcode != RCODE_COMPLETE) {
+ dev_err(lu_dev(lu), "management write failed, rcode 0x%02x\n",
+ orb->base.rcode);
+ goto out;
+ }
+
+ if (STATUS_GET_RESPONSE(orb->status) != 0 ||
+ STATUS_GET_SBP_STATUS(orb->status) != 0) {
+ dev_err(lu_dev(lu), "error status: %d:%d\n",
+ STATUS_GET_RESPONSE(orb->status),
+ STATUS_GET_SBP_STATUS(orb->status));
+ goto out;
+ }
+
+ retval = 0;
+ out:
+ dma_unmap_single(device->card->device, orb->base.request_bus,
+ sizeof(orb->request), DMA_TO_DEVICE);
+ fail_mapping_request:
+ dma_unmap_single(device->card->device, orb->response_bus,
+ sizeof(orb->response), DMA_FROM_DEVICE);
+ fail_mapping_response:
+ if (response)
+ memcpy(response, orb->response, sizeof(orb->response));
+ kref_put(&orb->base.kref, free_orb);
+
+ return retval;
+}
+
+static void sbp2_agent_reset(struct sbp2_logical_unit *lu)
+{
+ struct fw_device *device = target_parent_device(lu->tgt);
+ __be32 d = 0;
+
+ fw_run_transaction(device->card, TCODE_WRITE_QUADLET_REQUEST,
+ lu->tgt->node_id, lu->generation, device->max_speed,
+ lu->command_block_agent_address + SBP2_AGENT_RESET,
+ &d, 4);
+}
+
+static void complete_agent_reset_write_no_wait(struct fw_card *card,
+ int rcode, void *payload, size_t length, void *data)
+{
+ kfree(data);
+}
+
+static void sbp2_agent_reset_no_wait(struct sbp2_logical_unit *lu)
+{
+ struct fw_device *device = target_parent_device(lu->tgt);
+ struct fw_transaction *t;
+ static __be32 d;
+
+ t = kmalloc_obj(*t, GFP_ATOMIC);
+ if (t == NULL)
+ return;
+
+ fw_send_request(device->card, t, TCODE_WRITE_QUADLET_REQUEST,
+ lu->tgt->node_id, lu->generation, device->max_speed,
+ lu->command_block_agent_address + SBP2_AGENT_RESET,
+ &d, 4, complete_agent_reset_write_no_wait, t);
+}
+
+static inline void sbp2_allow_block(struct sbp2_target *tgt)
+{
+ spin_lock_irq(&tgt->lock);
+ --tgt->dont_block;
+ spin_unlock_irq(&tgt->lock);
+}
+
+/*
+ * Blocks lu->tgt if all of the following conditions are met:
+ * - Login, INQUIRY, and high-level SCSI setup of all of the target's
+ * logical units have been finished (indicated by dont_block == 0).
+ * - lu->generation is stale.
+ *
+ * Note, scsi_block_requests() must be called while holding tgt->lock,
+ * otherwise it might foil sbp2_[conditionally_]unblock()'s attempt to
+ * unblock the target.
+ */
+static void sbp2_conditionally_block(struct sbp2_logical_unit *lu)
+{
+ struct sbp2_target *tgt = lu->tgt;
+ struct fw_card *card = target_parent_device(tgt)->card;
+ struct Scsi_Host *shost =
+ container_of((void *)tgt, struct Scsi_Host, hostdata[0]);
+ unsigned long flags;
+
+ spin_lock_irqsave(&tgt->lock, flags);
+ if (!tgt->dont_block && !lu->blocked &&
+ lu->generation != card->generation) {
+ lu->blocked = true;
+ if (++tgt->blocked == 1)
+ scsi_block_requests(shost);
+ }
+ spin_unlock_irqrestore(&tgt->lock, flags);
+}
+
+/*
+ * Unblocks lu->tgt as soon as all its logical units can be unblocked.
+ * Note, it is harmless to run scsi_unblock_requests() outside the
+ * tgt->lock protected section. On the other hand, running it inside
+ * the section might clash with shost->host_lock.
+ */
+static void sbp2_conditionally_unblock(struct sbp2_logical_unit *lu)
+{
+ struct sbp2_target *tgt = lu->tgt;
+ struct fw_card *card = target_parent_device(tgt)->card;
+ struct Scsi_Host *shost =
+ container_of((void *)tgt, struct Scsi_Host, hostdata[0]);
+ bool unblock = false;
+
+ spin_lock_irq(&tgt->lock);
+ if (lu->blocked && lu->generation == card->generation) {
+ lu->blocked = false;
+ unblock = --tgt->blocked == 0;
+ }
+ spin_unlock_irq(&tgt->lock);
+
+ if (unblock)
+ scsi_unblock_requests(shost);
+}
+
+/*
+ * Prevents future blocking of tgt and unblocks it.
+ * Note, it is harmless to run scsi_unblock_requests() outside the
+ * tgt->lock protected section. On the other hand, running it inside
+ * the section might clash with shost->host_lock.
+ */
+static void sbp2_unblock(struct sbp2_target *tgt)
+{
+ struct Scsi_Host *shost =
+ container_of((void *)tgt, struct Scsi_Host, hostdata[0]);
+
+ spin_lock_irq(&tgt->lock);
+ ++tgt->dont_block;
+ spin_unlock_irq(&tgt->lock);
+
+ scsi_unblock_requests(shost);
+}
+
+static int sbp2_lun2int(u16 lun)
+{
+ struct scsi_lun eight_bytes_lun;
+
+ memset(&eight_bytes_lun, 0, sizeof(eight_bytes_lun));
+ eight_bytes_lun.scsi_lun[0] = (lun >> 8) & 0xff;
+ eight_bytes_lun.scsi_lun[1] = lun & 0xff;
+
+ return scsilun_to_int(&eight_bytes_lun);
+}
+
+/*
+ * Write retransmit retry values into the BUSY_TIMEOUT register.
+ * - The single-phase retry protocol is supported by all SBP-2 devices, but the
+ * default retry_limit value is 0 (i.e. never retry transmission). We write a
+ * saner value after logging into the device.
+ * - The dual-phase retry protocol is optional to implement, and if not
+ * supported, writes to the dual-phase portion of the register will be
+ * ignored. We try to write the original 1394-1995 default here.
+ * - In the case of devices that are also SBP-3-compliant, all writes are
+ * ignored, as the register is read-only, but contains single-phase retry of
+ * 15, which is what we're trying to set for all SBP-2 device anyway, so this
+ * write attempt is safe and yields more consistent behavior for all devices.
+ *
+ * See section 8.3.2.3.5 of the 1394-1995 spec, section 6.2 of the SBP-2 spec,
+ * and section 6.4 of the SBP-3 spec for further details.
+ */
+static void sbp2_set_busy_timeout(struct sbp2_logical_unit *lu)
+{
+ struct fw_device *device = target_parent_device(lu->tgt);
+ __be32 d = cpu_to_be32(SBP2_CYCLE_LIMIT | SBP2_RETRY_LIMIT);
+
+ fw_run_transaction(device->card, TCODE_WRITE_QUADLET_REQUEST,
+ lu->tgt->node_id, lu->generation, device->max_speed,
+ CSR_REGISTER_BASE + CSR_BUSY_TIMEOUT, &d, 4);
+}
+
+static void sbp2_reconnect(struct work_struct *work);
+
+static void sbp2_login(struct work_struct *work)
+{
+ struct sbp2_logical_unit *lu =
+ container_of(work, struct sbp2_logical_unit, work.work);
+ struct sbp2_target *tgt = lu->tgt;
+ struct fw_device *device = target_parent_device(tgt);
+ struct Scsi_Host *shost;
+ struct scsi_device *sdev;
+ struct sbp2_login_response response;
+ int generation, node_id, local_node_id;
+
+ if (fw_device_is_shutdown(device))
+ return;
+
+ generation = device->generation;
+ smp_rmb(); /* node IDs must not be older than generation */
+ node_id = device->node_id;
+ local_node_id = device->card->node_id;
+
+ /* If this is a re-login attempt, log out, or we might be rejected. */
+ if (lu->has_sdev)
+ sbp2_send_management_orb(lu, device->node_id, generation,
+ SBP2_LOGOUT_REQUEST, lu->login_id, NULL);
+
+ if (sbp2_send_management_orb(lu, node_id, generation,
+ SBP2_LOGIN_REQUEST, lu->lun, &response) < 0) {
+ if (lu->retries++ < 5) {
+ sbp2_queue_work(lu, DIV_ROUND_UP(HZ, 5));
+ } else {
+ dev_err(tgt_dev(tgt), "failed to login to LUN %04x\n",
+ lu->lun);
+ /* Let any waiting I/O fail from now on. */
+ sbp2_unblock(lu->tgt);
+ }
+ return;
+ }
+
+ tgt->node_id = node_id;
+ tgt->address_high = local_node_id << 16;
+ smp_wmb(); /* node IDs must not be older than generation */
+ lu->generation = generation;
+
+ lu->command_block_agent_address =
+ ((u64)(be32_to_cpu(response.command_block_agent.high) & 0xffff)
+ << 32) | be32_to_cpu(response.command_block_agent.low);
+ lu->login_id = be32_to_cpu(response.misc) & 0xffff;
+
+ dev_notice(tgt_dev(tgt), "logged in to LUN %04x (%d retries)\n",
+ lu->lun, lu->retries);
+
+ /* set appropriate retry limit(s) in BUSY_TIMEOUT register */
+ sbp2_set_busy_timeout(lu);
+
+ lu->workfn = sbp2_reconnect;
+ sbp2_agent_reset(lu);
+
+ /* This was a re-login. */
+ if (lu->has_sdev) {
+ sbp2_cancel_orbs(lu);
+ sbp2_conditionally_unblock(lu);
+
+ return;
+ }
+
+ if (lu->tgt->workarounds & SBP2_WORKAROUND_DELAY_INQUIRY)
+ ssleep(SBP2_INQUIRY_DELAY);
+
+ shost = container_of((void *)tgt, struct Scsi_Host, hostdata[0]);
+ sdev = __scsi_add_device(shost, 0, 0, sbp2_lun2int(lu->lun), lu);
+ /*
+ * FIXME: We are unable to perform reconnects while in sbp2_login().
+ * Therefore __scsi_add_device() will get into trouble if a bus reset
+ * happens in parallel. It will either fail or leave us with an
+ * unusable sdev. As a workaround we check for this and retry the
+ * whole login and SCSI probing.
+ */
+
+ /* Reported error during __scsi_add_device() */
+ if (IS_ERR(sdev))
+ goto out_logout_login;
+
+ /* Unreported error during __scsi_add_device() */
+ smp_rmb(); /* get current card generation */
+ if (generation != device->card->generation) {
+ scsi_remove_device(sdev);
+ scsi_device_put(sdev);
+ goto out_logout_login;
+ }
+
+ /* No error during __scsi_add_device() */
+ lu->has_sdev = true;
+ scsi_device_put(sdev);
+ sbp2_allow_block(tgt);
+
+ return;
+
+ out_logout_login:
+ smp_rmb(); /* generation may have changed */
+ generation = device->generation;
+ smp_rmb(); /* node_id must not be older than generation */
+
+ sbp2_send_management_orb(lu, device->node_id, generation,
+ SBP2_LOGOUT_REQUEST, lu->login_id, NULL);
+ /*
+ * If a bus reset happened, sbp2_update will have requeued
+ * lu->work already. Reset the work from reconnect to login.
+ */
+ lu->workfn = sbp2_login;
+}
+
+static void sbp2_reconnect(struct work_struct *work)
+{
+ struct sbp2_logical_unit *lu =
+ container_of(work, struct sbp2_logical_unit, work.work);
+ struct sbp2_target *tgt = lu->tgt;
+ struct fw_device *device = target_parent_device(tgt);
+ int generation, node_id, local_node_id;
+
+ if (fw_device_is_shutdown(device))
+ return;
+
+ generation = device->generation;
+ smp_rmb(); /* node IDs must not be older than generation */
+ node_id = device->node_id;
+ local_node_id = device->card->node_id;
+
+ if (sbp2_send_management_orb(lu, node_id, generation,
+ SBP2_RECONNECT_REQUEST,
+ lu->login_id, NULL) < 0) {
+ /*
+ * If reconnect was impossible even though we are in the
+ * current generation, fall back and try to log in again.
+ *
+ * We could check for "Function rejected" status, but
+ * looking at the bus generation as simpler and more general.
+ */
+ smp_rmb(); /* get current card generation */
+ if (generation == device->card->generation ||
+ lu->retries++ >= 5) {
+ dev_err(tgt_dev(tgt), "failed to reconnect\n");
+ lu->retries = 0;
+ lu->workfn = sbp2_login;
+ }
+ sbp2_queue_work(lu, DIV_ROUND_UP(HZ, 5));
+
+ return;
+ }
+
+ tgt->node_id = node_id;
+ tgt->address_high = local_node_id << 16;
+ smp_wmb(); /* node IDs must not be older than generation */
+ lu->generation = generation;
+
+ dev_notice(tgt_dev(tgt), "reconnected to LUN %04x (%d retries)\n",
+ lu->lun, lu->retries);
+
+ sbp2_agent_reset(lu);
+ sbp2_cancel_orbs(lu);
+ sbp2_conditionally_unblock(lu);
+}
+
+static void sbp2_lu_workfn(struct work_struct *work)
+{
+ struct sbp2_logical_unit *lu = container_of(to_delayed_work(work),
+ struct sbp2_logical_unit, work);
+ lu->workfn(work);
+}
+
+static int sbp2_add_logical_unit(struct sbp2_target *tgt, int lun_entry)
+{
+ struct sbp2_logical_unit *lu;
+
+ lu = kmalloc_obj(*lu);
+ if (!lu)
+ return -ENOMEM;
+
+ lu->address_handler.length = 0x100;
+ lu->address_handler.address_callback = sbp2_status_write;
+ lu->address_handler.callback_data = lu;
+
+ if (fw_core_add_address_handler(&lu->address_handler,
+ &fw_high_memory_region) < 0) {
+ kfree(lu);
+ return -ENOMEM;
+ }
+
+ lu->tgt = tgt;
+ lu->lun = lun_entry & 0xffff;
+ lu->login_id = INVALID_LOGIN_ID;
+ lu->retries = 0;
+ lu->has_sdev = false;
+ lu->blocked = false;
+ ++tgt->dont_block;
+ INIT_LIST_HEAD(&lu->orb_list);
+ lu->workfn = sbp2_login;
+ INIT_DELAYED_WORK(&lu->work, sbp2_lu_workfn);
+
+ list_add_tail(&lu->link, &tgt->lu_list);
+ return 0;
+}
+
+static void sbp2_get_unit_unique_id(struct sbp2_target *tgt,
+ const u32 *leaf)
+{
+ if ((leaf[0] & 0xffff0000) == 0x00020000)
+ tgt->guid = (u64)leaf[1] << 32 | leaf[2];
+}
+
+static int sbp2_scan_logical_unit_dir(struct sbp2_target *tgt,
+ const u32 *directory)
+{
+ struct fw_csr_iterator ci;
+ int key, value;
+
+ fw_csr_iterator_init(&ci, directory);
+ while (fw_csr_iterator_next(&ci, &key, &value))
+ if (key == SBP2_CSR_LOGICAL_UNIT_NUMBER &&
+ sbp2_add_logical_unit(tgt, value) < 0)
+ return -ENOMEM;
+ return 0;
+}
+
+static int sbp2_scan_unit_dir(struct sbp2_target *tgt, const u32 *directory,
+ u32 *model, u32 *firmware_revision)
+{
+ struct fw_csr_iterator ci;
+ int key, value;
+
+ fw_csr_iterator_init(&ci, directory);
+ while (fw_csr_iterator_next(&ci, &key, &value)) {
+ switch (key) {
+
+ case CSR_DEPENDENT_INFO | CSR_OFFSET:
+ tgt->management_agent_address =
+ CSR_REGISTER_BASE + 4 * value;
+ break;
+
+ case CSR_DIRECTORY_ID:
+ tgt->directory_id = value;
+ break;
+
+ case CSR_MODEL:
+ *model = value;
+ break;
+
+ case SBP2_CSR_FIRMWARE_REVISION:
+ *firmware_revision = value;
+ break;
+
+ case SBP2_CSR_UNIT_CHARACTERISTICS:
+ /* the timeout value is stored in 500ms units */
+ tgt->mgt_orb_timeout = (value >> 8 & 0xff) * 500;
+ break;
+
+ case SBP2_CSR_LOGICAL_UNIT_NUMBER:
+ if (sbp2_add_logical_unit(tgt, value) < 0)
+ return -ENOMEM;
+ break;
+
+ case SBP2_CSR_UNIT_UNIQUE_ID:
+ sbp2_get_unit_unique_id(tgt, ci.p - 1 + value);
+ break;
+
+ case SBP2_CSR_LOGICAL_UNIT_DIRECTORY:
+ /* Adjust for the increment in the iterator */
+ if (sbp2_scan_logical_unit_dir(tgt, ci.p - 1 + value) < 0)
+ return -ENOMEM;
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Per section 7.4.8 of the SBP-2 spec, a mgt_ORB_timeout value can be
+ * provided in the config rom. Most devices do provide a value, which
+ * we'll use for login management orbs, but with some sane limits.
+ */
+static void sbp2_clamp_management_orb_timeout(struct sbp2_target *tgt)
+{
+ unsigned int timeout = tgt->mgt_orb_timeout;
+
+ if (timeout > 40000)
+ dev_notice(tgt_dev(tgt), "%ds mgt_ORB_timeout limited to 40s\n",
+ timeout / 1000);
+
+ tgt->mgt_orb_timeout = clamp_val(timeout, 5000, 40000);
+}
+
+static void sbp2_init_workarounds(struct sbp2_target *tgt, u32 model,
+ u32 firmware_revision)
+{
+ int i;
+ unsigned int w = sbp2_param_workarounds;
+
+ if (w)
+ dev_notice(tgt_dev(tgt),
+ "Please notify linux1394-devel@lists.sf.net "
+ "if you need the workarounds parameter\n");
+
+ if (w & SBP2_WORKAROUND_OVERRIDE)
+ goto out;
+
+ for (i = 0; i < ARRAY_SIZE(sbp2_workarounds_table); i++) {
+
+ if (sbp2_workarounds_table[i].firmware_revision !=
+ (firmware_revision & 0xffffff00))
+ continue;
+
+ if (sbp2_workarounds_table[i].model != model &&
+ sbp2_workarounds_table[i].model != SBP2_ROM_VALUE_WILDCARD)
+ continue;
+
+ w |= sbp2_workarounds_table[i].workarounds;
+ break;
+ }
+ out:
+ if (w)
+ dev_notice(tgt_dev(tgt), "workarounds 0x%x "
+ "(firmware_revision 0x%06x, model_id 0x%06x)\n",
+ w, firmware_revision, model);
+ tgt->workarounds = w;
+}
+
+static const struct scsi_host_template scsi_driver_template;
+static void sbp2_remove(struct fw_unit *unit);
+
+static int sbp2_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ struct sbp2_target *tgt;
+ struct sbp2_logical_unit *lu;
+ struct Scsi_Host *shost;
+ u32 model, firmware_revision;
+
+ /* cannot (or should not) handle targets on the local node */
+ if (device->is_local)
+ return -ENODEV;
+
+ shost = scsi_host_alloc(&scsi_driver_template, sizeof(*tgt));
+ if (shost == NULL)
+ return -ENOMEM;
+
+ tgt = (struct sbp2_target *)shost->hostdata;
+ dev_set_drvdata(&unit->device, tgt);
+ tgt->unit = unit;
+ INIT_LIST_HEAD(&tgt->lu_list);
+ spin_lock_init(&tgt->lock);
+ tgt->guid = (u64)device->config_rom[3] << 32 | device->config_rom[4];
+
+ if (fw_device_enable_phys_dma(device) < 0)
+ goto fail_shost_put;
+
+ shost->max_cmd_len = SBP2_MAX_CDB_SIZE;
+
+ if (scsi_add_host_with_dma(shost, &unit->device,
+ device->card->device) < 0)
+ goto fail_shost_put;
+
+ /* implicit directory ID */
+ tgt->directory_id = ((unit->directory - device->config_rom) * 4
+ + CSR_CONFIG_ROM) & 0xffffff;
+
+ firmware_revision = SBP2_ROM_VALUE_MISSING;
+ model = SBP2_ROM_VALUE_MISSING;
+
+ if (sbp2_scan_unit_dir(tgt, unit->directory, &model,
+ &firmware_revision) < 0)
+ goto fail_remove;
+
+ sbp2_clamp_management_orb_timeout(tgt);
+ sbp2_init_workarounds(tgt, model, firmware_revision);
+
+ /*
+ * At S100 we can do 512 bytes per packet, at S200 1024 bytes,
+ * and so on up to 4096 bytes. The SBP-2 max_payload field
+ * specifies the max payload size as 2 ^ (max_payload + 2), so
+ * if we set this to max_speed + 7, we get the right value.
+ */
+ tgt->max_payload = min3(device->max_speed + 7, 10U,
+ device->card->max_receive - 1);
+
+ /* Do the login in a workqueue so we can easily reschedule retries. */
+ list_for_each_entry(lu, &tgt->lu_list, link)
+ sbp2_queue_work(lu, DIV_ROUND_UP(HZ, 5));
+
+ return 0;
+
+ fail_remove:
+ sbp2_remove(unit);
+ return -ENOMEM;
+
+ fail_shost_put:
+ scsi_host_put(shost);
+ return -ENOMEM;
+}
+
+static void sbp2_update(struct fw_unit *unit)
+{
+ struct sbp2_target *tgt = dev_get_drvdata(&unit->device);
+ struct sbp2_logical_unit *lu;
+
+ fw_device_enable_phys_dma(fw_parent_device(unit));
+
+ /*
+ * Fw-core serializes sbp2_update() against sbp2_remove().
+ * Iteration over tgt->lu_list is therefore safe here.
+ */
+ list_for_each_entry(lu, &tgt->lu_list, link) {
+ sbp2_conditionally_block(lu);
+ lu->retries = 0;
+ sbp2_queue_work(lu, 0);
+ }
+}
+
+static void sbp2_remove(struct fw_unit *unit)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ struct sbp2_target *tgt = dev_get_drvdata(&unit->device);
+ struct sbp2_logical_unit *lu, *next;
+ struct Scsi_Host *shost =
+ container_of((void *)tgt, struct Scsi_Host, hostdata[0]);
+ struct scsi_device *sdev;
+
+ /* prevent deadlocks */
+ sbp2_unblock(tgt);
+
+ list_for_each_entry_safe(lu, next, &tgt->lu_list, link) {
+ cancel_delayed_work_sync(&lu->work);
+ sdev = scsi_device_lookup(shost, 0, 0, sbp2_lun2int(lu->lun));
+ if (sdev) {
+ scsi_remove_device(sdev);
+ scsi_device_put(sdev);
+ }
+ if (lu->login_id != INVALID_LOGIN_ID) {
+ int generation, node_id;
+ /*
+ * tgt->node_id may be obsolete here if we failed
+ * during initial login or after a bus reset where
+ * the topology changed.
+ */
+ generation = device->generation;
+ smp_rmb(); /* node_id vs. generation */
+ node_id = device->node_id;
+ sbp2_send_management_orb(lu, node_id, generation,
+ SBP2_LOGOUT_REQUEST,
+ lu->login_id, NULL);
+ }
+ fw_core_remove_address_handler(&lu->address_handler);
+ list_del(&lu->link);
+ kfree(lu);
+ }
+ scsi_remove_host(shost);
+ dev_notice(&unit->device, "released target %d:0:0\n", shost->host_no);
+
+ scsi_host_put(shost);
+}
+
+#define SBP2_UNIT_SPEC_ID_ENTRY 0x0000609e
+#define SBP2_SW_VERSION_ENTRY 0x00010483
+
+static const struct ieee1394_device_id sbp2_id_table[] = {
+ {
+ .match_flags = IEEE1394_MATCH_SPECIFIER_ID |
+ IEEE1394_MATCH_VERSION,
+ .specifier_id = SBP2_UNIT_SPEC_ID_ENTRY,
+ .version = SBP2_SW_VERSION_ENTRY,
+ },
+ { }
+};
+
+static struct fw_driver sbp2_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .bus = &fw_bus_type,
+ },
+ .probe = sbp2_probe,
+ .update = sbp2_update,
+ .remove = sbp2_remove,
+ .id_table = sbp2_id_table,
+};
+
+static void sbp2_unmap_scatterlist(struct device *card_device,
+ struct sbp2_command_orb *orb)
+{
+ scsi_dma_unmap(orb->cmd);
+
+ if (orb->request.misc & cpu_to_be32(COMMAND_ORB_PAGE_TABLE_PRESENT))
+ dma_unmap_single(card_device, orb->page_table_bus,
+ sizeof(orb->page_table), DMA_TO_DEVICE);
+}
+
+static unsigned int sbp2_status_to_sense_data(u8 *sbp2_status, u8 *sense_data)
+{
+ int sam_status;
+ int sfmt = (sbp2_status[0] >> 6) & 0x03;
+
+ if (sfmt == 2 || sfmt == 3) {
+ /*
+ * Reserved for future standardization (2) or
+ * Status block format vendor-dependent (3)
+ */
+ return DID_ERROR << 16;
+ }
+
+ sense_data[0] = 0x70 | sfmt | (sbp2_status[1] & 0x80);
+ sense_data[1] = 0x0;
+ sense_data[2] = ((sbp2_status[1] << 1) & 0xe0) | (sbp2_status[1] & 0x0f);
+ sense_data[3] = sbp2_status[4];
+ sense_data[4] = sbp2_status[5];
+ sense_data[5] = sbp2_status[6];
+ sense_data[6] = sbp2_status[7];
+ sense_data[7] = 10;
+ sense_data[8] = sbp2_status[8];
+ sense_data[9] = sbp2_status[9];
+ sense_data[10] = sbp2_status[10];
+ sense_data[11] = sbp2_status[11];
+ sense_data[12] = sbp2_status[2];
+ sense_data[13] = sbp2_status[3];
+ sense_data[14] = sbp2_status[12];
+ sense_data[15] = sbp2_status[13];
+
+ sam_status = sbp2_status[0] & 0x3f;
+
+ switch (sam_status) {
+ case SAM_STAT_GOOD:
+ case SAM_STAT_CHECK_CONDITION:
+ case SAM_STAT_CONDITION_MET:
+ case SAM_STAT_BUSY:
+ case SAM_STAT_RESERVATION_CONFLICT:
+ case SAM_STAT_COMMAND_TERMINATED:
+ return DID_OK << 16 | sam_status;
+
+ default:
+ return DID_ERROR << 16;
+ }
+}
+
+static void complete_command_orb(struct sbp2_orb *base_orb,
+ struct sbp2_status *status)
+{
+ struct sbp2_command_orb *orb =
+ container_of(base_orb, struct sbp2_command_orb, base);
+ struct fw_device *device = target_parent_device(base_orb->lu->tgt);
+ int result;
+
+ if (status != NULL) {
+ if (STATUS_GET_DEAD(*status))
+ sbp2_agent_reset_no_wait(base_orb->lu);
+
+ switch (STATUS_GET_RESPONSE(*status)) {
+ case SBP2_STATUS_REQUEST_COMPLETE:
+ result = DID_OK << 16;
+ break;
+ case SBP2_STATUS_TRANSPORT_FAILURE:
+ result = DID_BUS_BUSY << 16;
+ break;
+ case SBP2_STATUS_ILLEGAL_REQUEST:
+ case SBP2_STATUS_VENDOR_DEPENDENT:
+ default:
+ result = DID_ERROR << 16;
+ break;
+ }
+
+ if (result == DID_OK << 16 && STATUS_GET_LEN(*status) > 1)
+ result = sbp2_status_to_sense_data(STATUS_GET_DATA(*status),
+ orb->cmd->sense_buffer);
+ } else {
+ /*
+ * If the orb completes with status == NULL, something
+ * went wrong, typically a bus reset happened mid-orb
+ * or when sending the write (less likely).
+ */
+ result = DID_BUS_BUSY << 16;
+ sbp2_conditionally_block(base_orb->lu);
+ }
+
+ dma_unmap_single(device->card->device, orb->base.request_bus,
+ sizeof(orb->request), DMA_TO_DEVICE);
+ sbp2_unmap_scatterlist(device->card->device, orb);
+
+ orb->cmd->result = result;
+ scsi_done(orb->cmd);
+}
+
+static int sbp2_map_scatterlist(struct sbp2_command_orb *orb,
+ struct fw_device *device, struct sbp2_logical_unit *lu)
+{
+ struct scatterlist *sg = scsi_sglist(orb->cmd);
+ int i, n;
+
+ n = scsi_dma_map(orb->cmd);
+ if (n <= 0)
+ goto fail;
+
+ /*
+ * Handle the special case where there is only one element in
+ * the scatter list by converting it to an immediate block
+ * request. This is also a workaround for broken devices such
+ * as the second generation iPod which doesn't support page
+ * tables.
+ */
+ if (n == 1) {
+ orb->request.data_descriptor.high =
+ cpu_to_be32(lu->tgt->address_high);
+ orb->request.data_descriptor.low =
+ cpu_to_be32(sg_dma_address(sg));
+ orb->request.misc |=
+ cpu_to_be32(COMMAND_ORB_DATA_SIZE(sg_dma_len(sg)));
+ return 0;
+ }
+
+ for_each_sg(sg, sg, n, i) {
+ orb->page_table[i].high = cpu_to_be32(sg_dma_len(sg) << 16);
+ orb->page_table[i].low = cpu_to_be32(sg_dma_address(sg));
+ }
+
+ orb->page_table_bus =
+ dma_map_single(device->card->device, orb->page_table,
+ sizeof(orb->page_table), DMA_TO_DEVICE);
+ if (dma_mapping_error(device->card->device, orb->page_table_bus))
+ goto fail_page_table;
+
+ /*
+ * The data_descriptor pointer is the one case where we need
+ * to fill in the node ID part of the address. All other
+ * pointers assume that the data referenced reside on the
+ * initiator (i.e. us), but data_descriptor can refer to data
+ * on other nodes so we need to put our ID in descriptor.high.
+ */
+ orb->request.data_descriptor.high = cpu_to_be32(lu->tgt->address_high);
+ orb->request.data_descriptor.low = cpu_to_be32(orb->page_table_bus);
+ orb->request.misc |= cpu_to_be32(COMMAND_ORB_PAGE_TABLE_PRESENT |
+ COMMAND_ORB_DATA_SIZE(n));
+
+ return 0;
+
+ fail_page_table:
+ scsi_dma_unmap(orb->cmd);
+ fail:
+ return -ENOMEM;
+}
+
+/* SCSI stack integration */
+
+static enum scsi_qc_status sbp2_scsi_queuecommand(struct Scsi_Host *shost,
+ struct scsi_cmnd *cmd)
+{
+ struct sbp2_logical_unit *lu = cmd->device->hostdata;
+ struct fw_device *device = target_parent_device(lu->tgt);
+ enum scsi_qc_status retval = SCSI_MLQUEUE_HOST_BUSY;
+ struct sbp2_command_orb *orb;
+ int generation;
+
+ orb = kzalloc_obj(*orb, GFP_ATOMIC);
+ if (orb == NULL)
+ return SCSI_MLQUEUE_HOST_BUSY;
+
+ /* Initialize rcode to something not RCODE_COMPLETE. */
+ orb->base.rcode = -1;
+ kref_init(&orb->base.kref);
+ orb->cmd = cmd;
+ orb->request.next.high = cpu_to_be32(SBP2_ORB_NULL);
+ orb->request.misc = cpu_to_be32(
+ COMMAND_ORB_MAX_PAYLOAD(lu->tgt->max_payload) |
+ COMMAND_ORB_SPEED(device->max_speed) |
+ COMMAND_ORB_NOTIFY);
+
+ if (cmd->sc_data_direction == DMA_FROM_DEVICE)
+ orb->request.misc |= cpu_to_be32(COMMAND_ORB_DIRECTION);
+
+ generation = device->generation;
+ smp_rmb(); /* sbp2_map_scatterlist looks at tgt->address_high */
+
+ if (scsi_sg_count(cmd) && sbp2_map_scatterlist(orb, device, lu) < 0)
+ goto out;
+
+ memcpy(orb->request.command_block, cmd->cmnd, cmd->cmd_len);
+
+ orb->base.callback = complete_command_orb;
+ orb->base.request_bus =
+ dma_map_single(device->card->device, &orb->request,
+ sizeof(orb->request), DMA_TO_DEVICE);
+ if (dma_mapping_error(device->card->device, orb->base.request_bus)) {
+ sbp2_unmap_scatterlist(device->card->device, orb);
+ goto out;
+ }
+
+ sbp2_send_orb(&orb->base, lu, lu->tgt->node_id, generation,
+ lu->command_block_agent_address + SBP2_ORB_POINTER);
+ retval = 0;
+ out:
+ kref_put(&orb->base.kref, free_orb);
+ return retval;
+}
+
+static int sbp2_scsi_sdev_init(struct scsi_device *sdev)
+{
+ struct sbp2_logical_unit *lu = sdev->hostdata;
+
+ /* (Re-)Adding logical units via the SCSI stack is not supported. */
+ if (!lu)
+ return -ENOSYS;
+
+ sdev->allow_restart = 1;
+
+ if (lu->tgt->workarounds & SBP2_WORKAROUND_INQUIRY_36)
+ sdev->inquiry_len = 36;
+
+ return 0;
+}
+
+static int sbp2_scsi_sdev_configure(struct scsi_device *sdev,
+ struct queue_limits *lim)
+{
+ struct sbp2_logical_unit *lu = sdev->hostdata;
+
+ sdev->use_10_for_rw = 1;
+
+ if (sbp2_param_exclusive_login) {
+ sdev->manage_system_start_stop = 1;
+ sdev->manage_runtime_start_stop = 1;
+ sdev->manage_shutdown = 1;
+ }
+
+ if (sdev->type == TYPE_ROM)
+ sdev->use_10_for_ms = 1;
+
+ if (sdev->type == TYPE_DISK &&
+ lu->tgt->workarounds & SBP2_WORKAROUND_MODE_SENSE_8)
+ sdev->skip_ms_page_8 = 1;
+
+ if (lu->tgt->workarounds & SBP2_WORKAROUND_FIX_CAPACITY)
+ sdev->fix_capacity = 1;
+
+ if (lu->tgt->workarounds & SBP2_WORKAROUND_POWER_CONDITION)
+ sdev->start_stop_pwr_cond = 1;
+
+ if (lu->tgt->workarounds & SBP2_WORKAROUND_128K_MAX_TRANS)
+ lim->max_hw_sectors = 128 * 1024 / 512;
+
+ return 0;
+}
+
+/*
+ * Called by scsi stack when something has really gone wrong. Usually
+ * called when a command has timed-out for some reason.
+ */
+static int sbp2_scsi_abort(struct scsi_cmnd *cmd)
+{
+ struct sbp2_logical_unit *lu = cmd->device->hostdata;
+
+ dev_notice(lu_dev(lu), "sbp2_scsi_abort\n");
+ sbp2_agent_reset(lu);
+ sbp2_cancel_orbs(lu);
+
+ return SUCCESS;
+}
+
+/*
+ * Format of /sys/bus/scsi/devices/.../ieee1394_id:
+ * u64 EUI-64 : u24 directory_ID : u16 LUN (all printed in hexadecimal)
+ *
+ * This is the concatenation of target port identifier and logical unit
+ * identifier as per SAM-2...SAM-4 annex A.
+ */
+static ssize_t sbp2_sysfs_ieee1394_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct sbp2_logical_unit *lu;
+
+ if (!sdev)
+ return 0;
+
+ lu = sdev->hostdata;
+
+ return sprintf(buf, "%016llx:%06x:%04x\n",
+ (unsigned long long)lu->tgt->guid,
+ lu->tgt->directory_id, lu->lun);
+}
+
+static DEVICE_ATTR(ieee1394_id, S_IRUGO, sbp2_sysfs_ieee1394_id_show, NULL);
+
+static struct attribute *sbp2_scsi_sysfs_attrs[] = {
+ &dev_attr_ieee1394_id.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(sbp2_scsi_sysfs);
+
+static const struct scsi_host_template scsi_driver_template = {
+ .module = THIS_MODULE,
+ .name = "SBP-2 IEEE-1394",
+ .proc_name = "sbp2",
+ .queuecommand = sbp2_scsi_queuecommand,
+ .sdev_init = sbp2_scsi_sdev_init,
+ .sdev_configure = sbp2_scsi_sdev_configure,
+ .eh_abort_handler = sbp2_scsi_abort,
+ .this_id = -1,
+ .sg_tablesize = SG_ALL,
+ .max_segment_size = SBP2_MAX_SEG_SIZE,
+ .can_queue = 1,
+ .sdev_groups = sbp2_scsi_sysfs_groups,
+};
+
+MODULE_AUTHOR("Kristian Hoegsberg <krh@bitplanet.net>");
+MODULE_DESCRIPTION("SCSI over IEEE1394");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ieee1394, sbp2_id_table);
+
+/* Provide a module alias so root-on-sbp2 initrds don't break. */
+MODULE_ALIAS("sbp2");
+
+static int __init sbp2_init(void)
+{
+ return driver_register(&sbp2_driver.driver);
+}
+
+static void __exit sbp2_cleanup(void)
+{
+ driver_unregister(&sbp2_driver.driver);
+}
+
+module_init(sbp2_init);
+module_exit(sbp2_cleanup);
diff --git a/drivers/firewire/self-id-sequence-helper-test.c b/drivers/firewire/self-id-sequence-helper-test.c
new file mode 100644
index 000000000..eed7a2294
--- /dev/null
+++ b/drivers/firewire/self-id-sequence-helper-test.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// self-id-sequence-helper-test.c - An application of Kunit to test helpers of self ID sequence.
+//
+// Copyright (c) 2024 Takashi Sakamoto
+
+#include <kunit/test.h>
+
+#include "phy-packet-definitions.h"
+
+static void test_self_id_sequence_enumerator_valid(struct kunit *test)
+{
+ static const u32 valid_sequences[] = {
+ 0x00000000,
+ 0x00000001, 0x00800000,
+ 0x00000001, 0x00800001, 0x00900000,
+ 0x00000000,
+ };
+ struct self_id_sequence_enumerator enumerator;
+ const u32 *entry;
+ unsigned int quadlet_count;
+
+ enumerator.cursor = valid_sequences;
+ enumerator.quadlet_count = ARRAY_SIZE(valid_sequences);
+
+ entry = self_id_sequence_enumerator_next(&enumerator, &quadlet_count);
+ KUNIT_EXPECT_PTR_EQ(test, entry, &valid_sequences[0]);
+ KUNIT_EXPECT_EQ(test, quadlet_count, 1);
+ KUNIT_EXPECT_EQ(test, enumerator.quadlet_count, 6);
+
+ entry = self_id_sequence_enumerator_next(&enumerator, &quadlet_count);
+ KUNIT_EXPECT_PTR_EQ(test, entry, &valid_sequences[1]);
+ KUNIT_EXPECT_EQ(test, quadlet_count, 2);
+ KUNIT_EXPECT_EQ(test, enumerator.quadlet_count, 4);
+
+ entry = self_id_sequence_enumerator_next(&enumerator, &quadlet_count);
+ KUNIT_EXPECT_PTR_EQ(test, entry, &valid_sequences[3]);
+ KUNIT_EXPECT_EQ(test, quadlet_count, 3);
+ KUNIT_EXPECT_EQ(test, enumerator.quadlet_count, 1);
+
+ entry = self_id_sequence_enumerator_next(&enumerator, &quadlet_count);
+ KUNIT_EXPECT_PTR_EQ(test, entry, &valid_sequences[6]);
+ KUNIT_EXPECT_EQ(test, quadlet_count, 1);
+ KUNIT_EXPECT_EQ(test, enumerator.quadlet_count, 0);
+
+ entry = self_id_sequence_enumerator_next(&enumerator, &quadlet_count);
+ KUNIT_EXPECT_EQ(test, PTR_ERR(entry), -ENODATA);
+}
+
+static void test_self_id_sequence_enumerator_invalid(struct kunit *test)
+{
+ static const u32 invalid_sequences[] = {
+ 0x00000001,
+ };
+ struct self_id_sequence_enumerator enumerator;
+ const u32 *entry;
+ unsigned int count;
+
+ enumerator.cursor = invalid_sequences;
+ enumerator.quadlet_count = ARRAY_SIZE(invalid_sequences);
+
+ entry = self_id_sequence_enumerator_next(&enumerator, &count);
+ KUNIT_EXPECT_EQ(test, PTR_ERR(entry), -EPROTO);
+}
+
+static void test_self_id_sequence_get_port_status(struct kunit *test)
+{
+ static const u32 expected[] = {
+ 0x000000e5,
+ 0x00839e79,
+ 0x0091e79d,
+ 0x00a279e4,
+ };
+ u32 quadlets [] = {
+ 0x00000001,
+ 0x00800001,
+ 0x00900001,
+ 0x00a00000,
+ };
+ enum phy_packet_self_id_port_status port_status[28];
+ unsigned int port_capacity;
+ unsigned int port_index;
+
+ KUNIT_ASSERT_EQ(test, ARRAY_SIZE(expected), ARRAY_SIZE(quadlets));
+
+ // With an extra port.
+ port_capacity = self_id_sequence_get_port_capacity(ARRAY_SIZE(expected)) + 1;
+ KUNIT_ASSERT_EQ(test, port_capacity, ARRAY_SIZE(port_status));
+
+ for (port_index = 0; port_index < port_capacity; ++port_index) {
+ port_status[port_index] =
+ self_id_sequence_get_port_status(expected, ARRAY_SIZE(expected), port_index);
+ self_id_sequence_set_port_status(quadlets, ARRAY_SIZE(quadlets), port_index,
+ port_status[port_index]);
+ }
+
+ // Self ID zero.
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[0]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[1]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[2]);
+
+ // Self ID one.
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[3]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[4]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[5]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[6]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[7]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[8]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[9]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[10]);
+
+ // Self ID two.
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[11]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[12]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[13]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[14]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[15]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[16]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[17]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[18]);
+
+ // Self ID three.
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[19]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[20]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[21]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[22]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[23]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_CHILD, port_status[24]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_PARENT, port_status[25]);
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NCONN, port_status[26]);
+
+ // Our of order.
+ KUNIT_EXPECT_EQ(test, PHY_PACKET_SELF_ID_PORT_STATUS_NONE, port_status[27]);
+
+ KUNIT_EXPECT_MEMEQ(test, quadlets, expected, sizeof(expected));
+}
+
+static struct kunit_case self_id_sequence_helper_test_cases[] = {
+ KUNIT_CASE(test_self_id_sequence_enumerator_valid),
+ KUNIT_CASE(test_self_id_sequence_enumerator_invalid),
+ KUNIT_CASE(test_self_id_sequence_get_port_status),
+ {}
+};
+
+static struct kunit_suite self_id_sequence_helper_test_suite = {
+ .name = "self-id-sequence-helper",
+ .test_cases = self_id_sequence_helper_test_cases,
+};
+kunit_test_suite(self_id_sequence_helper_test_suite);
+
+MODULE_DESCRIPTION("Unit test suite for helpers of self ID sequence");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firewire/uapi-test.c b/drivers/firewire/uapi-test.c
new file mode 100644
index 000000000..bc3f10a2e
--- /dev/null
+++ b/drivers/firewire/uapi-test.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// uapi_test.c - An application of Kunit to check layout of structures exposed to user space for
+// FireWire subsystem.
+//
+// Copyright (c) 2023 Takashi Sakamoto
+
+#include <kunit/test.h>
+#include <linux/firewire-cdev.h>
+
+// Known issue added at v2.6.27 kernel.
+static void structure_layout_event_response(struct kunit *test)
+{
+#if defined(CONFIG_X86_32)
+ // 4 bytes alignment for aggregate type including 8 bytes storage types.
+ KUNIT_EXPECT_EQ(test, 20, sizeof(struct fw_cdev_event_response));
+#else
+ // 8 bytes alignment for aggregate type including 8 bytes storage types.
+ KUNIT_EXPECT_EQ(test, 24, sizeof(struct fw_cdev_event_response));
+#endif
+
+ KUNIT_EXPECT_EQ(test, 0, offsetof(struct fw_cdev_event_response, closure));
+ KUNIT_EXPECT_EQ(test, 8, offsetof(struct fw_cdev_event_response, type));
+ KUNIT_EXPECT_EQ(test, 12, offsetof(struct fw_cdev_event_response, rcode));
+ KUNIT_EXPECT_EQ(test, 16, offsetof(struct fw_cdev_event_response, length));
+ KUNIT_EXPECT_EQ(test, 20, offsetof(struct fw_cdev_event_response, data));
+}
+
+// Added at v6.5.
+static void structure_layout_event_request3(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, 56, sizeof(struct fw_cdev_event_request3));
+
+ KUNIT_EXPECT_EQ(test, 0, offsetof(struct fw_cdev_event_request3, closure));
+ KUNIT_EXPECT_EQ(test, 8, offsetof(struct fw_cdev_event_request3, type));
+ KUNIT_EXPECT_EQ(test, 12, offsetof(struct fw_cdev_event_request3, tcode));
+ KUNIT_EXPECT_EQ(test, 16, offsetof(struct fw_cdev_event_request3, offset));
+ KUNIT_EXPECT_EQ(test, 24, offsetof(struct fw_cdev_event_request3, source_node_id));
+ KUNIT_EXPECT_EQ(test, 28, offsetof(struct fw_cdev_event_request3, destination_node_id));
+ KUNIT_EXPECT_EQ(test, 32, offsetof(struct fw_cdev_event_request3, card));
+ KUNIT_EXPECT_EQ(test, 36, offsetof(struct fw_cdev_event_request3, generation));
+ KUNIT_EXPECT_EQ(test, 40, offsetof(struct fw_cdev_event_request3, handle));
+ KUNIT_EXPECT_EQ(test, 44, offsetof(struct fw_cdev_event_request3, length));
+ KUNIT_EXPECT_EQ(test, 48, offsetof(struct fw_cdev_event_request3, tstamp));
+ KUNIT_EXPECT_EQ(test, 56, offsetof(struct fw_cdev_event_request3, data));
+}
+
+// Added at v6.5.
+static void structure_layout_event_response2(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, 32, sizeof(struct fw_cdev_event_response2));
+
+ KUNIT_EXPECT_EQ(test, 0, offsetof(struct fw_cdev_event_response2, closure));
+ KUNIT_EXPECT_EQ(test, 8, offsetof(struct fw_cdev_event_response2, type));
+ KUNIT_EXPECT_EQ(test, 12, offsetof(struct fw_cdev_event_response2, rcode));
+ KUNIT_EXPECT_EQ(test, 16, offsetof(struct fw_cdev_event_response2, length));
+ KUNIT_EXPECT_EQ(test, 20, offsetof(struct fw_cdev_event_response2, request_tstamp));
+ KUNIT_EXPECT_EQ(test, 24, offsetof(struct fw_cdev_event_response2, response_tstamp));
+ KUNIT_EXPECT_EQ(test, 32, offsetof(struct fw_cdev_event_response2, data));
+}
+
+// Added at v6.5.
+static void structure_layout_event_phy_packet2(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, 24, sizeof(struct fw_cdev_event_phy_packet2));
+
+ KUNIT_EXPECT_EQ(test, 0, offsetof(struct fw_cdev_event_phy_packet2, closure));
+ KUNIT_EXPECT_EQ(test, 8, offsetof(struct fw_cdev_event_phy_packet2, type));
+ KUNIT_EXPECT_EQ(test, 12, offsetof(struct fw_cdev_event_phy_packet2, rcode));
+ KUNIT_EXPECT_EQ(test, 16, offsetof(struct fw_cdev_event_phy_packet2, length));
+ KUNIT_EXPECT_EQ(test, 20, offsetof(struct fw_cdev_event_phy_packet2, tstamp));
+ KUNIT_EXPECT_EQ(test, 24, offsetof(struct fw_cdev_event_phy_packet2, data));
+}
+
+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),
+ {}
+};
+
+static struct kunit_suite structure_layout_test_suite = {
+ .name = "firewire-uapi-structure-layout",
+ .test_cases = structure_layout_test_cases,
+};
+kunit_test_suite(structure_layout_test_suite);
+
+MODULE_DESCRIPTION("FireWire UAPI unit test suite");
+MODULE_LICENSE("GPL");