Skip to content

Commit

Permalink
sunxi-usb: workaround crashes with unaligned transfer buffers
Browse files Browse the repository at this point in the history
Reuse workaround from terga EHCI driver to solve issue memory corruption and
freezes with non-dma-aligned URB transfer buffers and control message setup
packets.

This patch prevents upstream rtl8192cu driver from freezing my mk802.

Signed-off-by: Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
Acked-by: Hans de Goede <hdegoede@redhat.com>
  • Loading branch information
jkivilin authored and amery committed Feb 19, 2013
1 parent 0ab7579 commit 0253717
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
6 changes: 6 additions & 0 deletions drivers/usb/host/ehci-sunxi.c
Expand Up @@ -171,6 +171,12 @@ static const struct hc_driver sw_ehci_hc_driver = {
*/
.get_frame_number = ehci_get_frame,

/*
* dma fixes
*/
.map_urb_for_dma = sunxi_hcd_map_urb_for_dma,
.unmap_urb_for_dma = sunxi_hcd_unmap_urb_for_dma,

/*
* root hub support
*/
Expand Down
6 changes: 6 additions & 0 deletions drivers/usb/host/ohci-sunxi.c
Expand Up @@ -139,6 +139,12 @@ static const struct hc_driver sw_ohci_hc_driver = {
*/
.get_frame_number = ohci_get_frame,

/*
* dma fixes
*/
.map_urb_for_dma = sunxi_hcd_map_urb_for_dma,
.unmap_urb_for_dma = sunxi_hcd_unmap_urb_for_dma,

/*
* root hub support
*/
Expand Down
162 changes: 162 additions & 0 deletions drivers/usb/host/sw_hci_sunxi.c
Expand Up @@ -57,6 +57,8 @@

#include "sw_hci_sunxi.h"

#define SUNXI_USB_DMA_ALIGN ARCH_DMA_MINALIGN

static char *usbc_name[3] = { "usbc0", "usbc1", "usbc2" };
static char *usbc_ahb_ehci_name[3] = { "", "ahb_ehci0", "ahb_ehci1" };
static char *usbc_ahb_ohci_name[3] = { "", "ahb_ohci0", "ahb_ohci1" };
Expand Down Expand Up @@ -501,6 +503,166 @@ static void sw_set_vbus(struct sw_hci_hcd *sw_hci, int is_on)
return;
}

struct temp_buffer {
void *kmalloc_ptr;
void *old_buffer;
u8 data[];
};

static void *alloc_temp_buffer(size_t size, gfp_t mem_flags)
{
struct temp_buffer *temp, *kmalloc_ptr;
void *temp_data;
size_t kmalloc_size;

kmalloc_size = size + sizeof(struct temp_buffer) +
SUNXI_USB_DMA_ALIGN - 1;

kmalloc_ptr = kmalloc(kmalloc_size, mem_flags);
if (!kmalloc_ptr)
return NULL;

/* Position our struct temp_buffer such that data is aligned */
temp_data = PTR_ALIGN(kmalloc_ptr->data + 1, SUNXI_USB_DMA_ALIGN) - 1;
temp = (void *)((uintptr_t)temp_data -
offsetof(struct temp_buffer, data));

temp->kmalloc_ptr = kmalloc_ptr;
return temp;
}

static void sunxi_hcd_free_temp_buffer(struct urb *urb)
{
enum dma_data_direction dir;
struct temp_buffer *temp;

if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
return;

dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;

temp = container_of(urb->transfer_buffer, struct temp_buffer, data);

if (dir == DMA_FROM_DEVICE)
memcpy(temp->old_buffer, temp->data,
urb->transfer_buffer_length);

urb->transfer_buffer = temp->old_buffer;
kfree(temp->kmalloc_ptr);

urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
}

static int sunxi_hcd_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags)
{
enum dma_data_direction dir;
struct temp_buffer *temp;

if (urb->num_sgs)
return 0;
if (urb->sg)
return 0;
if (urb->transfer_buffer_length == 0)
return 0;
if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
return 0;

/* sunxi hardware requires transfer buffers to be DMA aligned */
if (!((uintptr_t)urb->transfer_buffer & (SUNXI_USB_DMA_ALIGN - 1)))
return 0;

/* Allocate a buffer with enough padding for alignment */
temp = alloc_temp_buffer(urb->transfer_buffer_length, mem_flags);
if (!temp)
return -ENOMEM;

dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;

temp->old_buffer = urb->transfer_buffer;
if (dir == DMA_TO_DEVICE)
memcpy(temp->data, urb->transfer_buffer,
urb->transfer_buffer_length);
urb->transfer_buffer = temp->data;

urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER;

return 0;
}

