diff options
Diffstat (limited to 'drivers/usb/storage/protocol.c')
-rw-r--r-- | drivers/usb/storage/protocol.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/drivers/usb/storage/protocol.c b/drivers/usb/storage/protocol.c new file mode 100644 index 000000000000..9ad30428d2dd --- /dev/null +++ b/drivers/usb/storage/protocol.c @@ -0,0 +1,254 @@ +/* Driver for USB Mass Storage compliant devices + * + * $Id: protocol.c,v 1.14 2002/04/22 03:39:43 mdharm Exp $ + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2000 David L. Brown, Jr. (usb-storage@davidb.org) + * (c) 2002 Alan Stern (stern@rowland.org) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/highmem.h> +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> + +#include "usb.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" +#include "transport.h" + +/*********************************************************************** + * Protocol routines + ***********************************************************************/ + +void usb_stor_qic157_command(struct scsi_cmnd *srb, struct us_data *us) +{ + /* Pad the ATAPI command with zeros + * + * NOTE: This only works because a scsi_cmnd struct field contains + * a unsigned char cmnd[16], so we know we have storage available + */ + for (; srb->cmd_len<12; srb->cmd_len++) + srb->cmnd[srb->cmd_len] = 0; + + /* set command length to 12 bytes */ + srb->cmd_len = 12; + + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} + +void usb_stor_ATAPI_command(struct scsi_cmnd *srb, struct us_data *us) +{ + /* Pad the ATAPI command with zeros + * + * NOTE: This only works because a scsi_cmnd struct field contains + * a unsigned char cmnd[16], so we know we have storage available + */ + + /* Pad the ATAPI command with zeros */ + for (; srb->cmd_len<12; srb->cmd_len++) + srb->cmnd[srb->cmd_len] = 0; + + /* set command length to 12 bytes */ + srb->cmd_len = 12; + + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} + + +void usb_stor_ufi_command(struct scsi_cmnd *srb, struct us_data *us) +{ + /* fix some commands -- this is a form of mode translation + * UFI devices only accept 12 byte long commands + * + * NOTE: This only works because a scsi_cmnd struct field contains + * a unsigned char cmnd[16], so we know we have storage available + */ + + /* Pad the ATAPI command with zeros */ + for (; srb->cmd_len<12; srb->cmd_len++) + srb->cmnd[srb->cmd_len] = 0; + + /* set command length to 12 bytes (this affects the transport layer) */ + srb->cmd_len = 12; + + /* XXX We should be constantly re-evaluating the need for these */ + + /* determine the correct data length for these commands */ + switch (srb->cmnd[0]) { + + /* for INQUIRY, UFI devices only ever return 36 bytes */ + case INQUIRY: + srb->cmnd[4] = 36; + break; + + /* again, for MODE_SENSE_10, we get the minimum (8) */ + case MODE_SENSE_10: + srb->cmnd[7] = 0; + srb->cmnd[8] = 8; + break; + + /* for REQUEST_SENSE, UFI devices only ever return 18 bytes */ + case REQUEST_SENSE: + srb->cmnd[4] = 18; + break; + } /* end switch on cmnd[0] */ + + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} + +void usb_stor_transparent_scsi_command(struct scsi_cmnd *srb, + struct us_data *us) +{ + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} + +/*********************************************************************** + * Scatter-gather transfer buffer access routines + ***********************************************************************/ + +/* Copy a buffer of length buflen to/from the srb's transfer buffer. + * (Note: for scatter-gather transfers (srb->use_sg > 0), srb->request_buffer + * points to a list of s-g entries and we ignore srb->request_bufflen. + * For non-scatter-gather transfers, srb->request_buffer points to the + * transfer buffer itself and srb->request_bufflen is the buffer's length.) + * 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, struct scsi_cmnd *srb, unsigned int *index, + unsigned int *offset, enum xfer_buf_dir dir) +{ + unsigned int cnt; + + /* If not using scatter-gather, just transfer the data directly. + * Make certain it will fit in the available buffer space. */ + 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; + + /* Using scatter-gather. We have to go through the list one entry + * at a time. Each s-g entry contains some number of pages, and + * each page has to be kmap()'ed separately. If the page is already + * in kernel-addressable memory then kmap() will return its address. + * If the page is not directly accessible -- such as a user buffer + * located in high memory -- then kmap() will map it to a temporary + * position in the kernel's virtual address space. */ + } else { + struct scatterlist *sg = + (struct scatterlist *) srb->request_buffer + + *index; + + /* This loop handles a single s-g list entry, which may + * include multiple pages. Find the initial page structure + * and the starting offset within the page, and update + * the *offset and *index values for the next loop. */ + 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) { + + /* Transfer ends within this s-g entry */ + sglen = buflen - cnt; + *offset += sglen; + } else { + + /* Transfer continues to next s-g entry */ + *offset = 0; + ++*index; + ++sg; + } + + /* Transfer the data for all the pages in this + * s-g entry. For each page: call kmap(), do the + * transfer, and call kunmap() immediately after. */ + 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); + + /* Start at the beginning of the next page */ + poff = 0; + ++page; + cnt += plen; + sglen -= plen; + } + } + } + + /* Return the amount actually transferred */ + return cnt; +} + +/* Store the contents of buffer into srb's transfer buffer and set the + * SCSI residue. */ +void usb_stor_set_xfer_buf(unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb) +{ + unsigned int index = 0, offset = 0; + + usb_stor_access_xfer_buf(buffer, buflen, srb, &index, &offset, + TO_XFER_BUF); + if (buflen < srb->request_bufflen) + srb->resid = srb->request_bufflen - buflen; +} |