]> git.hungrycats.org Git - linux/commitdiff
[PATCH] USB storage: Fix scatter-gather buffer access in usb-storage core
authorAlan Stern <stern@rowland.harvard.edu>
Mon, 8 Dec 2003 09:45:21 +0000 (01:45 -0800)
committerGreg Kroah-Hartman <greg@kroah.com>
Mon, 8 Dec 2003 09:45:21 +0000 (01:45 -0800)
This patch adds a routine to protocol.c that correctly transfers data to
or from a scatter-gather buffer.  According to Jens Axboe, we've been
using page_address() incorrectly -- it's necessary to use kmap() instead
-- and in fact it doesn't give the desired result when the buffers are
located in high memory.  This could affect anyone using a system with 1 GB
or more of RAM, and one user has already reported such a problem (as you
know).

The three fixup routines in protocol.c and usb.c have been changed to use
the new s-g access routine.  When similar adjustments have been made to
all the subdrivers, we will be able to eliminate the raw_bulk.c source
file entirely.

drivers/usb/storage/protocol.c
drivers/usb/storage/protocol.h
drivers/usb/storage/usb.c

index a6082b08fad5dc955645155f587043393570aff7..c134913b5bd0b4fa18af033c998a6dc587faff7e 100644 (file)
@@ -44,6 +44,7 @@
  * 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+#include <linux/highmem.h>
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
  * Helper routines
  ***********************************************************************/
 
-static void *
-find_data_location(Scsi_Cmnd *srb) {
-       if (srb->use_sg) {
-               /*
-                * This piece of code only works if the first page is
-                * big enough to hold more than 3 bytes -- which is
-                * _very_ likely.
-                */
-               struct scatterlist *sg;
-
-               sg = (struct scatterlist *) srb->request_buffer;
-               return (void *) sg_address(sg[0]);
-       } else
-               return (void *) srb->request_buffer;
-}
-
 /*
  * Fix-up the return data from an INQUIRY command to show 
  * ANSI SCSI rev 2 so we don't confuse the SCSI layers above us
  */
 static void fix_inquiry_data(Scsi_Cmnd *srb)
 {
-       unsigned char *data_ptr;
+       unsigned char databuf[3];
+       unsigned int index, offset;
 
        /* verify that it's an INQUIRY command */
        if (srb->cmnd[0] != INQUIRY)
                return;
 
-       /* oddly short buffer -- bail out */
-       if (srb->request_bufflen < 3)
+       index = offset = 0;
+       if (usb_stor_access_xfer_buf(databuf, sizeof(databuf), srb,
+                       &index, &offset, FROM_XFER_BUF) != sizeof(databuf))
                return;
 
-       data_ptr = find_data_location(srb);
-
-       if ((data_ptr[2] & 7) == 2)
+       if ((databuf[2] & 7) == 2)
                return;
 
        US_DEBUGP("Fixing INQUIRY data to show SCSI rev 2 - was %d\n",
-                 data_ptr[2] & 7);
+                 databuf[2] & 7);
 
        /* Change the SCSI revision number */
