diff options
Diffstat (limited to 'drivers/usb/storage/sddr09.c')
-rw-r--r-- | drivers/usb/storage/sddr09.c | 534 |
1 files changed, 412 insertions, 122 deletions
diff --git a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c index 275712961..6bf9f1477 100644 --- a/drivers/usb/storage/sddr09.c +++ b/drivers/usb/storage/sddr09.c @@ -44,7 +44,7 @@ extern int usb_stor_control_msg(struct us_data *us, unsigned int pipe, extern int usb_stor_bulk_msg(struct us_data *us, void *data, int pipe, unsigned int len, unsigned int *act_len); -#define short_pack(b1,b2) ( ((u16)(b1)) | ( ((u16)(b2))<<8 ) ) +#define short_pack(lsb,msb) ( ((u16)(lsb)) | ( ((u16)(msb))<<8 ) ) #define LSB_of(s) ((s)&0xFF) #define MSB_of(s) ((s)>>8) @@ -132,7 +132,7 @@ static int sddr09_send_control(struct us_data *us, static int sddr09_raw_bulk(struct us_data *us, int direction, unsigned char *data, - unsigned short len) { + unsigned int len) { int result; int act_len; @@ -196,64 +196,38 @@ static int sddr09_raw_bulk(struct us_data *us, */ static int sddr09_bulk_transport(struct us_data *us, - unsigned char *command, - unsigned short command_len, int direction, unsigned char *data, - unsigned short len, + unsigned int len, int use_sg) { int result = USB_STOR_TRANSPORT_GOOD; int transferred = 0; - unsigned char execute[8] = { - 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; int i; struct scatterlist *sg; char string[64]; -/* - if (command_len != 0) { - - // Fix up the command's data length - command[6] = len&0xFF; - command[7] = (len>>8)&0xFF; - - result = sddr09_send_control(us, - execute, - command, - command_len); - - if (result != USB_STOR_TRANSPORT_GOOD) - return result; - } -*/ if (len==0) return USB_STOR_TRANSPORT_GOOD; - /* transfer the data payload for the command, if there is any */ - - - if (command_len != 0) - direction = (command[0]&0x80) ? SCSI_DATA_READ : - SCSI_DATA_WRITE; + /* transfer the data */ if (direction == SCSI_DATA_WRITE) { /* Debug-print the first 48 bytes of the write transfer */ if (!use_sg) { - string[0] = 0; + strcpy(string, "wr: "); for (i=0; i<len && i<48; i++) { sprintf(string+strlen(string), "%02X ", data[i]); if ((i%16)==15) { US_DEBUGP("%s\n", string); - string[0] = 0; + strcpy(string, "wr: "); } } - if (string[0]!=0) + if ((i%16)!=0) US_DEBUGP("%s\n", string); } } @@ -278,6 +252,25 @@ static int sddr09_bulk_transport(struct us_data *us, } } + if (direction == SCSI_DATA_READ) { + + /* Debug-print the first 48 bytes of the read transfer */ + + if (!use_sg) { + strcpy(string, "rd: "); + for (i=0; i<len && i<48; i++) { + sprintf(string+strlen(string), "%02X ", + data[i]); + if ((i%16)==15) { + US_DEBUGP("%s\n", string); + strcpy(string, "rd: "); + } + } + if ((i%16)!=0) + US_DEBUGP("%s\n", string); + } + } + return result; } @@ -289,45 +282,142 @@ int sddr09_read_data(struct us_data *us, int result; unsigned char command[12] = { - 0xe8, 0x20, MSB_of(address>>16), - LSB_of(address>>16), MSB_of(address&0xFFFF), - LSB_of(address&0xFFFF), 0, 0, 0, 0, - MSB_of(sectors), LSB_of(sectors) + 0xe8, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + struct sddr09_card_info *info = (struct sddr09_card_info *)us->extra; + unsigned int lba; + unsigned int pba; + unsigned short page; + unsigned short pages; + unsigned char *buffer = NULL; + unsigned char *ptr; + struct scatterlist *sg = NULL; + int i; + int len; + int transferred; - result = sddr09_send_control(us, - usb_sndctrlpipe(us->pusb_dev,0), - 0, - 0x41, - 0, - 0, - command, - 12); + // If we're using scatter-gather, we have to create a new + // buffer to read all of the data in first, since a + // scatter-gather buffer could in theory start in the middle + // of a page, which would be bad. A developer who wants a + // challenge might want to write a limited-buffer + // version of this code. + + len = sectors*info->pagesize; + + if (use_sg) { + sg = (struct scatterlist *)content; + buffer = kmalloc(len, GFP_KERNEL); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + ptr = buffer; + } else + ptr = content; + + // Figure out the initial LBA and page + + pba = (address/info->pagesize)>>4; + lba = info->pba_to_lba[pba]; + page = (address/info->pagesize)&0x0F; + + // This could be made much more efficient by checking for + // contiguous LBA's. Another exercise left to the student. + + while (sectors>0) { + + pba = info->lba_to_pba[lba]; + + // Read as many sectors as possible in this block + + pages = 0x10-page; + if (pages > sectors) + pages = sectors; + + US_DEBUGP("Read %01X pages, from PBA %04X" + " (LBA %04X) page %01X\n", + pages, pba, lba, page); + + address = ((pba<<4)+page)*info->pagesize; + + // Unlike in the documentation, the address is in + // words of 2 bytes. + + command[2] = MSB_of(address>>17); + command[3] = LSB_of(address>>17); + command[4] = MSB_of((address>>1)&0xFFFF); + command[5] = LSB_of((address>>1)&0xFFFF); + + command[10] = MSB_of(pages); + command[11] = LSB_of(pages); + + result = sddr09_send_control(us, + usb_sndctrlpipe(us->pusb_dev,0), + 0, + 0x41, + 0, + 0, + command, + 12); - if (result != USB_STOR_TRANSPORT_GOOD) - return result; + US_DEBUGP("Result for send_control in read_data %d\n", + result); + + if (result != USB_STOR_TRANSPORT_GOOD) { + if (use_sg) + kfree(buffer); + return result; + } - result = sddr09_bulk_transport(us, - NULL, 0, SCSI_DATA_READ, content, - sectors*512, use_sg); + result = sddr09_bulk_transport(us, + SCSI_DATA_READ, ptr, + pages*info->pagesize, 0); - return result; + if (result != USB_STOR_TRANSPORT_GOOD) { + if (use_sg) + kfree(buffer); + return result; + } + + page = 0; + lba++; + sectors -= pages; + ptr += pages*info->pagesize; + } + + if (use_sg) { + transferred = 0; + for (i=0; i<use_sg && transferred<len; i++) { + memcpy(sg[i].address, buffer+transferred, + len-transferred > sg[i].length ? + sg[i].length : len-transferred); + transferred += sg[i].length; + } + kfree(buffer); + } + + return USB_STOR_TRANSPORT_GOOD; } int sddr09_read_control(struct us_data *us, unsigned long address, - unsigned short sectors, + unsigned short blocks, unsigned char *content, int use_sg) { + // Unlike in the documentation, the last two bytes are the + // number of blocks, not sectors. + int result; unsigned char command[12] = { 0xe8, 0x21, MSB_of(address>>16), LSB_of(address>>16), MSB_of(address&0xFFFF), LSB_of(address&0xFFFF), 0, 0, 0, 0, - MSB_of(sectors), LSB_of(sectors) + MSB_of(blocks), LSB_of(blocks) }; + US_DEBUGP("Read control address %08X blocks %04X\n", + address, blocks); + result = sddr09_send_control(us, usb_sndctrlpipe(us->pusb_dev,0), 0, @@ -336,13 +426,19 @@ int sddr09_read_control(struct us_data *us, 0, command, 12); + + US_DEBUGP("Result for send_control in read_control %d\n", + result); if (result != USB_STOR_TRANSPORT_GOOD) return result; result = sddr09_bulk_transport(us, - NULL, 0, SCSI_DATA_READ, content, - sectors*64, use_sg); + SCSI_DATA_READ, content, + blocks*0x40, use_sg); + + US_DEBUGP("Result for bulk read in read_control %d\n", + result); return result; } @@ -373,7 +469,7 @@ int sddr09_read_deviceID(struct us_data *us, return result; result = sddr09_bulk_transport(us, - NULL, 0, SCSI_DATA_READ, content, + SCSI_DATA_READ, content, 64, 0); *manufacturerID = content[0]; @@ -389,7 +485,6 @@ int sddr09_read_status(struct us_data *us, unsigned char command[12] = { 0xec, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - unsigned char content[2]; result = sddr09_send_control(us, usb_sndctrlpipe(us->pusb_dev,0), @@ -404,7 +499,7 @@ int sddr09_read_status(struct us_data *us, return result; result = sddr09_bulk_transport(us, - NULL, 0, SCSI_DATA_READ, status, + SCSI_DATA_READ, status, 1, 0); return result; @@ -429,6 +524,154 @@ int sddr09_reset(struct us_data *us) { return result; } +unsigned long sddr09_get_capacity(struct us_data *us, + unsigned int *pagesize) { + + unsigned char manufacturerID; + unsigned char deviceID; + int result; + + US_DEBUGP("Reading capacity...\n"); + + result = sddr09_read_deviceID(us, + &manufacturerID, + &deviceID); + + US_DEBUGP("Result of read_deviceID is %d\n", + result); + + if (result != USB_STOR_TRANSPORT_GOOD) + return 0; + + US_DEBUGP("Device ID = %02X\n", deviceID); + US_DEBUGP("Manuf ID = %02X\n", manufacturerID); + + *pagesize = 512; + + switch (deviceID) { + + case 0x6e: // 1MB + case 0xe8: + case 0xec: + *pagesize = 256; + return 0x00100000; + + case 0x5d: // 2MB + case 0xea: + case 0x64: + if (deviceID!=0x5D) + *pagesize = 256; + return 0x00200000; + + case 0xe3: // 4MB + case 0xe5: + case 0x6b: + case 0xd5: + return 0x00400000; + + case 0xe6: // 8MB + case 0xd6: + return 0x00800000; + + case 0x73: // 16MB + return 0x01000000; + + case 0x75: // 32MB + return 0x02000000; + + default: // unknown + return 0; + + } +} + +int sddr09_read_map(struct us_data *us) { + + unsigned char *control; + struct sddr09_card_info *info = (struct sddr09_card_info *)(us->extra); + int numblocks; + int i; + unsigned char *ptr; + unsigned short lba; + unsigned char parity; + unsigned char fast_parity[16] = { + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0 + }; + int result; + + if (!info->capacity) + return -1; + + /* read 64 (2^6) bytes for every block (8192 (2^13) bytes) + of capacity: + 64*(capacity/8192) = capacity*(2^6)*(2^-13) = + capacity*2^(6-13) = capacity*(2^-7) + */ + + control = kmalloc(info->capacity>>7, GFP_KERNEL); + + + numblocks = info->capacity>>13; + + if ( (result = sddr09_read_control(us, 0, numblocks, + control, 0)) != + USB_STOR_TRANSPORT_GOOD) { + kfree(control); + return -1; + } + + + + if (info->lba_to_pba) + kfree(info->lba_to_pba); + if (info->pba_to_lba) + kfree(info->pba_to_lba); + info->lba_to_pba = kmalloc(numblocks*sizeof(int), GFP_KERNEL); + info->pba_to_lba = kmalloc(numblocks*sizeof(int), GFP_KERNEL); + memset(info->lba_to_pba, 0, numblocks*sizeof(int)); + memset(info->pba_to_lba, 0, numblocks*sizeof(int)); + + for (i=0; i<numblocks; i++) { + ptr = control+64*i; + if (ptr[0]!=0xFF || ptr[1]!=0xFF || ptr[2]!=0xFF || + ptr[3]!=0xFF || ptr[4]!=0xFF || ptr[5]!=0xFF) + continue; + if ((ptr[6]>>4)!=0x01) + continue; + + /* ensure even parity */ + + lba = short_pack(ptr[7], ptr[6]); + parity = 1; // the parity of 0x1000 + parity ^= fast_parity[lba & 0x000F]; + parity ^= fast_parity[(lba>>4) & 0x000F]; + parity ^= fast_parity[(lba>>8) & 0x000F]; + + if (parity) { /* bad parity bit */ + US_DEBUGP("Bad parity in LBA for block %04X\n", i); + continue; + } + + lba = (lba&0x07FF)>>1; + + if (lba>=numblocks) { + US_DEBUGP("Bad LBA %04X for block %04X\n", lba, i); + continue; + } + + if (i<0x10) + US_DEBUGP("LBA %04X <-> PBA %04X\n", + lba, i); + + info->pba_to_lba[i] = lba; + info->lba_to_pba[lba] = i; + } + + kfree(control); + return 0; +} + /* static int init_sddr09(struct us_data *us) { @@ -477,15 +720,24 @@ static int init_sddr09(struct us_data *us) { } */ +void sddr09_card_info_destructor(void *extra) { + struct sddr09_card_info *info = (struct sddr09_card_info *)extra; + + if (!extra) + return; + + if (info->lba_to_pba) + kfree(info->lba_to_pba); + if (info->pba_to_lba) + kfree(info->pba_to_lba); +} + /* * Transport for the Sandisk SDDR-09 */ int sddr09_transport(Scsi_Cmnd *srb, struct us_data *us) { int result; - unsigned char send_scsi_command[8] = { - 0x41, 0, 0, 0, 0, 0, 0, 0 - }; int i; char string[64]; unsigned char inquiry_response[36] = { @@ -495,9 +747,16 @@ int sddr09_transport(Scsi_Cmnd *srb, struct us_data *us) 'e', ' ', 'S', 'D', 'D', 'R', '0', '9', ' ', ' ', ' ', ' ' }; - unsigned char deviceID; - unsigned char manufacturerID; + unsigned char mode_page_01[12] = { + 0x01, 0x0a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; unsigned char *ptr; + unsigned long capacity; + unsigned int lba; + unsigned int pba; + unsigned int page; + unsigned short pages; + struct sddr09_card_info *info = (struct sddr09_card_info *)(us->extra); /* if (us->flags & US_FL_NEED_INIT) { @@ -507,85 +766,116 @@ int sddr09_transport(Scsi_Cmnd *srb, struct us_data *us) } */ + if (!us->extra) { + us->extra = kmalloc( + sizeof(struct sddr09_card_info), GFP_KERNEL); + memset(us->extra, 0, sizeof(struct sddr09_card_info)); + us->extra_destructor = sddr09_card_info_destructor; + } + ptr = (unsigned char *)srb->request_buffer; /* Dummy up a response for INQUIRY since SDDR09 doesn't respond to INQUIRY commands */ if (srb->cmnd[0] == INQUIRY) { - memcpy(srb->request_buffer, inquiry_response, 36); + memcpy(ptr, inquiry_response, 36); return USB_STOR_TRANSPORT_GOOD; } if (srb->cmnd[0] == READ_CAPACITY) { - US_DEBUGP("Reading capacity...\n"); + capacity = sddr09_get_capacity(us, &info->pagesize); + info->capacity = capacity; - result = sddr09_read_deviceID(us, - &manufacturerID, - &deviceID); + // Last page in the card - US_DEBUGP("Result of read_deviceID is %d\n", - result); + capacity /= info->pagesize; + capacity--; - if (result != USB_STOR_TRANSPORT_GOOD) - return result; + ptr[0] = MSB_of(capacity>>16); + ptr[1] = LSB_of(capacity>>16); + ptr[2] = MSB_of(capacity&0xFFFF); + ptr[3] = LSB_of(capacity&0xFFFF); - US_DEBUGP("Device ID = %02X\n", deviceID); - US_DEBUGP("Manuf ID = %02X\n", manufacturerID); - - ptr[0] = 0; - ptr[1] = 0; - ptr[2] = 0; - ptr[3] = 0; - - switch (deviceID) { - - case 0x6e: // 1MB - case 0xe8: - case 0xec: - ptr[4] = 0; - ptr[5] = 0x10; - break; - - case 0xea: // 2MB - case 0x64: - case 0x5d: - ptr[4] = 0; - ptr[5] = 0x20; - break; - - case 0xe3: // 4MB - case 0xe5: - case 0x6b: - case 0xd5: - ptr[4] = 0; - ptr[5] = 0x40; - break; - - case 0xe6: // 8MB - case 0xd6: - ptr[4] = 0; - ptr[5] = 0x80; - break; - - case 0x75: // 32MB - ptr[4] = 0x02; - ptr[5] = 0; - break; - - default: // unknown - ptr[4] = 0; - ptr[5] = 0; + // The page size - } + ptr[4] = MSB_of(info->pagesize>>16); + ptr[5] = LSB_of(info->pagesize>>16); + ptr[6] = MSB_of(info->pagesize&0xFFFF); + ptr[7] = LSB_of(info->pagesize&0xFFFF); - ptr[6] = 0; - ptr[7] = 0; + sddr09_read_map(us); return USB_STOR_TRANSPORT_GOOD; } + if (srb->cmnd[0] == MODE_SENSE) { + + // Read-write error recovery page: there needs to + // be a check for write-protect here + + if ( (srb->cmnd[2] & 0x3F) == 0x01 ) { + if (ptr==NULL || srb->request_bufflen<12) + return USB_STOR_TRANSPORT_ERROR; + memcpy(ptr, mode_page_01, 12); + return USB_STOR_TRANSPORT_GOOD; + } + + // FIXME: sense buffer? + + return USB_STOR_TRANSPORT_ERROR; + } + + if (srb->cmnd[0] == READ_10) { + + page = short_pack(srb->cmnd[3], srb->cmnd[2]); + page <<= 16; + page |= short_pack(srb->cmnd[5], srb->cmnd[4]); + pages = short_pack(srb->cmnd[8], srb->cmnd[7]); + + // convert page to block and page-within-block + + lba = page>>4; + page = page&0x0F; + + // locate physical block corresponding to logical block + + if (lba>=(info->capacity>>13)) { + + // FIXME: sense buffer? + + return USB_STOR_TRANSPORT_ERROR; + } + + pba = info->lba_to_pba[lba]; + + // if pba is 0, either it's really 0, in which case + // the pba-to-lba map for pba 0 will be the lba, + // or that lba doesn't exist. + + if (pba==0 && info->pba_to_lba[0] != lba) { + + // FIXME: sense buffer? + + return USB_STOR_TRANSPORT_ERROR; + } + + US_DEBUGP("READ_10: read block %04X (LBA %04X) page %01X" + " pages %d\n", + pba, lba, page, pages); + + return sddr09_read_data(us, + ((pba<<4)+page)*info->pagesize, pages, + ptr, srb->use_sg); + } + + // Pass TEST_UNIT_READY and REQUEST_SENSE through + + if (srb->cmnd[0] != TEST_UNIT_READY && + srb->cmnd[0] != REQUEST_SENSE) + return USB_STOR_TRANSPORT_ERROR; // FIXME: sense buffer? + for (; srb->cmd_len<12; srb->cmd_len++) srb->cmnd[srb->cmd_len] = 0; @@ -622,7 +912,7 @@ int sddr09_transport(Scsi_Cmnd *srb, struct us_data *us) srb->request_bufflen); result = sddr09_bulk_transport(us, - NULL, 0, srb->sc_data_direction, + srb->sc_data_direction, srb->request_buffer, srb->request_bufflen, srb->use_sg); |