mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 03:44:45 +01:00
firewire updates for v7.0
This update includes the following changes.
- Refactor page allocation dedicated to 1394 OHCI IR/IT/AR DMA contexts
Although 1394 OHCI specification does not impose any restriction on
the memory size dedicated to these DMA contexts, 1394 OHCI PCI driver
allocates pages for convenience when mapping them into either kernel
space or userspace VMA. The driver previously used dma_alloc_pages()
for both page allocation and mapping creation, even though this kernel
API is rarely used. Following discussions questioning the page-oriented
kernel API in the DMA layer, the driver has been refactored to avoid
using this API. In addition, the use of private members in the
allocated pages has been removed following long-standing concern.
- Allocate variable-sized buffer for isochronous context header
1394 OHCI PCI driver previously allocated a single page for isochronous
context header. As a result, the buffer size for the header was fixed
to PAGE_SIZE, which imposed a limitation on IEC 61883-1/6 packet
streaming engine. Consequently, the ALSA PCM devices provided by
drivers for audio and music units in IEEE 1394 bus were constrained in
the maximum size of buffer period (64 ms in most cases). This limitation
is resolved by dynamically allocating the header buffer with an
arbitrary size.
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQQE66IEYNDXNBPeGKSsLtaWM8LwEwUCaYv5twAKCRCsLtaWM8Lw
E8lKAQDFUprNC2Uygge9koT75sArhN6QQzZkPT/kK8aTcsWbeQEAwww148ck9fRF
LDlED0XFKuKLX5obpvxmY3hmQFWaKAs=
=6w+R
-----END PGP SIGNATURE-----
Merge tag 'firewire-updates-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394
Pull firewire updates from Takashi Sakamoto:
- Refactor page allocation dedicated to 1394 OHCI IR/IT/AR DMA contexts
Although 1394 OHCI specification does not impose any restriction on
the memory size dedicated to these DMA contexts, 1394 OHCI PCI driver
allocates pages for convenience when mapping them into either kernel
space or userspace VMA. The driver previously used dma_alloc_pages()
for both page allocation and mapping creation, even though this
kernel API is rarely used. Following discussions questioning the
page-oriented kernel API in the DMA layer, the driver has been
refactored to avoid using this API. In addition, the use of private
members in the allocated pages has been removed following
long-standing concern.
- Allocate variable-sized buffer for isochronous context header
1394 OHCI PCI driver previously allocated a single page for
isochronous context header. As a result, the buffer size for the
header was fixed to PAGE_SIZE, which imposed a limitation on IEC
61883-1/6 packet streaming engine. Consequently, the ALSA PCM devices
provided by drivers for audio and music units in IEEE 1394 bus were
constrained in the maximum size of buffer period (64 ms in most
cases). This limitation is resolved by dynamically allocating the
header buffer with an arbitrary size.
* tag 'firewire-updates-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394:
ALSA: firewire: remove PCM buffer size constraint from isoc context header
firewire: core: add fw_iso_context_create() variant with header storage size
firewire: core: provide isoc header buffer size outside card driver
firewire: ohci: allocate isoc context header by kvmalloc()
firewire: core: add flags member for isochronous context structure
firewire: ohci: use cleanup helper for isoc context header allocation
firewire: ohci: code refactoring to use union for isoc multiple channel state
firewire: ohci: refactor isoc single-channel state using a union
firewire: core: add function variants for isochronous context creation
firewire: ohci: fix index of pages for dma address to 1394 OHCI IT context
firewire: ohci: stop using page private to store DMA mapping address
firewire: ohci: split page allocation from dma mapping
firewire: ohci: use MAX macro to guarantee minimum count of pages for AR contexts
firewire: core: stop using page private to store DMA mapping address
firewire: core: use common kernel API to allocate and release a batch of pages
firewire: core: code refactoring with cleanup function for isoc pages
firewire: core: use mutex instead of spinlock for client isochronous context
firewire: core: move private function declaration from public header to internal header
This commit is contained in:
commit
0d6dd4738d
7 changed files with 268 additions and 228 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ 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;
|
||||
bool buffer_is_mapped;
|
||||
|
||||
struct list_head phy_receiver_link;
|
||||
u64 phy_receiver_closure;
|
||||
|
|
@ -306,6 +306,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;
|
||||
|
||||
|
|
@ -1025,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 ||
|
||||
|
|
@ -1055,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:
|
||||
|
|
@ -1076,38 +1057,36 @@ 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)
|
||||
context->drop_overflow_headers = true;
|
||||
context->flags |= FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS;
|
||||
|
||||
// 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.pages && !client->buffer.dma_addrs) {
|
||||
ret = fw_iso_buffer_map_dma(&client->buffer, client->device->card,
|
||||
iso_dma_direction(context));
|
||||
if (ret < 0) {
|
||||
fw_iso_context_destroy(context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
client->iso_closure = a->closure;
|
||||
client->iso_context = context;
|
||||
}
|
||||
client->iso_closure = a->closure;
|
||||
client->iso_context = context;
|
||||
|
||||
a->handle = 0;
|
||||
|
||||
|
|
@ -1826,13 +1805,14 @@ 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));
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
client->buffer_is_mapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1879,6 +1859,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);
|
||||
|
|
|
|||
|
|
@ -30,48 +30,57 @@
|
|||
|
||||
int fw_iso_buffer_alloc(struct fw_iso_buffer *buffer, int page_count)
|
||||
{
|
||||
int i;
|
||||
struct page **page_array __free(kfree) = kcalloc(page_count, sizeof(page_array[0]), GFP_KERNEL);
|
||||
|
||||
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)
|
||||
break;
|
||||
}
|
||||
buffer->page_count = i;
|
||||
if (i < page_count) {
|
||||
fw_iso_buffer_destroy(buffer, NULL);
|
||||
// Retrieve noncontiguous pages. The descriptors for 1394 OHCI isochronous DMA contexts
|
||||
// have a set of address and length per each, while the reason to use pages is the
|
||||
// convenience to map them into virtual address space of user process.
|
||||
unsigned long nr_populated = alloc_pages_bulk(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO,
|
||||
page_count, page_array);
|
||||
if (nr_populated != page_count) {
|
||||
// Assuming the above call fills page_array sequentially from the beginning.
|
||||
release_pages(page_array, nr_populated);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
buffer->page_count = page_count;
|
||||
buffer->pages = no_free_ptr(page_array);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fw_iso_buffer_map_dma(struct fw_iso_buffer *buffer, struct fw_card *card,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
dma_addr_t 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++) {
|
||||
address = dma_map_page(card->device, buffer->pages[i],
|
||||
0, PAGE_SIZE, direction);
|
||||
if (dma_mapping_error(card->device, address))
|
||||
// The dma_map_phys() with a physical address per page is available here, instead.
|
||||
dma_addr_t dma_addr = dma_map_page(card->device, buffer->pages[i], 0, PAGE_SIZE,
|
||||
direction);
|
||||
if (dma_mapping_error(card->device, dma_addr))
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -96,34 +105,31 @@ 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) {
|
||||
release_pages(buffer->pages, buffer->page_count);
|
||||
kfree(buffer->pages);
|
||||
buffer->pages = NULL;
|
||||
}
|
||||
for (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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -131,14 +137,14 @@ 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, 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;
|
||||
|
||||
|
|
@ -146,8 +152,10 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card,
|
|||
ctx->type = type;
|
||||
ctx->channel = channel;
|
||||
ctx->speed = speed;
|
||||
ctx->flags = 0;
|
||||
ctx->header_size = header_size;
|
||||
ctx->callback.sc = callback;
|
||||
ctx->header_storage_size = header_storage_size;
|
||||
ctx->callback = callback;
|
||||
ctx->callback_data = callback_data;
|
||||
|
||||
trace_isoc_outbound_allocate(ctx, channel, speed);
|
||||
|
|
@ -156,7 +164,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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -166,12 +166,22 @@ 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)
|
||||
{
|
||||
INIT_WORK(&ctx->work, func);
|
||||
}
|
||||
|
||||
static inline struct fw_iso_context *fw_iso_mc_context_create(struct fw_card *card,
|
||||
fw_iso_mc_callback_t callback, void *callback_data)
|
||||
{
|
||||
union fw_iso_callback cb = { .mc = callback };
|
||||
|
||||
return __fw_iso_context_create(card, FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL, 0, 0, 0, 0, cb,
|
||||
callback_data);
|
||||
}
|
||||
|
||||
|
||||
/* -topology */
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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;
|
||||
|
|
@ -167,14 +168,20 @@ 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;
|
||||
struct {
|
||||
u32 buffer_bus;
|
||||
u16 completed;
|
||||
} mc;
|
||||
};
|
||||
};
|
||||
|
||||
#define CONFIG_ROM_SIZE (CSR_CONFIG_ROM_END - CSR_CONFIG_ROM)
|
||||
|
|
@ -513,11 +520,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;
|
||||
|
|
@ -539,18 +541,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 (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);
|
||||
for (int i = 0; i < AR_BUFFERS; ++i) {
|
||||
dma_addr_t dma_addr = ctx->dma_addrs[i];
|
||||
if (dma_addr)
|
||||
dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE);
|
||||
}
|
||||
memset(ctx->dma_addrs, 0, sizeof(ctx->dma_addrs));
|
||||
|
||||
vunmap(ctx->buffer);
|
||||
ctx->buffer = NULL;
|
||||
|
||||
release_pages(ctx->pages, AR_BUFFERS);
|
||||
memset(ctx->pages, 0, sizeof(ctx->pages));
|
||||
}
|
||||
|
||||
static void ar_context_abort(struct ar_context *ctx, const char *error_msg)
|
||||
|
|
@ -643,14 +649,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);
|
||||
}
|
||||
|
||||
|
|
@ -791,9 +795,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);
|
||||
}
|
||||
|
|
@ -845,31 +848,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];
|
||||
dma_addr_t dma_addrs[AR_BUFFERS];
|
||||
void *vaddr;
|
||||
struct descriptor *d;
|
||||
|
||||
ctx->regs = regs;
|
||||
ctx->ohci = ohci;
|
||||
INIT_WORK(&ctx->work, ohci_ar_context_work);
|
||||
|
||||
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;
|
||||
dma_addrs[i] = dma_addr;
|
||||
dma_sync_single_for_device(dev, dma_addr, PAGE_SIZE, DMA_FROM_DEVICE);
|
||||
}
|
||||
if (i < AR_BUFFERS) {
|
||||
while (i-- > 0)
|
||||
dma_unmap_page(dev, dma_addrs[i], PAGE_SIZE, DMA_FROM_DEVICE);
|
||||
vunmap(vaddr);
|
||||
release_pages(pages, nr_populated);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(ctx->dma_addrs, dma_addrs, sizeof(ctx->dma_addrs));
|
||||
ctx->buffer = vaddr;
|
||||
memcpy(ctx->pages, pages, sizeof(ctx->pages));
|
||||
|
||||
ctx->descriptors = ohci->misc_buffer + descriptors_offset;
|
||||
ctx->descriptors_bus = ohci->misc_buffer_bus + descriptors_offset;
|
||||
|
|
@ -880,17 +909,12 @@ 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));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_of_memory:
|
||||
ar_context_release(ctx);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void ar_context_run(struct ar_context *ctx)
|
||||
|
|
@ -2717,29 +2741,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->base.drop_overflow_headers)
|
||||
if (ctx->sc.header_length + ctx->base.header_size > ctx->base.header_storage_size) {
|
||||
if (ctx->base.flags & FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS)
|
||||
return;
|
||||
flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW);
|
||||
}
|
||||
|
||||
ctx_hdr = ctx->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
|
||||
|
|
@ -2752,7 +2775,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,
|
||||
|
|
@ -2805,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)
|
||||
|
|
@ -2825,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;
|
||||
|
|
@ -2834,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,
|
||||
|
|
@ -2902,18 +2924,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->base.drop_overflow_headers)
|
||||
if (ctx->sc.header_length + 4 > ctx->base.header_storage_size) {
|
||||
if (ctx->base.flags & FW_ISO_CONTEXT_FLAG_DROP_OVERFLOW_HEADERS)
|
||||
return 1;
|
||||
flush_iso_completions(ctx, FW_ISO_CONTEXT_COMPLETIONS_CAUSE_HEADER_OVERFLOW);
|
||||
}
|
||||
|
||||
ctx_hdr = ctx->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);
|
||||
|
|
@ -2932,10 +2954,11 @@ 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;
|
||||
struct iso_context *ctx;
|
||||
descriptor_callback_t callback;
|
||||
u64 *channels;
|
||||
|
|
@ -2990,26 +3013,29 @@ 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;
|
||||
header = kvmalloc(header_storage_size, GFP_KERNEL);
|
||||
if (!header) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = context_init(&ctx->context, ohci, regs, callback);
|
||||
if (ret < 0)
|
||||
goto out_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;
|
||||
ctx->mc.completed = 0;
|
||||
}
|
||||
|
||||
return &ctx->base;
|
||||
|
||||
out_with_header:
|
||||
free_page((unsigned long)ctx->header);
|
||||
out:
|
||||
scoped_guard(spinlock_irq, &ohci->lock) {
|
||||
switch (type) {
|
||||
|
|
@ -3109,7 +3135,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) {
|
||||
kvfree(ctx->sc.header);
|
||||
ctx->sc.header = NULL;
|
||||
}
|
||||
|
||||
guard(spinlock_irqsave)(&ohci->lock);
|
||||
|
||||
|
|
@ -3184,7 +3214,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 +3284,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[page];
|
||||
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 +3317,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 +3367,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 +3397,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 +3430,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;
|
||||
|
|
@ -3457,11 +3487,11 @@ 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:
|
||||
if (ctx->mc_completed != 0)
|
||||
if (ctx->mc.completed != 0)
|
||||
flush_ir_buffer_fill(ctx);
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -526,14 +526,13 @@ 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,
|
||||
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,
|
||||
|
|
@ -547,21 +546,26 @@ 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;
|
||||
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,
|
||||
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, 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,
|
||||
|
|
@ -570,6 +574,26 @@ 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, PAGE_SIZE, cb,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue