firewire: core: fix DMA mapping direction

Seen with recent libdc1394:  If a client mmap()s the buffer of an
isochronous reception buffer with PROT_READ|PROT_WRITE instead of just
PROT_READ, firewire-core sets the wrong DMA mapping direction during
buffer initialization.

The fix is to split fw_iso_buffer_init() into allocation and DMA mapping
and to perform the latter after both buffer and DMA context were
allocated.  Buffer allocation and context allocation may happen in any
order, but we need the context type (reception or transmission) in order
to set the DMA direction of the buffer.

Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
This commit is contained in:
Stefan Richter 2012-04-09 20:51:18 +02:00
parent fe2af11c22
commit 0b6c4857f7
4 changed files with 103 additions and 42 deletions

View File

@ -22,6 +22,7 @@
#include <linux/compat.h> #include <linux/compat.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/firewire.h> #include <linux/firewire.h>
#include <linux/firewire-cdev.h> #include <linux/firewire-cdev.h>
@ -70,6 +71,7 @@ struct client {
u64 iso_closure; u64 iso_closure;
struct fw_iso_buffer buffer; struct fw_iso_buffer buffer;
unsigned long vm_start; unsigned long vm_start;
bool buffer_is_mapped;
struct list_head phy_receiver_link; struct list_head phy_receiver_link;
u64 phy_receiver_closure; u64 phy_receiver_closure;
@ -959,11 +961,20 @@ static void iso_mc_callback(struct fw_iso_context *context,
sizeof(e->interrupt), NULL, 0); sizeof(e->interrupt), NULL, 0);
} }
static enum dma_data_direction iso_dma_direction(struct fw_iso_context *context)
{
if (context->type == FW_ISO_CONTEXT_TRANSMIT)
return DMA_TO_DEVICE;
else
return DMA_FROM_DEVICE;
}
static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) 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_cdev_create_iso_context *a = &arg->create_iso_context;
struct fw_iso_context *context; struct fw_iso_context *context;
fw_iso_callback_t cb; fw_iso_callback_t cb;
int ret;
BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT || BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT ||
FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE || FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE ||
@ -1004,8 +1015,21 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
if (client->iso_context != NULL) { if (client->iso_context != NULL) {
spin_unlock_irq(&client->lock); spin_unlock_irq(&client->lock);
fw_iso_context_destroy(context); fw_iso_context_destroy(context);
return -EBUSY; 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) {
spin_unlock_irq(&client->lock);
fw_iso_context_destroy(context);
return ret;
}
client->buffer_is_mapped = true;
}
client->iso_closure = a->closure; client->iso_closure = a->closure;
client->iso_context = context; client->iso_context = context;
spin_unlock_irq(&client->lock); spin_unlock_irq(&client->lock);
@ -1651,7 +1675,6 @@ static long fw_device_op_compat_ioctl(struct file *file,
static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma) static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma)
{ {
struct client *client = file->private_data; struct client *client = file->private_data;
enum dma_data_direction direction;
unsigned long size; unsigned long size;
int page_count, ret; int page_count, ret;
@ -1674,20 +1697,28 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma)
if (size & ~PAGE_MASK) if (size & ~PAGE_MASK)
return -EINVAL; return -EINVAL;
if (vma->vm_flags & VM_WRITE) ret = fw_iso_buffer_alloc(&client->buffer, page_count);
direction = DMA_TO_DEVICE;
else
direction = DMA_FROM_DEVICE;
ret = fw_iso_buffer_init(&client->buffer, client->device->card,
page_count, direction);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = fw_iso_buffer_map(&client->buffer, vma); spin_lock_irq(&client->lock);
if (client->iso_context) {
ret = fw_iso_buffer_map_dma(&client->buffer,
client->device->card,
iso_dma_direction(client->iso_context));
client->buffer_is_mapped = (ret == 0);
}
spin_unlock_irq(&client->lock);
if (ret < 0) if (ret < 0)
fw_iso_buffer_destroy(&client->buffer, client->device->card); goto fail;
ret = fw_iso_buffer_map_vma(&client->buffer, vma);
if (ret < 0)
goto fail;
return 0;
fail:
fw_iso_buffer_destroy(&client->buffer, client->device->card);
return ret; return ret;
} }

View File

