[PATCH 1/5] firewire: ohci: use workqueue to handle error case of AT request/response packet queueing

From: Takashi Sakamoto

Date: Sat Sep 19 2026 - 07:50:42 EST


When a packet cannot be queued to an AT context, the packet callback is
invoked to report the error to the caller. Since the callback runs in the
caller's context, its execution context depends on where the packet was
submitted. This makes the callback context inconsistent between
successful and failed subactions.

Use a workqueue to handle the error cases.

Signed-off-by: Takashi Sakamoto <o-takashi@xxxxxxxxxxxxx>
---
drivers/firewire/core-card.c | 7 +--
drivers/firewire/ohci.c | 90 +++++++++++++++++++++++++++++++++---
include/linux/firewire.h | 3 ++
3 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c
index 23749434d900..be6f18d67ece 100644
--- a/drivers/firewire/core-card.c
+++ b/drivers/firewire/core-card.c
@@ -643,11 +643,12 @@ int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid,
// * == WQ_FREEZABLE The target device would not be available when being freezed.
// * == WQ_HIGHPRI High priority to process semi-realtime timestamped data.
// * == WQ_SYSFS Parameters are available via sysfs.
- // * max_active == 4 A hardIRQ could notify events for a pair of requests and
- // response AR/AT contexts.
+ // * max_active == 4 + 2 A hardIRQ could notify events for a pair of requests and
+ // response AR/AT contexts. Additional 2 capacity are for the
+ // internal handling of local AT request and response packets.
async_wq = alloc_workqueue("firewire-async-card%u",
WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS,
- 4, card->index);
+ 6, card->index);
if (!async_wq)
return -ENOMEM;

diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c
index bd3e01b2f450..45f03095a196 100644
--- a/drivers/firewire/ohci.c
+++ b/drivers/firewire/ohci.c
@@ -164,6 +164,13 @@ struct at_context {
struct work_struct work;
};

+// The local-to-local transaction is handled by the work item in the following structure.
+struct at_local {
+ struct list_head list;
+ spinlock_t lock;
+ struct work_struct work;
+};
+
struct iso_context {
struct fw_iso_context base;
struct context context;
@@ -216,6 +223,9 @@ struct fw_ohci {
struct at_context at_request_ctx;
struct at_context at_response_ctx;

+ struct at_local at_request_local;
+ struct at_local at_response_local;
+
u32 it_context_support;
u32 it_context_mask; /* unoccupied IT contexts */
struct iso_context *it_context_list;
@@ -1579,6 +1589,51 @@ static void handle_local_at_response_packet(struct fw_ohci *ohci, struct fw_pack
packet->callback(packet, &ohci->card, packet->ack);
}

+static void handle_at_local_packets(struct at_local *local, struct fw_ohci *ohci)
+{
+ struct fw_packet *packet;
+
+ spin_lock(&local->lock);
+
+ while ((packet = list_first_entry_or_null(&local->list, typeof(*packet), link_for_local))) {
+ list_del(&packet->link_for_local);
+ spin_unlock(&local->lock);
+
+ if (unlikely(packet->ack != 0)) {
+ // This case is active when the call of at_context_queue_packet() returns
+ // error in at_context_transmit().
+ packet->callback(packet, &ohci->card, packet->ack);
+ }
+
+ spin_lock(&local->lock);
+ }
+
+ spin_unlock(&local->lock);
+}
+
+static void at_request_local_work(struct work_struct *work)
+{
+ struct at_local *local = from_work(local, work, work);
+ struct fw_ohci *ohci = container_of(local, struct fw_ohci, at_request_local);
+
+ handle_at_local_packets(local, ohci);
+}
+
+static void at_response_local_work(struct work_struct *work)
+{
+ struct at_local *local = from_work(local, work, work);
+ struct fw_ohci *ohci = container_of(local, struct fw_ohci, at_response_local);
+
+ handle_at_local_packets(local, ohci);
+}
+
+static void at_local_init(struct at_local *local, work_func_t func)
+{
+ spin_lock_init(&local->lock);
+ INIT_LIST_HEAD(&local->list);
+ INIT_WORK(&local->work, func);
+}
+
static bool destination_is_local(const struct fw_packet *packet, const struct fw_ohci *ohci)
__must_hold(&ohci->lock)
{
@@ -1588,6 +1643,24 @@ __must_hold(&ohci->lock)
ohci->generation == packet->generation);
}

+static void queue_work_for_at_local_packet(struct at_context *ctx, struct fw_packet *packet,
+ struct fw_ohci *ohci)
+{
+ struct at_local *local;
+
+ if (ctx == &ohci->at_request_ctx)
+ local = &ohci->at_request_local;
+ else
+ local = &ohci->at_response_local;
+
+ // Timestamping on behalf of the hardware.
+ packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
+
+ scoped_guard(spinlock_irqsave, &local->lock)
+ list_add_tail(&packet->link_for_local, &local->list);
+ queue_work(ohci->card.async_wq, &local->work);
+}
+
static void at_context_transmit(struct at_context *ctx, struct fw_packet *packet)
{
struct fw_ohci *ohci = ctx->context.ohci;
@@ -1612,12 +1685,8 @@ static void at_context_transmit(struct at_context *ctx, struct fw_packet *packet
ret = at_context_queue_packet(ctx, packet);
spin_unlock_irqrestore(&ohci->lock, flags);

- if (ret < 0) {
- // Timestamping on behalf of the hardware.
- packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
-
- packet->callback(packet, &ohci->card, packet->ack);
- }
+ if (ret < 0)
+ queue_work_for_at_local_packet(ctx, packet, ohci);
}

static void detect_dead_context(struct fw_ohci *ohci,
@@ -2474,6 +2543,9 @@ static void ohci_disable(struct fw_card *card)
flush_work(&ohci->at_request_ctx.work);
flush_work(&ohci->at_response_ctx.work);

+ flush_work(&ohci->at_request_local.work);
+ flush_work(&ohci->at_response_local.work);
+
for (i = 0; i < ohci->n_ir; ++i) {
if (!(ohci->ir_context_mask & BIT(i)))
flush_work(&ohci->ir_context_list[i].base.work);
@@ -2485,6 +2557,9 @@ static void ohci_disable(struct fw_card *card)

at_context_flush(&ohci->at_request_ctx);
at_context_flush(&ohci->at_response_ctx);
+
+ at_request_local_work(&ohci->at_request_local.work);
+ at_response_local_work(&ohci->at_response_local.work);
}

static int ohci_set_config_rom(struct fw_card *card,
@@ -3684,6 +3759,9 @@ static int pci_probe(struct pci_dev *dev,
return err;
INIT_WORK(&ohci->at_response_ctx.work, ohci_at_context_work);

+ at_local_init(&ohci->at_request_local, at_request_local_work);
+ at_local_init(&ohci->at_response_local, at_response_local_work);
+
reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, ~0);
ohci->ir_context_channels = ~0ULL;
ohci->ir_context_support = reg_read(ohci, OHCI1394_IsoRecvIntMaskSet);
diff --git a/include/linux/firewire.h b/include/linux/firewire.h
index cbe59a18162e..1c71ff69c42f 100644
--- a/include/linux/firewire.h
+++ b/include/linux/firewire.h
@@ -329,6 +329,9 @@ struct fw_packet {
bool payload_mapped;
u32 timestamp;

+ // Used to handle the local-to-local packets in the AT request/response contexts.
+ struct list_head link_for_local;
+
/*
* This callback is called when the packet transmission has completed.
* For successful transmission, the status code is the ack received
--
2.53.0