From c91adaddb0740c5a436084c85bedfe07b6d0c157 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:04 +0900 Subject: [PATCH 01/18] firewire: core: move private function declaration from public header to internal header The fw_iso_buffer_lookup function is used by core module only, thus no need to describe its prototype in kernel internal header. Link: https://lore.kernel.org/r/20260110013911.19160-2-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core.h | 1 + include/linux/firewire.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index 41fb39d9a4e6..26868f007131 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -166,6 +166,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event); 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) { diff --git a/include/linux/firewire.h b/include/linux/firewire.h index 6143b7d28eac..aa84421b58ac 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -533,7 +533,6 @@ struct fw_iso_buffer { int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, int page_count, enum dma_data_direction direction); void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, struct fw_card *card); -size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed); struct fw_iso_context; typedef void (*fw_iso_callback_t)(struct fw_iso_context *context, From adc839ae7d0f6c1a823e9731e45abfd87c6cf28b Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:05 +0900 Subject: [PATCH 02/18] firewire: core: use mutex instead of spinlock for client isochronous context There is a restriction that the userspace client associated to a file descriptor can hold one isochronous context. The client-level spinning lock is used to guarantee it, however the lock is also used for multi-purposes. Additionally, there is no need to use this type of lock, and the mutex is available, instead. Link: https://lore.kernel.org/r/20260110013911.19160-3-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-cdev.c | 40 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index 49dc1612c691..2b8a878c8aae 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -63,6 +63,7 @@ struct client { 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; @@ -306,6 +307,7 @@ static int fw_device_op_open(struct inode *inode, struct file *file) 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; @@ -1088,26 +1090,27 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) context->drop_overflow_headers = true; // We only support one context at this time. - guard(spinlock_irq)(&client->lock); - - if (client->iso_context != NULL) { - fw_iso_context_destroy(context); - - return -EBUSY; - } - if (!client->buffer_is_mapped) { - ret = fw_iso_buffer_map_dma(&client->buffer, - client->device->card, - iso_dma_direction(context)); - if (ret < 0) { + scoped_guard(mutex, &client->iso_context_mutex) { + if (client->iso_context != NULL) { fw_iso_context_destroy(context); - return ret; + return -EBUSY; } - client->buffer_is_mapped = true; + // 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_is_mapped) { + 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->buffer_is_mapped = true; + } + client->iso_closure = a->closure; + client->iso_context = context; } - client->iso_closure = a->closure; - client->iso_context = context; a->handle = 0; @@ -1826,7 +1829,9 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma) if (ret < 0) return ret; - scoped_guard(spinlock_irq, &client->lock) { + 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)); @@ -1879,6 +1884,7 @@ static int fw_device_op_release(struct inode *inode, struct file *file) 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); From a69a9e630d49e2b990486360c371278d42993df0 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:06 +0900 Subject: [PATCH 03/18] firewire: core: code refactoring with cleanup function for isoc pages This commit refactors the implementation to allocate pages for isochronous DMA contexts with cleanup function. Link: https://lore.kernel.org/r/20260110013911.19160-4-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-iso.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index a67493862c85..e678ffba5de2 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -30,26 +30,29 @@ int fw_iso_buffer_alloc(struct fw_iso_buffer *buffer, int page_count) { + struct page **page_array __free(kfree) = kcalloc(page_count, sizeof(page_array[0]), GFP_KERNEL); int i; - buffer->page_count = 0; - buffer->page_count_mapped = 0; - buffer->pages = kmalloc_array(page_count, sizeof(buffer->pages[0]), - GFP_KERNEL); - if (buffer->pages == NULL) + if (!page_array) return -ENOMEM; - for (i = 0; i < page_count; i++) { - buffer->pages[i] = alloc_page(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO); - if (buffer->pages[i] == NULL) + for (i = 0; i < page_count; ++i) { + struct page *page = alloc_page(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO); + + if (!page) break; + page_array[i] = page; } - buffer->page_count = i; + if (i < page_count) { - fw_iso_buffer_destroy(buffer, NULL); + while (i-- > 0) + __free_page(page_array[i]); return -ENOMEM; } + buffer->page_count = page_count; + buffer->pages = no_free_ptr(page_array); + return 0; } @@ -104,11 +107,14 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, dma_unmap_page(card->device, address, PAGE_SIZE, buffer->direction); } - for (i = 0; i < buffer->page_count; i++) - __free_page(buffer->pages[i]); - kfree(buffer->pages); - buffer->pages = NULL; + if (buffer->pages) { + for (int i = 0; i < buffer->page_count; ++i) + __free_page(buffer->pages[i]); + kfree(buffer->pages); + buffer->pages = NULL; + } + buffer->page_count = 0; buffer->page_count_mapped = 0; } From c2f60aa1602d8bdf2cd44bbaef2c862a356ce414 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:07 +0900 Subject: [PATCH 04/18] firewire: core: use common kernel API to allocate and release a batch of pages The pair of alloc_pages_bulk() and release_pages() are convenient to allocate and release a batch of pages. This commit utilizes the pair to maintain pages for isochronous DMA context. Link: https://lore.kernel.org/r/20260110013911.19160-5-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-iso.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index e678ffba5de2..f2e35ac7a476 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -31,22 +31,18 @@ int fw_iso_buffer_alloc(struct fw_iso_buffer *buffer, int page_count) { struct page **page_array __free(kfree) = kcalloc(page_count, sizeof(page_array[0]), GFP_KERNEL); - int i; if (!page_array) return -ENOMEM; - for (i = 0; i < page_count; ++i) { - struct page *page = alloc_page(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO); - - if (!page) - break; - page_array[i] = page; - } - - if (i < page_count) { - while (i-- > 0) - __free_page(page_array[i]); + // 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; } @@ -64,7 +60,10 @@ int fw_iso_buffer_map_dma(struct fw_iso_buffer *buffer, struct fw_card *card, buffer->direction = direction; + // 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. address = dma_map_page(card->device, buffer->pages[i], 0, PAGE_SIZE, direction); if (dma_mapping_error(card->device, address)) @@ -109,8 +108,7 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, } if (buffer->pages) { - for (int i = 0; i < buffer->page_count; ++i) - __free_page(buffer->pages[i]); + release_pages(buffer->pages, buffer->page_count); kfree(buffer->pages); buffer->pages = NULL; } From ef6bdffbb88d86c8a2906fc8a13ae90eb5a6b64e Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:08 +0900 Subject: [PATCH 05/18] firewire: core: stop using page private to store DMA mapping address There is a long discussion about the use of private field in page structure between Linux kernel developers. This commit stop using page private to store DMA mapping address for isochronous context, to prepare for mm future change. Link: https://lore.kernel.org/r/20260110013911.19160-6-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-cdev.c | 5 +--- drivers/firewire/core-iso.c | 48 +++++++++++++++++++----------------- drivers/firewire/ohci.c | 24 +++++++++--------- include/linux/firewire.h | 2 +- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index 2b8a878c8aae..bb4d0f938f5b 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -67,7 +67,6 @@ struct client { u64 iso_closure; struct fw_iso_buffer buffer; unsigned long vm_start; - bool buffer_is_mapped; struct list_head phy_receiver_link; u64 phy_receiver_closure; @@ -1098,7 +1097,7 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) } // 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_is_mapped) { + 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) { @@ -1106,7 +1105,6 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) return ret; } - client->buffer_is_mapped = true; } client->iso_closure = a->closure; client->iso_context = context; @@ -1837,7 +1835,6 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma) iso_dma_direction(client->iso_context)); if (ret < 0) goto fail; - client->buffer_is_mapped = true; } } diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index f2e35ac7a476..3f36243ec0c1 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -55,25 +55,32 @@ 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) { - dma_addr_t address; + dma_addr_t *dma_addrs __free(kfree) = kcalloc(buffer->page_count, sizeof(dma_addrs[0]), + GFP_KERNEL); int i; - buffer->direction = direction; + 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. - address = dma_map_page(card->device, buffer->pages[i], - 0, PAGE_SIZE, direction); - if (dma_mapping_error(card->device, address)) + 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; - set_page_private(buffer->pages[i], address); + dma_addrs[i] = dma_addr; } - buffer->page_count_mapped = i; - if (i < buffer->page_count) + 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; } @@ -98,13 +105,13 @@ EXPORT_SYMBOL(fw_iso_buffer_init); void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, struct fw_card *card) { - int i; - dma_addr_t address; - - for (i = 0; i < buffer->page_count_mapped; i++) { - address = page_private(buffer->pages[i]); - dma_unmap_page(card->device, address, - PAGE_SIZE, buffer->direction); + 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) { @@ -114,20 +121,15 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, } buffer->page_count = 0; - buffer->page_count_mapped = 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) { - size_t i; - dma_addr_t address; - ssize_t offset; - - for (i = 0; i < buffer->page_count; i++) { - address = page_private(buffer->pages[i]); - offset = (ssize_t)completed - (ssize_t)address; + 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; } diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index e3e78dc42530..68a336577d36 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -3184,7 +3184,7 @@ static int queue_iso_transmit(struct iso_context *ctx, struct descriptor *d, *last, *pd; struct fw_iso_packet *p; __le32 *header; - dma_addr_t d_bus, page_bus; + 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; @@ -3254,11 +3254,11 @@ static int queue_iso_transmit(struct iso_context *ctx, min(next_page_index, payload_end_index) - payload_index; pd[i].req_count = cpu_to_le16(length); - page_bus = page_private(buffer->pages[page]); - pd[i].data_address = cpu_to_le32(page_bus + offset); + dma_addr_t dma_addr = buffer->dma_addrs[i]; + pd[i].data_address = cpu_to_le32(dma_addr + offset); dma_sync_single_range_for_device(ctx->context.ohci->card.device, - page_bus, offset, length, + dma_addr, offset, length, DMA_TO_DEVICE); payload_index += length; @@ -3287,7 +3287,7 @@ static int queue_iso_packet_per_buffer(struct iso_context *ctx, { struct device *device = ctx->context.ohci->card.device; struct descriptor *d, *pd; - dma_addr_t d_bus, page_bus; + dma_addr_t d_bus; u32 z, header_z, rest; int i, j, length; int page, offset, packet_count, header_size, payload_per_buffer; @@ -3337,10 +3337,10 @@ static int queue_iso_packet_per_buffer(struct iso_context *ctx, pd->res_count = pd->req_count; pd->transfer_status = 0; - page_bus = page_private(buffer->pages[page]); - pd->data_address = cpu_to_le32(page_bus + offset); + 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, page_bus, + dma_sync_single_range_for_device(device, dma_addr, offset, length, DMA_FROM_DEVICE); @@ -3367,7 +3367,7 @@ static int queue_iso_buffer_fill(struct iso_context *ctx, unsigned long payload) { struct descriptor *d; - dma_addr_t d_bus, page_bus; + dma_addr_t d_bus; int page, offset, rest, z, i, length; page = payload >> PAGE_SHIFT; @@ -3400,11 +3400,11 @@ static int queue_iso_buffer_fill(struct iso_context *ctx, d->res_count = d->req_count; d->transfer_status = 0; - page_bus = page_private(buffer->pages[page]); - d->data_address = cpu_to_le32(page_bus + offset); + 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, - page_bus, offset, length, + dma_addr, offset, length, DMA_FROM_DEVICE); rest -= length; diff --git a/include/linux/firewire.h b/include/linux/firewire.h index aa84421b58ac..09c8484f7430 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -526,8 +526,8 @@ struct fw_iso_packet { struct fw_iso_buffer { enum dma_data_direction direction; struct page **pages; + dma_addr_t *dma_addrs; int page_count; - int page_count_mapped; }; int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, From e62b46cdd3ea8117ec6e39cec5cb8d53f96adb7c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:09 +0900 Subject: [PATCH 06/18] firewire: ohci: use MAX macro to guarantee minimum count of pages for AR contexts The computation of page size for AR DMA context can be simplified by MAX macro. Link: https://lore.kernel.org/r/20260110013911.19160-7-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 68a336577d36..f6da4cd7d123 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -86,7 +86,7 @@ struct descriptor { #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 (AR_BUFFERS_MIN >= 2 ? AR_BUFFERS_MIN : 2) +#define AR_BUFFERS MAX(2, AR_BUFFERS_MIN) #define MAX_ASYNC_PAYLOAD 4096 #define MAX_AR_PACKET_SIZE (16 + MAX_ASYNC_PAYLOAD + 4) From f2ae92780ab93ed7e59e469cfd5c7269bd68ddb4 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:10 +0900 Subject: [PATCH 07/18] firewire: ohci: split page allocation from dma mapping 1394 OHCI PCI driver had long been the only user of dma_alloc_pages(). Although tee subsystem recently started using it, they are still a few users of the infrequently-used function. In the discussion for dma-mapping function, Jason Gunthorpe shows his opinion about the design of public API for the function. According to it, the users provide physical address to the function, then receive DMA mapping address, regardless of the location of mapping target. With the above aspects, this commit eliminates the use of dma_alloc_pages() from this subsystem. Link: https://lore.kernel.org/lkml/20250905174324.GI616306@nvidia.com/ Link: https://lore.kernel.org/r/20260110013911.19160-8-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 77 +++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index f6da4cd7d123..e34965acb925 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -539,18 +539,22 @@ static void ar_context_link_page(struct ar_context *ctx, unsigned int index) static void ar_context_release(struct ar_context *ctx) { struct device *dev = ctx->ohci->card.device; - unsigned int i; if (!ctx->buffer) return; - vunmap(ctx->buffer); + for (int i = 0; i < AR_BUFFERS; ++i) { + dma_addr_t dma_addr = page_private(ctx->pages[i]); - for (i = 0; i < AR_BUFFERS; i++) { - if (ctx->pages[i]) - dma_free_pages(dev, PAGE_SIZE, ctx->pages[i], - ar_buffer_bus(ctx, i), DMA_FROM_DEVICE); + dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); + set_page_private(ctx->pages[i], 0); } + + 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) @@ -845,31 +849,57 @@ static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci, { struct device *dev = ohci->card.device; unsigned int i; - dma_addr_t dma_addr; struct page *pages[AR_BUFFERS + AR_WRAPAROUND_PAGES]; + void *vaddr; struct descriptor *d; ctx->regs = regs; ctx->ohci = ohci; 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, - DMA_FROM_DEVICE, GFP_KERNEL); - if (!ctx->pages[i]) - goto out_of_memory; - set_page_private(ctx->pages[i], dma_addr); - dma_sync_single_for_device(dev, dma_addr, PAGE_SIZE, - DMA_FROM_DEVICE); + // 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; } - for (i = 0; i < AR_BUFFERS; i++) - pages[i] = ctx->pages[i]; + // 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] = ctx->pages[i]; - ctx->buffer = vmap(pages, ARRAY_SIZE(pages), VM_MAP, PAGE_KERNEL); - if (!ctx->buffer) - goto out_of_memory; + 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; + set_page_private(pages[i], dma_addr); + dma_sync_single_for_device(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); + } + if (i < AR_BUFFERS) { + while (i-- > 0) { + dma_addr_t dma_addr = page_private(pages[i]); + dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); + } + vunmap(vaddr); + release_pages(pages, nr_populated); + return -ENOMEM; + } + + 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; @@ -886,11 +916,6 @@ static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci, } return 0; - -out_of_memory: - ar_context_release(ctx); - - return -ENOMEM; } static void ar_context_run(struct ar_context *ctx) From 993ab48006b3016cdd3f2b33905e8e167f955495 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 10 Jan 2026 10:39:11 +0900 Subject: [PATCH 08/18] firewire: ohci: stop using page private to store DMA mapping address There is a long discussion about the use of private field in page structure between Linux kernel developers. This commit stop using page private to store DMA mapping address for AR context, to prepare for mm future change. Link: https://lore.kernel.org/r/20260110013911.19160-9-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index e34965acb925..a43fe680d508 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -96,6 +96,7 @@ 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; @@ -513,11 +514,6 @@ static int ohci_update_phy_reg(struct fw_card *card, int addr, return update_phy_reg(ohci, addr, clear_bits, set_bits); } -static inline dma_addr_t ar_buffer_bus(struct ar_context *ctx, unsigned int i) -{ - return page_private(ctx->pages[i]); -} - static void ar_context_link_page(struct ar_context *ctx, unsigned int index) { struct descriptor *d; @@ -544,11 +540,11 @@ static void ar_context_release(struct ar_context *ctx) return; for (int i = 0; i < AR_BUFFERS; ++i) { - dma_addr_t dma_addr = page_private(ctx->pages[i]); - - dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); - set_page_private(ctx->pages[i], 0); + 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; @@ -647,14 +643,12 @@ static void ar_sync_buffers_for_cpu(struct ar_context *ctx, i = ar_first_buffer_index(ctx); while (i != end_buffer_index) { - dma_sync_single_for_cpu(ctx->ohci->card.device, - ar_buffer_bus(ctx, i), - PAGE_SIZE, DMA_FROM_DEVICE); + 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, - ar_buffer_bus(ctx, i), + dma_sync_single_for_cpu(ctx->ohci->card.device, ctx->dma_addrs[i], end_buffer_offset, DMA_FROM_DEVICE); } @@ -795,9 +789,8 @@ static void ar_recycle_buffers(struct ar_context *ctx, unsigned int end_buffer) i = ar_first_buffer_index(ctx); while (i != end_buffer) { - dma_sync_single_for_device(ctx->ohci->card.device, - ar_buffer_bus(ctx, i), - PAGE_SIZE, DMA_FROM_DEVICE); + 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); } @@ -850,6 +843,7 @@ static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci, struct device *dev = ohci->card.device; unsigned int i; struct page *pages[AR_BUFFERS + AR_WRAPAROUND_PAGES]; + dma_addr_t dma_addrs[AR_BUFFERS]; void *vaddr; struct descriptor *d; @@ -885,19 +879,18 @@ static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci, 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; - set_page_private(pages[i], dma_addr); + 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_addr_t dma_addr = page_private(pages[i]); - dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); - } + 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)); @@ -910,7 +903,7 @@ static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci, d->control = cpu_to_le16(DESCRIPTOR_INPUT_MORE | DESCRIPTOR_STATUS | DESCRIPTOR_BRANCH_ALWAYS); - d->data_address = cpu_to_le32(ar_buffer_bus(ctx, i)); + 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)); } From a4cd9860fa085f0d04d2065f4c151fcde9fcdf4a Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 14 Jan 2026 22:17:29 +0900 Subject: [PATCH 09/18] firewire: ohci: fix index of pages for dma address to 1394 OHCI IT context The index of pages for dma address was changed wrongly. This commit corrents it. Fixes: ef6bdffbb88d ("firewire: core: stop using page private to store DMA mapping address") Link: https://lore.kernel.org/r/20260114131729.16133-1-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index a43fe680d508..339047a2e768 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -3272,7 +3272,7 @@ static int queue_iso_transmit(struct iso_context *ctx, 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[i]; + 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, From 6b67470dce11cfaa09915cca8a5a807d3daf0b87 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:14 +0900 Subject: [PATCH 10/18] firewire: core: add function variants for isochronous context creation The fw_iso_callback union was added by a commit ebe4560ed5c ("firewire: Remove function callback casts") to remove function pointer cast. That change affected the cdev layer of the core code, but it is more convenient for fw_iso_context_create() to accept the union directly. This commit renames and changes the existing function to take the union argument, and add static inline wrapper functions as variants. Link: https://lore.kernel.org/r/20260117142823.440811-2-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-cdev.c | 28 +++------------------------- drivers/firewire/core-iso.c | 9 ++++----- drivers/firewire/core.h | 9 +++++++++ include/linux/firewire.h | 14 +++++++++++--- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index bb4d0f938f5b..c26bea253208 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -1026,25 +1026,10 @@ static enum dma_data_direction iso_dma_direction(struct fw_iso_context *context) return DMA_FROM_DEVICE; } -static struct fw_iso_context *fw_iso_mc_context_create(struct fw_card *card, - fw_iso_mc_callback_t callback, - void *callback_data) -{ - struct fw_iso_context *ctx; - - ctx = fw_iso_context_create(card, FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL, - 0, 0, 0, NULL, callback_data); - if (!IS_ERR(ctx)) - ctx->callback.mc = callback; - - return ctx; -} - 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; - union fw_iso_callback cb; int ret; BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT || @@ -1056,20 +1041,15 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) case FW_ISO_CONTEXT_TRANSMIT: if (a->speed > SCODE_3200 || a->channel > 63) return -EINVAL; - - cb.sc = iso_callback; break; case FW_ISO_CONTEXT_RECEIVE: if (a->header_size < 4 || (a->header_size & 3) || a->channel > 63) return -EINVAL; - - cb.sc = iso_callback; break; case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL: - cb.mc = iso_mc_callback; break; default: @@ -1077,12 +1057,10 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) } if (a->type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) - context = fw_iso_mc_context_create(client->device->card, cb.mc, - client); + 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, cb.sc, client); + 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) diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index 3f36243ec0c1..d9b8896c8ce1 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -137,9 +137,8 @@ size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed) return 0; } -struct fw_iso_context *fw_iso_context_create(struct fw_card *card, - int type, int channel, int speed, size_t header_size, - fw_iso_callback_t callback, void *callback_data) +struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, int channel, + int speed, size_t header_size, union fw_iso_callback callback, void *callback_data) { struct fw_iso_context *ctx; @@ -153,7 +152,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card, ctx->channel = channel; ctx->speed = speed; ctx->header_size = header_size; - ctx->callback.sc = callback; + ctx->callback = callback; ctx->callback_data = callback_data; trace_isoc_outbound_allocate(ctx, channel, speed); @@ -162,7 +161,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card, return ctx; } -EXPORT_SYMBOL(fw_iso_context_create); +EXPORT_SYMBOL(__fw_iso_context_create); void fw_iso_context_destroy(struct fw_iso_context *ctx) { diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index 26868f007131..e0ae948605e1 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -173,6 +173,15 @@ static inline void fw_iso_context_init_work(struct fw_iso_context *ctx, work_fun 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, cb, + callback_data); +} + /* -topology */ diff --git a/include/linux/firewire.h b/include/linux/firewire.h index 09c8484f7430..68161b8a8a58 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -558,9 +558,8 @@ struct fw_iso_context { void *callback_data; }; -struct fw_iso_context *fw_iso_context_create(struct fw_card *card, - int type, int channel, int speed, size_t header_size, - fw_iso_callback_t callback, void *callback_data); +struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, int channel, + int speed, size_t header_size, union fw_iso_callback callback, void *callback_data); int fw_iso_context_set_channels(struct fw_iso_context *ctx, u64 *channels); int fw_iso_context_queue(struct fw_iso_context *ctx, struct fw_iso_packet *packet, @@ -569,6 +568,15 @@ int fw_iso_context_queue(struct fw_iso_context *ctx, void fw_iso_context_queue_flush(struct fw_iso_context *ctx); int fw_iso_context_flush_completions(struct fw_iso_context *ctx); +static inline struct fw_iso_context *fw_iso_context_create(struct fw_card *card, int type, + int channel, int speed, size_t header_size, fw_iso_callback_t callback, + void *callback_data) +{ + union fw_iso_callback cb = { .sc = callback }; + + return __fw_iso_context_create(card, type, channel, speed, header_size, cb, callback_data); +} + /** * fw_iso_context_schedule_flush_completions() - schedule work item to process isochronous context. * @ctx: the isochronous context From 0fe92d5d2c2ac3af9cb54260e7c90b945ebb306c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:15 +0900 Subject: [PATCH 11/18] firewire: ohci: refactor isoc single-channel state using a union In 1394 OHCI driver, some members of struct iso_context are only used for single-channel isochronous contexts. This commit groups these members into a union. Link: https://lore.kernel.org/r/20260117142823.440811-3-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 68 +++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 339047a2e768..b1dc0c4feb86 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -168,14 +168,18 @@ struct at_context { struct iso_context { struct fw_iso_context base; struct context context; - void *header; - size_t header_length; unsigned long flushing_completions; u32 mc_buffer_bus; u16 mc_completed; - u16 last_timestamp; u8 sync; u8 tags; + union { + struct { + u16 last_timestamp; + size_t header_length; + void *header; + } sc; + }; }; #define CONFIG_ROM_SIZE (CSR_CONFIG_ROM_END - CSR_CONFIG_ROM) @@ -2735,29 +2739,28 @@ static void ohci_write_csr(struct fw_card *card, int csr_offset, u32 value) static void flush_iso_completions(struct iso_context *ctx, enum fw_iso_context_completions_cause cause) { - trace_isoc_inbound_single_completions(&ctx->base, ctx->last_timestamp, cause, ctx->header, - ctx->header_length); - trace_isoc_outbound_completions(&ctx->base, ctx->last_timestamp, cause, ctx->header, - ctx->header_length); + 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->last_timestamp, - ctx->header_length, ctx->header, - ctx->base.callback_data); - ctx->header_length = 0; + 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->header_length + ctx->base.header_size > PAGE_SIZE) { + if (ctx->sc.header_length + ctx->base.header_size > PAGE_SIZE) { if (ctx->base.drop_overflow_headers) return; flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW); } - ctx_hdr = ctx->header + ctx->header_length; - ctx->last_timestamp = (u16)le32_to_cpu((__force __le32)dma_hdr[0]); + 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 @@ -2770,7 +2773,7 @@ static void copy_iso_headers(struct iso_context *ctx, const u32 *dma_hdr) 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->header_length += ctx->base.header_size; + ctx->sc.header_length += ctx->base.header_size; } static int handle_ir_packet_per_buffer(struct context *context, @@ -2920,18 +2923,18 @@ static int handle_it_packet(struct context *context, sync_it_packet_for_cpu(context, d); - if (ctx->header_length + 4 > PAGE_SIZE) { + if (ctx->sc.header_length + 4 > PAGE_SIZE) { if (ctx->base.drop_overflow_headers) return 1; flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW); } - ctx_hdr = ctx->header + ctx->header_length; - ctx->last_timestamp = le16_to_cpu(last->res_count); + 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->header_length += 4; + ctx->sc.header_length += 4; if (last->control & cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS)) flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_INTERRUPT); @@ -3008,12 +3011,16 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, } memset(ctx, 0, sizeof(*ctx)); - ctx->header_length = 0; - ctx->header = (void *) __get_free_page(GFP_KERNEL); - if (ctx->header == NULL) { - ret = -ENOMEM; - goto out; + + if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { + ctx->sc.header_length = 0; + ctx->sc.header = (void *) __get_free_page(GFP_KERNEL); + if (!ctx->sc.header) { + ret = -ENOMEM; + goto out; + } } + ret = context_init(&ctx->context, ohci, regs, callback); if (ret < 0) goto out_with_header; @@ -3027,7 +3034,10 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, return &ctx->base; out_with_header: - free_page((unsigned long)ctx->header); + if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { + free_page((unsigned long)ctx->sc.header); + ctx->sc.header = NULL; + } out: scoped_guard(spinlock_irq, &ohci->lock) { switch (type) { @@ -3127,7 +3137,11 @@ static void ohci_free_iso_context(struct fw_iso_context *base) ohci_stop_iso(base); context_release(&ctx->context); - free_page((unsigned long)ctx->header); + + if (base->type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { + free_page((unsigned long)ctx->sc.header); + ctx->sc.header = NULL; + } guard(spinlock_irqsave)(&ohci->lock); @@ -3475,7 +3489,7 @@ static int ohci_flush_iso_completions(struct fw_iso_context *base) switch (base->type) { case FW_ISO_CONTEXT_TRANSMIT: case FW_ISO_CONTEXT_RECEIVE: - if (ctx->header_length != 0) + if (ctx->sc.header_length != 0) flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_FLUSH); break; case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL: From c544c829894faa9251be1dcb2fe59a0a7ad21fa9 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:16 +0900 Subject: [PATCH 12/18] firewire: ohci: code refactoring to use union for isoc multiple channel state In 1394 OHCI driver, some members of struct iso_context are only used for multi-channel isochronous contexts. This commit uses a union for these members to clearly separate multi-channel specific state. Link: https://lore.kernel.org/r/20260117142823.440811-4-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index b1dc0c4feb86..5d9857cbbd24 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -169,8 +169,6 @@ struct iso_context { struct fw_iso_context base; struct context context; unsigned long flushing_completions; - u32 mc_buffer_bus; - u16 mc_completed; u8 sync; u8 tags; union { @@ -179,6 +177,10 @@ struct iso_context { size_t header_length; void *header; } sc; + struct { + u32 buffer_bus; + u16 completed; + } mc; }; }; @@ -2826,8 +2828,8 @@ static int handle_ir_buffer_fill(struct context *context, buffer_dma = le32_to_cpu(last->data_address); if (completed > 0) { - ctx->mc_buffer_bus = buffer_dma; - ctx->mc_completed = completed; + ctx->mc.buffer_bus = buffer_dma; + ctx->mc.completed = completed; } if (res_count != 0) @@ -2846,7 +2848,7 @@ static int handle_ir_buffer_fill(struct context *context, ctx->base.callback.mc(&ctx->base, buffer_dma + completed, ctx->base.callback_data); - ctx->mc_completed = 0; + ctx->mc.completed = 0; } return 1; @@ -2855,17 +2857,16 @@ static int handle_ir_buffer_fill(struct context *context, 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); + 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, + 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.mc(&ctx->base, ctx->mc.buffer_bus + ctx->mc.completed, ctx->base.callback_data); - ctx->mc_completed = 0; + ctx->mc.completed = 0; } static inline void sync_it_packet_for_cpu(struct context *context, @@ -3028,7 +3029,7 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, if (type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { set_multichannel_mask(ohci, 0); - ctx->mc_completed = 0; + ctx->mc.completed = 0; } return &ctx->base; @@ -3493,7 +3494,7 @@ static int ohci_flush_iso_completions(struct fw_iso_context *base) flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_FLUSH); break; case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL: - if (ctx->mc_completed != 0) + if (ctx->mc.completed != 0) flush_ir_buffer_fill(ctx); break; default: From ea6fdb1cbde04b16c64205176e20082358ce8e99 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:17 +0900 Subject: [PATCH 13/18] firewire: ohci: use cleanup helper for isoc context header allocation Some cleanup helpers are useful in error path after memory allocation for header storage. Link: https://lore.kernel.org/r/20260117142823.440811-5-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 5d9857cbbd24..6760c8d12637 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -2958,6 +2958,7 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, int type, int channel, size_t header_size) { struct fw_ohci *ohci = fw_ohci(card); + void *header __free(free_page) = NULL; struct iso_context *ctx; descriptor_callback_t callback; u64 *channels; @@ -3015,8 +3016,8 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { ctx->sc.header_length = 0; - ctx->sc.header = (void *) __get_free_page(GFP_KERNEL); - if (!ctx->sc.header) { + header = (void *) __get_free_page(GFP_KERNEL); + if (!header) { ret = -ENOMEM; goto out; } @@ -3024,21 +3025,17 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, ret = context_init(&ctx->context, ohci, regs, callback); if (ret < 0) - goto out_with_header; + goto out; fw_iso_context_init_work(&ctx->base, ohci_isoc_context_work); - if (type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { + 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_with_header: - if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { - free_page((unsigned long)ctx->sc.header); - ctx->sc.header = NULL; - } out: scoped_guard(spinlock_irq, &ohci->lock) { switch (type) { From 090ac6520044d0d02e039d76aaa22f4c9751837f Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:18 +0900 Subject: [PATCH 14/18] firewire: core: add flags member for isochronous context structure This is minor code refactoring to add a flag member to the isochronous context structure. At present, it is used only for the option to drop packets when the context header overflows. Link: https://lore.kernel.org/r/20260117142823.440811-6-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-cdev.c | 2 +- drivers/firewire/core-iso.c | 1 + drivers/firewire/ohci.c | 4 ++-- include/linux/firewire.h | 6 +++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index c26bea253208..9e964fdd175c 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -1064,7 +1064,7 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) if (IS_ERR(context)) return PTR_ERR(context); if (client->version < FW_CDEV_VERSION_AUTO_FLUSH_ISO_OVERFLOW) - context->drop_overflow_headers = true; + context->flags |= FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS; // We only support one context at this time. scoped_guard(mutex, &client->iso_context_mutex) { diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index d9b8896c8ce1..fbbd14d21ca4 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -151,6 +151,7 @@ struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, i ctx->type = type; ctx->channel = channel; ctx->speed = speed; + ctx->flags = 0; ctx->header_size = header_size; ctx->callback = callback; ctx->callback_data = callback_data; diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 6760c8d12637..8bba70b65ad7 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -2756,7 +2756,7 @@ 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 > PAGE_SIZE) { - if (ctx->base.drop_overflow_headers) + if (ctx->base.flags & FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS) return; flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW); } @@ -2925,7 +2925,7 @@ static int handle_it_packet(struct context *context, sync_it_packet_for_cpu(context, d); if (ctx->sc.header_length + 4 > PAGE_SIZE) { - if (ctx->base.drop_overflow_headers) + if (ctx->base.flags & FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS) return 1; flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW); } diff --git a/include/linux/firewire.h b/include/linux/firewire.h index 68161b8a8a58..71d5cc8f28ce 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -546,13 +546,17 @@ union fw_iso_callback { fw_iso_mc_callback_t mc; }; +enum fw_iso_context_flag { + FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS = BIT(0), +}; + struct fw_iso_context { struct fw_card *card; struct work_struct work; int type; int channel; int speed; - bool drop_overflow_headers; + int flags; size_t header_size; union fw_iso_callback callback; void *callback_data; From fc999c7b68ac80cf260a43237e2b304222987243 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:19 +0900 Subject: [PATCH 15/18] firewire: ohci: allocate isoc context header by kvmalloc() Isochronous packet handling now runs in a workqueue context, where page faults is acceptable. This commit replaces __get_free_page() with kvmalloc() when allocating the isochronous context header buffer. Link: https://lore.kernel.org/r/20260117142823.440811-7-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/ohci.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 8bba70b65ad7..888c43940999 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -2958,7 +2958,7 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, int type, int channel, size_t header_size) { struct fw_ohci *ohci = fw_ohci(card); - void *header __free(free_page) = NULL; + void *header __free(kvfree) = NULL; struct iso_context *ctx; descriptor_callback_t callback; u64 *channels; @@ -3016,7 +3016,7 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { ctx->sc.header_length = 0; - header = (void *) __get_free_page(GFP_KERNEL); + header = kvmalloc(PAGE_SIZE, GFP_KERNEL); if (!header) { ret = -ENOMEM; goto out; @@ -3137,7 +3137,7 @@ static void ohci_free_iso_context(struct fw_iso_context *base) context_release(&ctx->context); if (base->type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { - free_page((unsigned long)ctx->sc.header); + kvfree(ctx->sc.header); ctx->sc.header = NULL; } From 9bf71acd65bf190a0ef1bc885a0a664f91beff03 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:20 +0900 Subject: [PATCH 16/18] firewire: core: provide isoc header buffer size outside card driver For single-channel isochronous contexts, the header storage size is hard-coded to PAGE_SIZE. which is inconvenient for protocol implementations requiring more space. This commit refactors the code to obtain the header storage size outside the 1394 OHCI driver. Link: https://lore.kernel.org/r/20260117142823.440811-8-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-card.c | 4 ++-- drivers/firewire/core-iso.c | 8 +++++--- drivers/firewire/core.h | 6 +++--- drivers/firewire/ohci.c | 10 +++++----- include/linux/firewire.h | 7 +++++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c index 0462d7b9e547..a754c6366b97 100644 --- a/drivers/firewire/core-card.c +++ b/drivers/firewire/core-card.c @@ -704,8 +704,8 @@ static int dummy_enable_phys_dma(struct fw_card *card, return -ENODEV; } -static struct fw_iso_context *dummy_allocate_iso_context(struct fw_card *card, - int type, int channel, size_t header_size) +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); } diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index fbbd14d21ca4..3190b2ca1298 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -138,12 +138,13 @@ size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed) } struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, int channel, - int speed, size_t header_size, union fw_iso_callback callback, void *callback_data) + 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); + ctx = card->driver->allocate_iso_context(card, type, channel, header_size, + header_storage_size); if (IS_ERR(ctx)) return ctx; @@ -153,6 +154,7 @@ struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, i 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; diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index e0ae948605e1..8b49d7480c37 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -100,8 +100,8 @@ struct fw_card_driver { 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); + (*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, @@ -178,7 +178,7 @@ static inline struct fw_iso_context *fw_iso_mc_context_create(struct fw_card *ca { union fw_iso_callback cb = { .mc = callback }; - return __fw_iso_context_create(card, FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL, 0, 0, 0, cb, + return __fw_iso_context_create(card, FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL, 0, 0, 0, 0, cb, callback_data); } diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 888c43940999..1c868c1e4a49 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -2755,7 +2755,7 @@ 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 > PAGE_SIZE) { + 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); @@ -2924,7 +2924,7 @@ static int handle_it_packet(struct context *context, sync_it_packet_for_cpu(context, d); - if (ctx->sc.header_length + 4 > PAGE_SIZE) { + 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); @@ -2954,8 +2954,8 @@ static void set_multichannel_mask(struct fw_ohci *ohci, u64 channels) 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) +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; @@ -3016,7 +3016,7 @@ static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card, if (type != FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL) { ctx->sc.header_length = 0; - header = kvmalloc(PAGE_SIZE, GFP_KERNEL); + header = kvmalloc(header_storage_size, GFP_KERNEL); if (!header) { ret = -ENOMEM; goto out; diff --git a/include/linux/firewire.h b/include/linux/firewire.h index 71d5cc8f28ce..8bf568471588 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -558,12 +558,14 @@ struct fw_iso_context { int speed; int flags; size_t header_size; + size_t header_storage_size; union fw_iso_callback callback; void *callback_data; }; struct fw_iso_context *__fw_iso_context_create(struct fw_card *card, int type, int channel, - int speed, size_t header_size, union fw_iso_callback callback, void *callback_data); + int speed, size_t header_size, size_t header_storage_size, + union fw_iso_callback callback, void *callback_data); int fw_iso_context_set_channels(struct fw_iso_context *ctx, u64 *channels); int fw_iso_context_queue(struct fw_iso_context *ctx, struct fw_iso_packet *packet, @@ -578,7 +580,8 @@ static inline struct fw_iso_context *fw_iso_context_create(struct fw_card *card, { union fw_iso_callback cb = { .sc = callback }; - return __fw_iso_context_create(card, type, channel, speed, header_size, cb, callback_data); + return __fw_iso_context_create(card, type, channel, speed, header_size, PAGE_SIZE, cb, + callback_data); } /** From e5f1291b6f3f85559ed124316f814c013262b44e Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:21 +0900 Subject: [PATCH 17/18] firewire: core: add fw_iso_context_create() variant with header storage size This commit adds a new variant of fw_iso_context_create() that allows specifying the size of the isochronous context header storage at allocation time. Link: https://lore.kernel.org/r/20260117142823.440811-9-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- include/linux/firewire.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/firewire.h b/include/linux/firewire.h index 8bf568471588..986d712e4d94 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -584,6 +584,16 @@ static inline struct fw_iso_context *fw_iso_context_create(struct fw_card *card, callback_data); } +static inline struct fw_iso_context *fw_iso_context_create_with_header_storage_size( + struct fw_card *card, int type, int channel, int speed, size_t header_size, + size_t header_storage_size, fw_iso_callback_t callback, void *callback_data) +{ + union fw_iso_callback cb = { .sc = callback }; + + return __fw_iso_context_create(card, type, channel, speed, header_size, header_storage_size, + cb, callback_data); +} + /** * fw_iso_context_schedule_flush_completions() - schedule work item to process isochronous context. * @ctx: the isochronous context From 6b617317e5bc95e9962a712314ae0c4b7a4d5cc3 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sat, 17 Jan 2026 23:28:22 +0900 Subject: [PATCH 18/18] ALSA: firewire: remove PCM buffer size constraint from isoc context header In the IEC 61883-1/6 packet streaming engine, the isochronous context header stores CIP headers. Previously, the header storage was limited to PAGE_SIZE, which constrained the maximum PCM buffer size. There is a function with configurable header size. Now the limitation is removed. Link: https://lore.kernel.org/r/20260117142823.440811-10-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- sound/firewire/amdtp-stream.c | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 5cdc34877fc1..223c880af802 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -191,8 +191,6 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime) { struct snd_pcm_hardware *hw = &runtime->hw; - unsigned int ctx_header_size; - unsigned int maximum_usec_per_period; int err; hw->info = SNDRV_PCM_INFO_BLOCK_TRANSFER | @@ -212,21 +210,6 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, hw->period_bytes_max = hw->period_bytes_min * 2048; hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; - // Linux driver for 1394 OHCI controller voluntarily flushes isoc - // context when total size of accumulated context header reaches - // PAGE_SIZE. This kicks work for the isoc context and brings - // callback in the middle of scheduled interrupts. - // Although AMDTP streams in the same domain use the same events per - // IRQ, use the largest size of context header between IT/IR contexts. - // Here, use the value of context header in IR context is for both - // contexts. - if (!(s->flags & CIP_NO_HEADER)) - ctx_header_size = IR_CTX_HEADER_SIZE_CIP; - else - ctx_header_size = IR_CTX_HEADER_SIZE_NO_CIP; - maximum_usec_per_period = USEC_PER_SEC * PAGE_SIZE / - CYCLES_PER_SECOND / ctx_header_size; - // In IEC 61883-6, one isoc packet can transfer events up to the value // of syt interval. This comes from the interval of isoc cycle. As 1394 // OHCI controller can generate hardware IRQ per isoc packet, the @@ -239,9 +222,10 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, // Due to the above protocol design, the minimum PCM frames per // interrupt should be double of the value of syt interval, thus it is // 250 usec. + // There is no reason, but up to 250 msec to avoid consuming resources so much. err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 250, maximum_usec_per_period); + 250, USEC_PER_SEC / 4); if (err < 0) goto end; @@ -261,6 +245,7 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, SNDRV_PCM_HW_PARAM_RATE, -1); if (err < 0) goto end; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, apply_constraint_to_size, NULL, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, @@ -1715,7 +1700,9 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed, } else { dir = DMA_TO_DEVICE; type = FW_ISO_CONTEXT_TRANSMIT; - ctx_header_size = 0; // No effect for IT context. + // Although no effect for IT context, this value is required to compute the size + // of header storage correctly. + ctx_header_size = sizeof(__be32); } max_ctx_payload_size = amdtp_stream_get_max_ctx_payload_size(s); @@ -1724,9 +1711,9 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed, return err; s->queue_size = queue_size; - s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, - type, channel, speed, ctx_header_size, - amdtp_stream_first_callback, s); + s->context = fw_iso_context_create_with_header_storage_size( + fw_parent_device(s->unit)->card, type, channel, speed, ctx_header_size, + ctx_header_size * queue_size, amdtp_stream_first_callback, s); if (IS_ERR(s->context)) { err = PTR_ERR(s->context); if (err == -EBUSY)