summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2025-08-02 09:52:53 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2025-08-02 09:52:53 -0700
commit7061835997daba9e73c723c85bd70bc4c44aef77 (patch)
tree54fd09c443b298e9e3b48b07d1731d53f5a12e29
parenta6923c06a3b2e2c534ae28c53a7531e76cc95cfa (diff)
parent95a042a0c8ecd3c1e886648f6f6ab9c7e4403db9 (diff)
Merge tag 'firewire-updates-6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394
Pull firewire updates from Takashi Sakamoto: "This update replaces the remaining tasklet usage in the FireWire subsystem with workqueue for asynchronous packet transmission. With this change, tasklets are now fully eliminated from the subsystem. Asynchronous packet transmission is used for serial bus topology management as well as for the operation of the SBP-2 protocol driver (firewire-sbp2). To ensure reliability during low-memory conditions, the associated workqueue is created with the WQ_MEM_RECLAIM flag, allowing it to participate in memory reclaim paths. Other attributes are aligned with those used for isochronous packet handling, which was migrated to workqueues in v6.12. The workqueues are sleepable and support preemptible work items, making them more suitable for real-time workloads that benefit from timely task preemption at the system level. There remains an issue where 'schedule()' may be called within an RCU read-side critical section, due to a direct replacement of 'tasklet_disable_in_atomic()' with 'disable_work_sync()'. A proposed fix for this has been posted[1], and is currently under review and testing. It is expected to be sent upstream later" Link: https://lore.kernel.org/lkml/20250728015125.17825-1-o-takashi@sakamocchi.jp/ [1] * tag 'firewire-updates-6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394: firewire: ohci: reduce the size of common context structure by extracting members into AT structure firewire: core: minor code refactoring to localize table of gap count firewire: ohci: use workqueue to handle events of AT request/response contexts firewire: ohci: use workqueue to handle events of AR request/response contexts firewire: core: allocate workqueue for AR/AT request/response contexts firewire: core: use from_work() macro to expand parent structure of work_struct firewire: ohci: use from_work() macro to expand parent structure of work_struct firewire: ohci: correct code comments about bus_reset tasklet
-rw-r--r--drivers/firewire/core-card.c59
-rw-r--r--drivers/firewire/core-cdev.c3
-rw-r--r--drivers/firewire/core-device.c15
-rw-r--r--drivers/firewire/core-transaction.c7
-rw-r--r--drivers/firewire/net.c4
-rw-r--r--drivers/firewire/ohci.c162
-rw-r--r--include/linux/firewire.h12
7 files changed, 146 insertions, 116 deletions
diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c
index 01354b9de8b2..aae774e7a5c3 100644
--- a/drivers/firewire/core-card.c
+++ b/drivers/firewire/core-card.c
@@ -237,7 +237,7 @@ EXPORT_SYMBOL(fw_schedule_bus_reset);
static void br_work(struct work_struct *work)
{
- struct fw_card *card = container_of(work, struct fw_card, br_work.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 &&
@@ -273,10 +273,6 @@ static void allocate_broadcast_channel(struct fw_card *card, int generation)
fw_device_set_broadcast_channel);
}
-static const char gap_count_table[] = {
- 63, 5, 7, 8, 10, 13, 16, 18, 21, 24, 26, 29, 32, 35, 37, 40
-};
-
void fw_schedule_bm_work(struct fw_card *card, unsigned long delay)
{
fw_card_get(card);
@@ -286,7 +282,10 @@ void fw_schedule_bm_work(struct fw_card *card, unsigned long delay)
static void bm_work(struct work_struct *work)
{
- struct fw_card *card = container_of(work, struct fw_card, bm_work.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 = from_work(card, work, bm_work.work);
struct fw_device *root_device, *irm_device;
struct fw_node *root_node;
int root_id, new_root_id, irm_id, bm_id, local_id;
@@ -574,7 +573,6 @@ EXPORT_SYMBOL(fw_card_initialize);
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;
int ret;
// This workqueue should be:
@@ -589,29 +587,48 @@ int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid,
// * == 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)
+ card->isoc_wq = alloc_workqueue("firewire-isoc-card%u",
+ WQ_UNBOUND | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS,
+ supported_isoc_contexts, card->index);
+ if (!card->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.
+ card->async_wq = alloc_workqueue("firewire-async-card%u",
+ WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_HIGHPRI | WQ_SYSFS,
+ 4, card->index);
+ if (!card->async_wq) {
+ ret = -ENOMEM;
+ goto err_isoc;
+ }
+
card->max_receive = max_receive;
card->link_speed = link_speed;
card->guid = guid;
- guard(mutex)(&card_mutex);
+ 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)
+ goto err_async;
- generate_config_rom(card, tmp_config_rom);
- ret = card->driver->enable(card, tmp_config_rom, config_rom_length);
- if (ret < 0) {
- destroy_workqueue(isoc_wq);
- return ret;
+ list_add_tail(&card->link, &card_list);
}
- card->isoc_wq = isoc_wq;
- list_add_tail(&card->link, &card_list);
-
return 0;
+err_async:
+ destroy_workqueue(card->async_wq);
+err_isoc:
+ destroy_workqueue(card->isoc_wq);
+ return ret;
}
EXPORT_SYMBOL(fw_card_add);
@@ -744,6 +761,7 @@ void fw_core_remove_card(struct fw_card *card)
dummy_driver.stop_iso = card->driver->stop_iso;
card->driver = &dummy_driver;
drain_workqueue(card->isoc_wq);
+ drain_workqueue(card->async_wq);
scoped_guard(spinlock_irqsave, &card->lock)
fw_destroy_nodes(card);
@@ -753,6 +771,7 @@ void fw_core_remove_card(struct fw_card *card)
wait_for_completion(&card->done);
destroy_workqueue(card->isoc_wq);
+ destroy_workqueue(card->async_wq);
WARN_ON(!list_empty(&card->transaction_list));
}
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c
index bd04980009a4..78b10c6ef7fe 100644
--- a/drivers/firewire/core-cdev.c
+++ b/drivers/firewire/core-cdev.c
@@ -1313,8 +1313,7 @@ static int ioctl_get_cycle_timer(struct client *client, union ioctl_arg *arg)
static void iso_resource_work(struct work_struct *work)
{
struct iso_resource_event *e;
- struct iso_resource *r =
- container_of(work, struct iso_resource, work.work);
+ struct iso_resource *r = from_work(r, work, work.work);
struct client *client = r->client;
unsigned long index = r->resource.handle;
int generation, channel, bandwidth, todo;
diff --git a/drivers/firewire/core-device.c b/drivers/firewire/core-device.c
index ec3e21ad2025..aeacd4cfd694 100644
--- a/drivers/firewire/core-device.c
+++ b/drivers/firewire/core-device.c
@@ -853,8 +853,7 @@ static void fw_schedule_device_work(struct fw_device *device,
static void fw_device_shutdown(struct work_struct *work)
{
- struct fw_device *device =
- container_of(work, struct fw_device, work.work);
+ struct fw_device *device = from_work(device, work, work.work);
if (time_before64(get_jiffies_64(),
device->card->reset_jiffies + SHUTDOWN_DELAY)
@@ -921,8 +920,7 @@ static int update_unit(struct device *dev, void *data)
static void fw_device_update(struct work_struct *work)
{
- struct fw_device *device =
- container_of(work, struct fw_device, work.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);
@@ -1002,8 +1000,7 @@ static int compare_configuration_rom(struct device *dev, const void *data)
static void fw_device_init(struct work_struct *work)
{
- struct fw_device *device =
- container_of(work, struct fw_device, work.work);
+ struct fw_device *device = from_work(device, work, work.work);
struct fw_card *card = device->card;
struct device *found;
u32 minor;
@@ -1184,8 +1181,7 @@ static int reread_config_rom(struct fw_device *device, int generation,
static void fw_device_refresh(struct work_struct *work)
{
- struct fw_device *device =
- container_of(work, struct fw_device, work.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;
@@ -1251,8 +1247,7 @@ static void fw_device_refresh(struct work_struct *work)
static void fw_device_workfn(struct work_struct *work)
{
- struct fw_device *device = container_of(to_delayed_work(work),
- struct fw_device, work);
+ struct fw_device *device = from_work(device, to_delayed_work(work), work);
device->workfn(work);
}
diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c
index 2bd5deb9054e..d28477d84697 100644
--- a/drivers/firewire/core-transaction.c
+++ b/drivers/firewire/core-transaction.c
@@ -557,9 +557,10 @@ const struct fw_address_region fw_unit_space_region =
*
* 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.
+ * 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.
diff --git a/drivers/firewire/net.c b/drivers/firewire/net.c
index 1bf0e15c1540..6d6446713539 100644
--- a/drivers/firewire/net.c
+++ b/drivers/firewire/net.c
@@ -1007,7 +1007,7 @@ static int fwnet_send_packet(struct fwnet_packet_task *ptask)
spin_lock_irqsave(&dev->lock, flags);
- /* If the AT tasklet already ran, we may be last user. */
+ /* If the AT work item already ran, we may be last user. */
free = (ptask->outstanding_pkts == 0 && !ptask->enqueued);
if (!free)
ptask->enqueued = true;
@@ -1026,7 +1026,7 @@ static int fwnet_send_packet(struct fwnet_packet_task *ptask)
spin_lock_irqsave(&dev->lock, flags);
- /* If the AT tasklet already ran, we may be last user. */
+ /* If the AT work item already ran, we may be last user. */
free = (ptask->outstanding_pkts == 0 && !ptask->enqueued);
if (!free)
ptask->enqueued = true;
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c
index edaedd156a6d..5d8301b0f3aa 100644
--- a/drivers/firewire/ohci.c
+++ b/drivers/firewire/ohci.c
@@ -101,7 +101,7 @@ struct ar_context {
void *pointer;
unsigned int last_buffer_index;
u32 regs;
- struct tasklet_struct tasklet;
+ struct work_struct work;
};
struct context;
@@ -128,7 +128,6 @@ struct context {
int total_allocation;
u32 current_bus;
bool running;
- bool flushing;
/*
* List of page-sized buffers for storing DMA descriptors.
@@ -157,8 +156,12 @@ struct context {
int prev_z;
descriptor_callback_t callback;
+};
- struct tasklet_struct tasklet;
+struct at_context {
+ struct context context;
+ struct work_struct work;
+ bool flushing;
};
struct iso_context {
@@ -204,8 +207,8 @@ struct fw_ohci {
struct ar_context ar_request_ctx;
struct ar_context ar_response_ctx;
- struct context at_request_ctx;
- struct context at_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 */
@@ -1016,9 +1019,9 @@ static void ar_recycle_buffers(struct ar_context *ctx, unsigned int end_buffer)
}
}
-static void ar_context_tasklet(unsigned long data)
+static void ohci_ar_context_work(struct work_struct *work)
{
- struct ar_context *ctx = (struct ar_context *)data;
+ struct ar_context *ctx = from_work(ctx, work, work);
unsigned int end_buffer_index, end_buffer_offset;
void *p, *end;
@@ -1026,23 +1029,19 @@ static void ar_context_tasklet(unsigned long data)
if (!p)
return;
- end_buffer_index = ar_search_last_active_buffer(ctx,
- &end_buffer_offset);
+ 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.
- */
+ // 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 */
+ // adjust p to point back into the actual buffer
p -= AR_BUFFERS * PAGE_SIZE;
}
@@ -1057,7 +1056,6 @@ static void ar_context_tasklet(unsigned long data)
ar_recycle_buffers(ctx, end_buffer_index);
return;
-
error:
ctx->pointer = NULL;
}
@@ -1073,7 +1071,7 @@ static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci,
ctx->regs = regs;
ctx->ohci = ohci;
- tasklet_init(&ctx->tasklet, ar_context_tasklet, (unsigned long)ctx);
+ INIT_WORK(&ctx->work, ohci_ar_context_work);
for (i = 0; i < AR_BUFFERS; i++) {
ctx->pages[i] = dma_alloc_pages(dev, PAGE_SIZE, &dma_addr,
@@ -1181,16 +1179,16 @@ static void context_retire_descriptors(struct context *ctx)
}
}
-static void context_tasklet(unsigned long data)
+static void ohci_at_context_work(struct work_struct *work)
{
- struct context *ctx = (struct context *) data;
+ struct at_context *ctx = from_work(ctx, work, work);
- context_retire_descriptors(ctx);
+ context_retire_descriptors(&ctx->context);
}
static void ohci_isoc_context_work(struct work_struct *work)
{
- struct fw_iso_context *base = container_of(work, struct fw_iso_context, 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);
@@ -1248,7 +1246,6 @@ static int context_init(struct context *ctx, struct fw_ohci *ohci,
ctx->buffer_tail = list_entry(ctx->buffer_list.next,
struct descriptor_buffer, list);
- tasklet_init(&ctx->tasklet, context_tasklet, (unsigned long)ctx);
ctx->callback = callback;
/*
@@ -1388,17 +1385,17 @@ struct driver_data {
* 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 context *ctx,
- struct fw_packet *packet)
+static int at_context_queue_packet(struct at_context *ctx, struct fw_packet *packet)
{
- struct fw_ohci *ohci = ctx->ohci;
+ 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(ctx, 4, &d_bus);
+ d = context_get_descriptors(context, 4, &d_bus);
if (d == NULL) {
packet->ack = RCODE_SEND_ERROR;
return -1;
@@ -1428,7 +1425,7 @@ static int at_context_queue_packet(struct context *ctx,
ohci1394_at_data_set_destination_id(header,
async_header_get_destination(packet->header));
- if (ctx == &ctx->ohci->at_response_ctx) {
+ 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,
@@ -1517,37 +1514,42 @@ static int at_context_queue_packet(struct context *ctx,
return -1;
}
- context_append(ctx, d, z, 4 - z);
+ context_append(context, d, z, 4 - z);
- if (ctx->running)
- reg_write(ohci, CONTROL_SET(ctx->regs), CONTEXT_WAKE);
+ if (context->running)
+ reg_write(ohci, CONTROL_SET(context->regs), CONTEXT_WAKE);
else
- context_run(ctx, 0);
+ context_run(context, 0);
return 0;
}
-static void at_context_flush(struct context *ctx)
+static void at_context_flush(struct at_context *ctx)
{
- tasklet_disable(&ctx->tasklet);
+ // Avoid dead lock due to programming mistake.
+ if (WARN_ON_ONCE(current_work() == &ctx->work))
+ return;
- ctx->flushing = true;
- context_tasklet((unsigned long)ctx);
- ctx->flushing = false;
+ disable_work_sync(&ctx->work);
- tasklet_enable(&ctx->tasklet);
+ WRITE_ONCE(ctx->flushing, true);
+ ohci_at_context_work(&ctx->work);
+ WRITE_ONCE(ctx->flushing, false);
+
+ enable_work(&ctx->work);
}
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;
- struct fw_ohci *ohci = context->ohci;
int evt;
- if (last->transfer_status == 0 && !context->flushing)
+ if (last->transfer_status == 0 && !READ_ONCE(ctx->flushing))
/* This descriptor isn't done yet, stop iteration. */
return 0;
@@ -1581,7 +1583,7 @@ static int handle_at_packet(struct context *context,
break;
case OHCI1394_evt_missing_ack:
- if (context->flushing)
+ if (READ_ONCE(ctx->flushing))
packet->ack = RCODE_GENERATION;
else {
/*
@@ -1603,7 +1605,7 @@ static int handle_at_packet(struct context *context,
break;
case OHCI1394_evt_no_status:
- if (context->flushing) {
+ if (READ_ONCE(ctx->flushing)) {
packet->ack = RCODE_GENERATION;
break;
}
@@ -1700,13 +1702,14 @@ static void handle_local_lock(struct fw_ohci *ohci,
fw_core_handle_response(&ohci->card, &response);
}
-static void handle_local_request(struct context *ctx, struct fw_packet *packet)
+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 == &ctx->ohci->at_request_ctx) {
+ if (ctx == &ohci->at_request_ctx) {
packet->ack = ACK_PENDING;
- packet->callback(packet, &ctx->ohci->card, packet->ack);
+ packet->callback(packet, &ohci->card, packet->ack);
}
offset = async_header_get_offset(packet->header);
@@ -1714,54 +1717,55 @@ static void handle_local_request(struct context *ctx, struct fw_packet *packet)
/* Handle config rom reads. */
if (csr >= CSR_CONFIG_ROM && csr < CSR_CONFIG_ROM_END)
- handle_local_rom(ctx->ohci, packet, csr);
+ 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(ctx->ohci, packet, csr);
+ handle_local_lock(ohci, packet, csr);
break;
default:
- if (ctx == &ctx->ohci->at_request_ctx)
- fw_core_handle_request(&ctx->ohci->card, packet);
+ if (ctx == &ohci->at_request_ctx)
+ fw_core_handle_request(&ohci->card, packet);
else
- fw_core_handle_response(&ctx->ohci->card, packet);
+ fw_core_handle_response(&ohci->card, packet);
break;
}
- if (ctx == &ctx->ohci->at_response_ctx) {
+ if (ctx == &ohci->at_response_ctx) {
packet->ack = ACK_COMPLETE;
- packet->callback(packet, &ctx->ohci->card, packet->ack);
+ packet->callback(packet, &ohci->card, packet->ack);
}
}
-static void at_context_transmit(struct context *ctx, struct fw_packet *packet)
+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(&ctx->ohci->lock, flags);
+ spin_lock_irqsave(&ohci->lock, flags);
- if (async_header_get_destination(packet->header) == ctx->ohci->node_id &&
- ctx->ohci->generation == packet->generation) {
- spin_unlock_irqrestore(&ctx->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(ctx->ohci));
+ 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(&ctx->ohci->lock, flags);
+ 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(ctx->ohci));
+ packet->timestamp = cycle_time_to_ohci_tstamp(get_cycle_time(ohci));
- packet->callback(packet, &ctx->ohci->card, packet->ack);
+ packet->callback(packet, &ohci->card, packet->ack);
}
}
@@ -2028,8 +2032,7 @@ static int find_and_insert_self_id(struct fw_ohci *ohci, int self_id_count)
static void bus_reset_work(struct work_struct *work)
{
- struct fw_ohci *ohci =
- container_of(work, struct fw_ohci, bus_reset_work);
+ struct fw_ohci *ohci = from_work(ohci, work, bus_reset_work);
int self_id_count, generation, new_generation, i, j;
u32 reg, quadlet;
void *free_rom = NULL;
@@ -2141,8 +2144,8 @@ static void bus_reset_work(struct work_struct *work)
// 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_stop(&ohci->at_response_ctx);
+ context_stop(&ohci->at_request_ctx.context);
+ context_stop(&ohci->at_response_ctx.context);
}
/*
@@ -2239,16 +2242,16 @@ static irqreturn_t irq_handler(int irq, void *data)
}
if (event & OHCI1394_RQPkt)
- tasklet_schedule(&ohci->ar_request_ctx.tasklet);
+ queue_work(ohci->card.async_wq, &ohci->ar_request_ctx.work);
if (event & OHCI1394_RSPkt)
- tasklet_schedule(&ohci->ar_response_ctx.tasklet);
+ queue_work(ohci->card.async_wq, &ohci->ar_response_ctx.work);
if (event & OHCI1394_reqTxComplete)
- tasklet_schedule(&ohci->at_request_ctx.tasklet);
+ queue_work(ohci->card.async_wq, &ohci->at_request_ctx.work);
if (event & OHCI1394_respTxComplete)
- tasklet_schedule(&ohci->at_response_ctx.tasklet);
+ queue_work(ohci->card.async_wq, &ohci->at_response_ctx.work);
if (event & OHCI1394_isochRx) {
iso_event = reg_read(ohci, OHCI1394_IsoRecvIntEventClear);
@@ -2528,7 +2531,7 @@ static int ohci_enable(struct fw_card *card,
* 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 tasklet.
+ * the right values in the bus reset work item.
*/
if (config_rom) {
@@ -2617,7 +2620,7 @@ static int ohci_set_config_rom(struct fw_card *card,
* 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 tasklet we
+ * 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
@@ -2659,7 +2662,7 @@ static int ohci_set_config_rom(struct fw_card *card,
/*
* 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 tasklet, since the OHCI
+ * mappings in the bus reset work item, since the OHCI
* controller could need to access it before the bus reset
* takes effect.
*/
@@ -2686,11 +2689,14 @@ static void ohci_send_response(struct fw_card *card, struct fw_packet *packet)
static int ohci_cancel_packet(struct fw_card *card, struct fw_packet *packet)
{
struct fw_ohci *ohci = fw_ohci(card);
- struct context *ctx = &ohci->at_request_ctx;
+ struct at_context *ctx = &ohci->at_request_ctx;
struct driver_data *driver_data = packet->driver_data;
int ret = -ENOENT;
- tasklet_disable_in_atomic(&ctx->tasklet);
+ // 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;
@@ -2709,7 +2715,7 @@ static int ohci_cancel_packet(struct fw_card *card, struct fw_packet *packet)
packet->callback(packet, &ohci->card, packet->ack);
ret = 0;
out:
- tasklet_enable(&ctx->tasklet);
+ enable_work(&ctx->work);
return ret;
}
@@ -3767,15 +3773,17 @@ static int pci_probe(struct pci_dev *dev,
if (err < 0)
return err;
- err = context_init(&ohci->at_request_ctx, ohci,
+ 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, ohci,
+ 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;
diff --git a/include/linux/firewire.h b/include/linux/firewire.h
index b632eec3ab52..cceb70415ed2 100644
--- a/include/linux/firewire.h
+++ b/include/linux/firewire.h
@@ -136,6 +136,7 @@ struct fw_card {
__be32 maint_utility_register;
struct workqueue_struct *isoc_wq;
+ struct workqueue_struct *async_wq;
};
static inline struct fw_card *fw_card_get(struct fw_card *card)
@@ -307,8 +308,7 @@ struct fw_packet {
* For successful transmission, the status code is the ack received
* from the destination. Otherwise it is one of the juju-specific
* rcodes: RCODE_SEND_ERROR, _CANCELLED, _BUSY, _GENERATION, _NO_ACK.
- * The callback can be called from tasklet context and thus
- * must never block.
+ * The callback can be called from workqueue and thus must never block.
*/
fw_packet_callback_t callback;
int ack;
@@ -381,6 +381,10 @@ void __fw_send_request(struct fw_card *card, struct fw_transaction *t, int tcode
*
* A variation of __fw_send_request() to generate callback for response subaction without time
* stamp.
+ *
+ * The callback is invoked in the workqueue context in most cases. However, if an error is detected
+ * before queueing or the destination address refers to the local node, it is invoked in the
+ * current context instead.
*/
static inline void fw_send_request(struct fw_card *card, struct fw_transaction *t, int tcode,
int destination_id, int generation, int speed,
@@ -410,6 +414,10 @@ static inline void fw_send_request(struct fw_card *card, struct fw_transaction *
* @callback_data: data to be passed to the transaction completion callback
*
* A variation of __fw_send_request() to generate callback for response subaction with time stamp.
+ *
+ * The callback is invoked in the workqueue context in most cases. However, if an error is detected
+ * before queueing or the destination address refers to the local node, it is invoked in the current
+ * context instead.
*/
static inline void fw_send_request_with_tstamp(struct fw_card *card, struct fw_transaction *t,
int tcode, int destination_id, int generation, int speed, unsigned long long offset,