@ -39,52 +39,73 @@
* Isochronous DMA context management * Isochronous DMA context management
*/ */
int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, int fw_iso_buffer_alloc(struct fw_iso_buffer *buffer, int page_count)
int page_count, enum dma_data_direction direction)
{ {
int i, j; int i;
dma_addr_t address;
buffer->page_count = page_count;
buffer->direction = direction;
buffer->page_count = 0;
buffer->page_count_mapped = 0;
buffer->pages = kmalloc(page_count * sizeof(buffer->pages[0]), buffer->pages = kmalloc(page_count * sizeof(buffer->pages[0]),
GFP_KERNEL); GFP_KERNEL);
if (buffer->pages == NULL) if (buffer->pages == NULL)
goto out; return -ENOMEM;
for (i = 0; i < buffer->page_count; i++) { for (i = 0; i < page_count; i++) {
buffer->pages[i] = alloc_page(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO); buffer->pages[i] = alloc_page(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO);
if (buffer->pages[i] == NULL) if (buffer->pages[i] == NULL)
goto out_pages; break;
}
address = dma_map_page(card->device, buffer->pages[i], buffer->page_count = i;
0, PAGE_SIZE, direction); if (i < page_count) {
if (dma_mapping_error(card->device, address)) { fw_iso_buffer_destroy(buffer, NULL);
__free_page(buffer->pages[i]); return -ENOMEM;
goto out_pages;
}
set_page_private(buffer->pages[i], address);
} }
return 0; return 0;
}
out_pages: int fw_iso_buffer_map_dma(struct fw_iso_buffer *buffer, struct fw_card *card,
for (j = 0; j < i; j++) { enum dma_data_direction direction)
address = page_private(buffer->pages[j]); {
dma_unmap_page(card->device, address, dma_addr_t address;
PAGE_SIZE, direction); int i;
__free_page(buffer->pages[j]);
buffer->direction = direction;
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))
break;
set_page_private(buffer->pages[i], address);
} }
kfree(buffer->pages); buffer->page_count_mapped = i;
out: if (i < buffer->page_count)
buffer->pages = NULL; return -ENOMEM;
return -ENOMEM; return 0;
}
int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card,
int page_count, enum dma_data_direction direction)
{
int ret;
ret = fw_iso_buffer_alloc(buffer, page_count);
if (ret < 0)
return ret;
ret = fw_iso_buffer_map_dma(buffer, card, direction);
if (ret < 0)
fw_iso_buffer_destroy(buffer, card);
return ret;
} }
EXPORT_SYMBOL(fw_iso_buffer_init); EXPORT_SYMBOL(fw_iso_buffer_init);
int fw_iso_buffer_map(struct fw_iso_buffer *buffer, struct vm_area_struct *vma) int fw_iso_buffer_map_vma(struct fw_iso_buffer *buffer,
struct vm_area_struct *vma)
{ {
unsigned long uaddr; unsigned long uaddr;
int i, err; int i, err;
@ -107,15 +128,18 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer,
int i; int i;
dma_addr_t address; dma_addr_t address;
for (i = 0; i < buffer->page_count; i++) { for (i = 0; i < buffer->page_count_mapped; i++) {
address = page_private(buffer->pages[i]); address = page_private(buffer->pages[i]);
dma_unmap_page(card->device, address, dma_unmap_page(card->device, address,
PAGE_SIZE, buffer->direction); PAGE_SIZE, buffer->direction);
__free_page(buffer->pages[i]);
} }
for (i = 0; i < buffer->page_count; i++)
__free_page(buffer->pages[i]);
kfree(buffer->pages); kfree(buffer->pages);
buffer->pages = NULL; buffer->pages = NULL;
buffer->page_count = 0;
buffer->page_count_mapped = 0;
} }
EXPORT_SYMBOL(fw_iso_buffer_destroy); EXPORT_SYMBOL(fw_iso_buffer_destroy);

View File

@ -3,6 +3,7 @@
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/idr.h> #include <linux/idr.h>
@ -169,7 +170,11 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event);
/* -iso */ /* -iso */
int fw_iso_buffer_map(struct fw_iso_buffer *buffer, struct vm_area_struct *vma); 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);
int fw_iso_buffer_map_vma(struct fw_iso_buffer *buffer,
struct vm_area_struct *vma);
/* -topology */ /* -topology */

View File

@ -391,6 +391,7 @@ struct fw_iso_buffer {
enum dma_data_direction direction; enum dma_data_direction direction;
struct page **pages; struct page **pages;
int page_count; int page_count;
int page_count_mapped;
}; };
int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card,