static void sunxi_hcd_free_temp_setup(struct urb *urb)
{
struct temp_buffer *temp;

if (!(urb->transfer_flags & URB_ALIGNED_TEMP_SETUP))
return;

temp = container_of(urb->transfer_buffer, struct temp_buffer, data);

urb->setup_packet = temp->old_buffer;
kfree(temp->kmalloc_ptr);

urb->transfer_flags &= ~URB_ALIGNED_TEMP_SETUP;
}

static int sunxi_hcd_alloc_temp_setup(struct urb *urb, gfp_t mem_flags)
{
struct temp_buffer *temp;

if (!usb_endpoint_xfer_control(&urb->ep->desc))
return 0;

/* sunxi hardware requires setup packet to be DMA aligned */
if (!((uintptr_t)urb->setup_packet & (SUNXI_USB_DMA_ALIGN - 1)))
return 0;

/* Allocate a buffer with enough padding for alignment */
temp = alloc_temp_buffer(sizeof(struct usb_ctrlrequest), mem_flags);
if (!temp)
return -ENOMEM;

temp->old_buffer = urb->setup_packet;
memcpy(temp->data, urb->setup_packet, sizeof(struct usb_ctrlrequest));
urb->setup_packet = temp->data;

urb->transfer_flags |= URB_ALIGNED_TEMP_SETUP;

return 0;
}

int sunxi_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
gfp_t mem_flags)
{
int ret;

ret = sunxi_hcd_alloc_temp_buffer(urb, mem_flags);
if (ret)
return ret;

ret = sunxi_hcd_alloc_temp_setup(urb, mem_flags);
if (ret) {
sunxi_hcd_free_temp_buffer(urb);
return ret;
}

ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
if (ret) {
sunxi_hcd_free_temp_setup(urb);
sunxi_hcd_free_temp_buffer(urb);
return ret;
}

return ret;
}
EXPORT_SYMBOL_GPL(sunxi_hcd_map_urb_for_dma);

void sunxi_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
{
usb_hcd_unmap_urb_for_dma(hcd, urb);
sunxi_hcd_free_temp_setup(urb);
sunxi_hcd_free_temp_buffer(urb);
}
EXPORT_SYMBOL_GPL(sunxi_hcd_unmap_urb_for_dma);

/*
*---------------------------------------------------------------
* EHCI
Expand Down
7 changes: 7 additions & 0 deletions drivers/usb/host/sw_hci_sunxi.h
Expand Up @@ -33,6 +33,9 @@
#include <linux/io.h>
#include <linux/irq.h>

#include <linux/usb.h>
#include <linux/usb/hcd.h>

#define DMSG_PRINT(stuff...) printk(stuff)
#define DMSG_ERR(...) \
(DMSG_PRINT("WRN:L%d(%s):", __LINE__, __FILE__), \
Expand Down Expand Up @@ -198,4 +201,8 @@ struct sw_hci_hcd {
void (*usb_passby) (struct sw_hci_hcd *sw_hci, u32 enable);
};

extern int sunxi_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
gfp_t mem_flags);
extern void sunxi_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb);

#endif /* __SW_HCI_SUNXI_H__ */
1 change: 1 addition & 0 deletions include/linux/usb.h
Expand Up @@ -1039,6 +1039,7 @@ extern int usb_disabled(void);
#define URB_SETUP_MAP_LOCAL 0x00200000 /* HCD-local setup packet */
#define URB_DMA_SG_COMBINED 0x00400000 /* S-G entries were combined */
#define URB_ALIGNED_TEMP_BUFFER 0x00800000 /* Temp buffer was alloc'd */
#define URB_ALIGNED_TEMP_SETUP 0x01000000 /* Temp setup packet alloc'd */

struct usb_iso_packet_descriptor {
unsigned int offset;
Expand Down

0 comments on commit 0253717

Please sign in to comment.