-       data_ptr[2] = (data_ptr[2] & ~7) | 2;
+       databuf[2] = (databuf[2] & ~7) | 2;
+
+       index = offset = 0;
+       usb_stor_access_xfer_buf(databuf, sizeof(databuf), srb,
+                       &index, &offset, TO_XFER_BUF);
 }
 
 /*
@@ -104,23 +93,27 @@ static void fix_inquiry_data(Scsi_Cmnd *srb)
  */
 static void fix_read_capacity(Scsi_Cmnd *srb)
 {
-       unsigned char *dp;
+       unsigned int index, offset;
+       u32 c;
        unsigned long capacity;
 
        /* verify that it's a READ CAPACITY command */
        if (srb->cmnd[0] != READ_CAPACITY)
                return;
 
-       dp = find_data_location(srb);
+       index = offset = 0;
+       if (usb_stor_access_xfer_buf((unsigned char *) &c, 4, srb,
+                       &index, &offset, FROM_XFER_BUF) != 4)
+               return;
 
-       capacity = (dp[0]<<24) + (dp[1]<<16) + (dp[2]<<8) + (dp[3]);
+       capacity = be32_to_cpu(c);
        US_DEBUGP("US: Fixing capacity: from %ld to %ld\n",
               capacity+1, capacity);
-       capacity--;
-       dp[0] = (capacity >> 24);
-       dp[1] = (capacity >> 16);
-       dp[2] = (capacity >> 8);
-       dp[3] = (capacity);
+       c = cpu_to_be32(capacity - 1);
+
+       index = offset = 0;
+       usb_stor_access_xfer_buf((unsigned char *) &c, 4, srb,
+                       &index, &offset, TO_XFER_BUF);
 }
 
 /***********************************************************************
@@ -234,3 +227,71 @@ void usb_stor_transparent_scsi_command(Scsi_Cmnd *srb, struct us_data *us)
                        fix_read_capacity(srb);
        }
 }
+
+/***********************************************************************
+ * Scatter-gather transfer buffer access routines
+ ***********************************************************************/
+
+/* Copy a buffer of length buflen to/from the srb's transfer buffer.
+ * Update the index and offset variables so that the next copy will
+ * pick up from where this one left off. */
+
+unsigned int usb_stor_access_xfer_buf(unsigned char *buffer,
+       unsigned int buflen, Scsi_Cmnd *srb, unsigned int *index,
+       unsigned int *offset, enum xfer_buf_dir dir)
+{
+       unsigned int cnt;
+
+       if (srb->use_sg == 0) {
+               if (*offset >= srb->request_bufflen)
+                       return 0;
+               cnt = min(buflen, srb->request_bufflen - *offset);
+               if (dir == TO_XFER_BUF)
+                       memcpy((unsigned char *) srb->request_buffer + *offset,
+                                       buffer, cnt);
+               else
+                       memcpy(buffer, (unsigned char *) srb->request_buffer +
+                                       *offset, cnt);
+               *offset += cnt;
+
+       } else {
+               struct scatterlist *sg =
+                               (struct scatterlist *) srb->request_buffer
+                               + *index;
+
+               cnt = 0;
+               while (cnt < buflen && *index < srb->use_sg) {
+                       struct page *page = sg->page +
+                                       ((sg->offset + *offset) >> PAGE_SHIFT);
+                       unsigned int poff =
+                                       (sg->offset + *offset) & (PAGE_SIZE-1);
+                       unsigned int sglen = sg->length - *offset;
+
+                       if (sglen > buflen - cnt) {
+                               sglen = buflen - cnt;
+                               *offset += sglen;
+                       } else {
+                               *offset = 0;
+                               ++sg;
+                               ++*index;
+                       }
+
+                       while (sglen > 0) {
+                               unsigned int plen = min(sglen, (unsigned int)
+                                               PAGE_SIZE - poff);
+                               unsigned char *ptr = kmap(page);
+
+                               if (dir == TO_XFER_BUF)
+                                       memcpy(ptr + poff, buffer + cnt, plen);
+                               else
+                                       memcpy(buffer + cnt, ptr + poff, plen);
+                               kunmap(page);
+                               poff = 0;
+                               ++page;
+                               cnt += plen;
+                               sglen -= plen;
+                       }
+               }
+       }
+       return cnt;
+}
index 8b5ecbe3f039d851b0af7882ecb698a6c87612ad..b2c0b224d37acd0d4456aebfa2888fa610809b31 100644 (file)
 
 #define US_SC_DEVICE   0xff            /* Use device's value */
 
+/* Protocol handling routines */
 extern void usb_stor_ATAPI_command(Scsi_Cmnd*, struct us_data*);
 extern void usb_stor_qic157_command(Scsi_Cmnd*, struct us_data*);
 extern void usb_stor_ufi_command(Scsi_Cmnd*, struct us_data*);
 extern void usb_stor_transparent_scsi_command(Scsi_Cmnd*, struct us_data*);
 
+/* Scsi_Cmnd transfer buffer access utilities */
+enum xfer_buf_dir      {TO_XFER_BUF, FROM_XFER_BUF};
+
+extern unsigned int usb_stor_access_xfer_buf(unsigned char *buffer,
+       unsigned int buflen, Scsi_Cmnd *srb, unsigned int *index,
+       unsigned int *offset, enum xfer_buf_dir dir);
+
 #endif
index fdec517bc3c53038d1354edb6fe42e9e92d7469f..639950ec8afd8768ba6b3e89ad6693f7100730f7 100644 (file)
@@ -234,15 +234,9 @@ struct usb_driver usb_storage_driver = {
  */
 
 void fill_inquiry_response(struct us_data *us, unsigned char *data,
-               unsigned int data_len) {
-
-       int i;
-       struct scatterlist *sg;
-       int len =
-               us->srb->request_bufflen > data_len ? data_len :
-               us->srb->request_bufflen;
-       int transferred;
-       int amt;
+               unsigned int data_len)
+{
+       unsigned int index, offset;
 
        if (data_len<36) // You lose.
                return;
@@ -270,22 +264,11 @@ void fill_inquiry_response(struct us_data *us, unsigned char *data,
                data[35] = 0x30 + ((us->pusb_dev->descriptor.bcdDevice) & 0x0F);
        }
 
-       if (us->srb->use_sg) {
-               sg = (struct scatterlist *)us->srb->request_buffer;
-               for (i=0; i<us->srb->use_sg; i++)
-                       memset(sg_address(sg[i]), 0, sg[i].length);
-               for (i=0, transferred=0; 
-                               i<us->srb->use_sg && transferred < len;
-                               i++) {
-                       amt = sg[i].length > len-transferred ? 
-                                       len-transferred : sg[i].length;
-                       memcpy(sg_address(sg[i]), data+transferred, amt);
-                       transferred -= amt;
-               }
-       } else {
-               memset(us->srb->request_buffer, 0, us->srb->request_bufflen);
-               memcpy(us->srb->request_buffer, data, len);
-       }
+       index = offset = 0;
+       usb_stor_access_xfer_buf(data, data_len, us->srb,
+                       &index, &offset, TO_XFER_BUF);
+       if (data_len < us->srb->request_bufflen)
+               us->srb->resid = us->srb->request_bufflen - data_len;
 }
 
 static int usb_stor_control_thread(void * __us)