forked from Minki/linux
077af794d9
The old URL for usb-storage driver help is long gone. So remove it from the comments to not confuse people anymore. Reported-by: Matthew Dharm <mdharm-usb@one-eyed-alien.net> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
182 lines
5.2 KiB
C
182 lines
5.2 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Driver for USB Mass Storage compliant devices
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/highmem.h>
|
|
#include <linux/export.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_pad12_command(struct scsi_cmnd *srb, struct us_data *us)
|
|
{
|
|
/*
|
|
* Pad the SCSI command with zeros out to 12 bytes. If the
|
|
* command already is 12 bytes or longer, leave it alone.
|
|
*
|
|
* 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;
|
|
|
|
/* 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);
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_stor_transparent_scsi_command);
|
|
|
|
/***********************************************************************
|
|
* Scatter-gather transfer buffer access routines
|
|
***********************************************************************/
|
|
|
|
/*
|
|
* Copy a buffer of length buflen to/from the srb's transfer buffer.
|
|
* Update the **sgptr 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, struct scatterlist **sgptr,
|
|
unsigned int *offset, enum xfer_buf_dir dir)
|
|
{
|
|
unsigned int cnt = 0;
|
|
struct scatterlist *sg = *sgptr;
|
|
struct sg_mapping_iter miter;
|
|
unsigned int nents = scsi_sg_count(srb);
|
|
|
|
if (sg)
|
|
nents = sg_nents(sg);
|
|
else
|
|
sg = scsi_sglist(srb);
|
|
|
|
sg_miter_start(&miter, sg, nents, dir == FROM_XFER_BUF ?
|
|
SG_MITER_FROM_SG: SG_MITER_TO_SG);
|
|
|
|
if (!sg_miter_skip(&miter, *offset))
|
|
return cnt;
|
|
|
|
while (sg_miter_next(&miter) && cnt < buflen) {
|
|
unsigned int len = min_t(unsigned int, miter.length,
|
|
buflen - cnt);
|
|
|
|
if (dir == FROM_XFER_BUF)
|
|
memcpy(buffer + cnt, miter.addr, len);
|
|
else
|
|
memcpy(miter.addr, buffer + cnt, len);
|
|
|
|
if (*offset + len < miter.piter.sg->length) {
|
|
*offset += len;
|
|
*sgptr = miter.piter.sg;
|
|
} else {
|
|
*offset = 0;
|
|
*sgptr = sg_next(miter.piter.sg);
|
|
}
|
|
cnt += len;
|
|
}
|
|
sg_miter_stop(&miter);
|
|
|
|
return cnt;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_stor_access_xfer_buf);
|
|
|
|
/*
|
|
* 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 offset = 0;
|
|
struct scatterlist *sg = NULL;
|
|
|
|
buflen = min(buflen, scsi_bufflen(srb));
|
|
buflen = usb_stor_access_xfer_buf(buffer, buflen, srb, &sg, &offset,
|
|
TO_XFER_BUF);
|
|
if (buflen < scsi_bufflen(srb))
|
|
scsi_set_resid(srb, scsi_bufflen(srb) - buflen);
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_stor_set_xfer_buf);
|