Commit 03e7d40d authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge kroah.com:/home/greg/linux/BK/bleed-2.5

into kroah.com:/home/greg/linux/BK/gregkh-2.5
parents 947af82f c748be11
...@@ -1701,6 +1701,15 @@ static const struct driver_info zaurus_slb500_info = { ...@@ -1701,6 +1701,15 @@ static const struct driver_info zaurus_slb500_info = {
.in = 1, .out = 2, .in = 1, .out = 2,
}; };
static const struct driver_info zaurus_slc700_info = {
.description = "Sharp Zaurus SL-C700",
.flags = FLAG_FRAMING_Z,
.check_connect = always_connected,
.tx_fixup = zaurus_tx_fixup,
.in = 1, .out = 2,
};
// SL-5600 and C-700 are PXA based; should resemble A300 // SL-5600 and C-700 are PXA based; should resemble A300
...@@ -2751,6 +2760,15 @@ static const struct usb_device_id products [] = { ...@@ -2751,6 +2760,15 @@ static const struct usb_device_id products [] = {
.bInterfaceSubClass = 0x0a, .bInterfaceSubClass = 0x0a,
.bInterfaceProtocol = 0x00, .bInterfaceProtocol = 0x00,
.driver_info = (unsigned long) &zaurus_slb500_info, .driver_info = (unsigned long) &zaurus_slb500_info,
}, {
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO
| USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x04DD,
.idProduct = 0x8007,
.bInterfaceClass = 0x02,
.bInterfaceSubClass = 0x0a,
.bInterfaceProtocol = 0x00,
.driver_info = (unsigned long) &zaurus_slc700_info,
}, },
#endif #endif
......
...@@ -166,6 +166,10 @@ static int device_reset( Scsi_Cmnd *srb ) ...@@ -166,6 +166,10 @@ static int device_reset( Scsi_Cmnd *srb )
/* lock the device pointers and do the reset */ /* lock the device pointers and do the reset */
down(&(us->dev_semaphore)); down(&(us->dev_semaphore));
if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
result = FAILED;
US_DEBUGP("No reset during disconnect\n");
} else
result = us->transport_reset(us); result = us->transport_reset(us);
up(&(us->dev_semaphore)); up(&(us->dev_semaphore));
...@@ -202,7 +206,7 @@ static int bus_reset( Scsi_Cmnd *srb ) ...@@ -202,7 +206,7 @@ static int bus_reset( Scsi_Cmnd *srb )
down(&(us->dev_semaphore)); down(&(us->dev_semaphore));
if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
result = -EIO; result = -EIO;
US_DEBUGP("Attempt to reset during disconnect\n"); US_DEBUGP("No reset during disconnect\n");
} else if (us->pusb_dev->actconfig->desc.bNumInterfaces != 1) { } else if (us->pusb_dev->actconfig->desc.bNumInterfaces != 1) {
result = -EBUSY; result = -EBUSY;
US_DEBUGP("Refusing to reset a multi-interface device\n"); US_DEBUGP("Refusing to reset a multi-interface device\n");
......
...@@ -147,9 +147,19 @@ static int usb_stor_msg_common(struct us_data *us, int timeout) ...@@ -147,9 +147,19 @@ static int usb_stor_msg_common(struct us_data *us, int timeout)
us->current_urb->context = &urb_done; us->current_urb->context = &urb_done;
us->current_urb->actual_length = 0; us->current_urb->actual_length = 0;
us->current_urb->error_count = 0; us->current_urb->error_count = 0;
us->current_urb->transfer_flags = URB_ASYNC_UNLINK;
us->current_urb->status = 0; us->current_urb->status = 0;
/* we assume that if transfer_buffer isn't us->iobuf then it
* hasn't been mapped for DMA. Yes, this is clunky, but it's
* easier than always having the caller tell us whether the
* transfer buffer has already been mapped. */
us->current_urb->transfer_flags =
URB_ASYNC_UNLINK | URB_NO_SETUP_DMA_MAP;
if (us->current_urb->transfer_buffer == us->iobuf)
us->current_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
us->current_urb->transfer_dma = us->iobuf_dma;
us->current_urb->setup_dma = us->cr_dma;
/* submit the URB */ /* submit the URB */
status = usb_submit_urb(us->current_urb, GFP_NOIO); status = usb_submit_urb(us->current_urb, GFP_NOIO);
if (status) { if (status) {
...@@ -207,15 +217,15 @@ int usb_stor_control_msg(struct us_data *us, unsigned int pipe, ...@@ -207,15 +217,15 @@ int usb_stor_control_msg(struct us_data *us, unsigned int pipe,
value, index, size); value, index, size);
/* fill in the devrequest structure */ /* fill in the devrequest structure */
us->dr->bRequestType = requesttype; us->cr->bRequestType = requesttype;
us->dr->bRequest = request; us->cr->bRequest = request;
us->dr->wValue = cpu_to_le16(value); us->cr->wValue = cpu_to_le16(value);
us->dr->wIndex = cpu_to_le16(index); us->cr->wIndex = cpu_to_le16(index);
us->dr->wLength = cpu_to_le16(size); us->cr->wLength = cpu_to_le16(size);
/* fill and submit the URB */ /* fill and submit the URB */
usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe, usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe,
(unsigned char*) us->dr, data, size, (unsigned char*) us->cr, data, size,
usb_stor_blocking_completion, NULL); usb_stor_blocking_completion, NULL);
status = usb_stor_msg_common(us, timeout); status = usb_stor_msg_common(us, timeout);
...@@ -346,15 +356,15 @@ int usb_stor_ctrl_transfer(struct us_data *us, unsigned int pipe, ...@@ -346,15 +356,15 @@ int usb_stor_ctrl_transfer(struct us_data *us, unsigned int pipe,
value, index, size); value, index, size);
/* fill in the devrequest structure */ /* fill in the devrequest structure */
us->dr->bRequestType = requesttype; us->cr->bRequestType = requesttype;
us->dr->bRequest = request; us->cr->bRequest = request;
us->dr->wValue = cpu_to_le16(value); us->cr->wValue = cpu_to_le16(value);
us->dr->wIndex = cpu_to_le16(index); us->cr->wIndex = cpu_to_le16(index);
us->dr->wLength = cpu_to_le16(size); us->cr->wLength = cpu_to_le16(size);
/* fill and submit the URB */ /* fill and submit the URB */
usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe, usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe,
(unsigned char*) us->dr, data, size, (unsigned char*) us->cr, data, size,
usb_stor_blocking_completion, NULL); usb_stor_blocking_completion, NULL);
result = usb_stor_msg_common(us, 0); result = usb_stor_msg_common(us, 0);
...@@ -786,9 +796,9 @@ int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -786,9 +796,9 @@ int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us)
} }
/* STATUS STAGE */ /* STATUS STAGE */
result = usb_stor_intr_transfer(us, us->irqdata, sizeof(us->irqdata)); result = usb_stor_intr_transfer(us, us->iobuf, 2);
US_DEBUGP("Got interrupt data (0x%x, 0x%x)\n", US_DEBUGP("Got interrupt data (0x%x, 0x%x)\n",
us->irqdata[0], us->irqdata[1]); us->iobuf[0], us->iobuf[1]);
if (result != USB_STOR_XFER_GOOD) if (result != USB_STOR_XFER_GOOD)
return USB_STOR_TRANSPORT_ERROR; return USB_STOR_TRANSPORT_ERROR;
...@@ -804,7 +814,7 @@ int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -804,7 +814,7 @@ int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us)
srb->cmnd[0] == INQUIRY) srb->cmnd[0] == INQUIRY)
return USB_STOR_TRANSPORT_GOOD; return USB_STOR_TRANSPORT_GOOD;
else { else {
if (us->irqdata[0]) if (us->iobuf[0])
return USB_STOR_TRANSPORT_FAILED; return USB_STOR_TRANSPORT_FAILED;
else else
return USB_STOR_TRANSPORT_GOOD; return USB_STOR_TRANSPORT_GOOD;
...@@ -815,13 +825,13 @@ int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -815,13 +825,13 @@ int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us)
* The first byte should always be a 0x0 * The first byte should always be a 0x0
* The second byte & 0x0F should be 0x0 for good, otherwise error * The second byte & 0x0F should be 0x0 for good, otherwise error
*/ */
if (us->irqdata[0]) { if (us->iobuf[0]) {
US_DEBUGP("CBI IRQ data showed reserved bType %d\n", US_DEBUGP("CBI IRQ data showed reserved bType %d\n",
us->irqdata[0]); us->iobuf[0]);
return USB_STOR_TRANSPORT_ERROR; return USB_STOR_TRANSPORT_ERROR;
} }
switch (us->irqdata[1] & 0x0F) { switch (us->iobuf[1] & 0x0F) {
case 0x00: case 0x00:
return USB_STOR_TRANSPORT_GOOD; return USB_STOR_TRANSPORT_GOOD;
case 0x01: case 0x01:
...@@ -889,7 +899,6 @@ int usb_stor_CB_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -889,7 +899,6 @@ int usb_stor_CB_transport(Scsi_Cmnd *srb, struct us_data *us)
/* Determine what the maximum LUN supported is */ /* Determine what the maximum LUN supported is */
int usb_stor_Bulk_max_lun(struct us_data *us) int usb_stor_Bulk_max_lun(struct us_data *us)
{ {
unsigned char data;
int result; int result;
/* issue the command */ /* issue the command */
...@@ -897,14 +906,14 @@ int usb_stor_Bulk_max_lun(struct us_data *us) ...@@ -897,14 +906,14 @@ int usb_stor_Bulk_max_lun(struct us_data *us)
US_BULK_GET_MAX_LUN, US_BULK_GET_MAX_LUN,
USB_DIR_IN | USB_TYPE_CLASS | USB_DIR_IN | USB_TYPE_CLASS |
USB_RECIP_INTERFACE, USB_RECIP_INTERFACE,
0, us->ifnum, &data, sizeof(data), HZ); 0, us->ifnum, us->iobuf, 1, HZ);
US_DEBUGP("GetMaxLUN command result is %d, data is %d\n", US_DEBUGP("GetMaxLUN command result is %d, data is %d\n",
result, data); result, us->iobuf[0]);
/* if we have a successful request, return the result */ /* if we have a successful request, return the result */
if (result == 1) if (result == 1)
return data; return us->iobuf[0];
/* return the default -- no LUNs */ /* return the default -- no LUNs */
return 0; return 0;
...@@ -912,33 +921,34 @@ int usb_stor_Bulk_max_lun(struct us_data *us) ...@@ -912,33 +921,34 @@ int usb_stor_Bulk_max_lun(struct us_data *us)
int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us) int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us)
{ {
struct bulk_cb_wrap bcb; struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf;
struct bulk_cs_wrap bcs; struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf;
unsigned int transfer_length = srb->request_bufflen; unsigned int transfer_length = srb->request_bufflen;
int result; int result;
int fake_sense = 0; int fake_sense = 0;
/* set up the command wrapper */ /* set up the command wrapper */
bcb.Signature = cpu_to_le32(US_BULK_CB_SIGN); bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN);
bcb.DataTransferLength = cpu_to_le32(transfer_length); bcb->DataTransferLength = cpu_to_le32(transfer_length);
bcb.Flags = srb->sc_data_direction == SCSI_DATA_READ ? 1 << 7 : 0; bcb->Flags = srb->sc_data_direction == SCSI_DATA_READ ? 1 << 7 : 0;
bcb.Tag = srb->serial_number; bcb->Tag = srb->serial_number;
bcb.Lun = srb->device->lun; bcb->Lun = srb->device->lun;
if (us->flags & US_FL_SCM_MULT_TARG) if (us->flags & US_FL_SCM_MULT_TARG)
bcb.Lun |= srb->device->id << 4; bcb->Lun |= srb->device->id << 4;
bcb.Length = srb->cmd_len; bcb->Length = srb->cmd_len;
/* copy the command payload */ /* copy the command payload */
memset(bcb.CDB, 0, sizeof(bcb.CDB)); memset(bcb->CDB, 0, sizeof(bcb->CDB));
memcpy(bcb.CDB, srb->cmnd, bcb.Length); memcpy(bcb->CDB, srb->cmnd, bcb->Length);
/* send it to out endpoint */ /* send it to out endpoint */
US_DEBUGP("Bulk command S 0x%x T 0x%x Trg %d LUN %d L %d F %d CL %d\n", US_DEBUGP("Bulk command S 0x%x T 0x%x Trg %d LUN %d L %d F %d CL %d\n",
le32_to_cpu(bcb.Signature), bcb.Tag, le32_to_cpu(bcb->Signature), bcb->Tag,
(bcb.Lun >> 4), (bcb.Lun & 0x0F), (bcb->Lun >> 4), (bcb->Lun & 0x0F),
le32_to_cpu(bcb.DataTransferLength), bcb.Flags, bcb.Length); le32_to_cpu(bcb->DataTransferLength),
bcb->Flags, bcb->Length);
result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
&bcb, US_BULK_CB_WRAP_LEN, NULL); bcb, US_BULK_CB_WRAP_LEN, NULL);
US_DEBUGP("Bulk command transfer result=%d\n", result); US_DEBUGP("Bulk command transfer result=%d\n", result);
if (result != USB_STOR_XFER_GOOD) if (result != USB_STOR_XFER_GOOD)
return USB_STOR_TRANSPORT_ERROR; return USB_STOR_TRANSPORT_ERROR;
...@@ -972,7 +982,7 @@ int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -972,7 +982,7 @@ int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us)
/* get CSW for device status */ /* get CSW for device status */
US_DEBUGP("Attempting to get CSW...\n"); US_DEBUGP("Attempting to get CSW...\n");
result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
&bcs, US_BULK_CS_WRAP_LEN, NULL); bcs, US_BULK_CS_WRAP_LEN, NULL);
/* did the attempt to read the CSW fail? */ /* did the attempt to read the CSW fail? */
if (result == USB_STOR_XFER_STALLED) { if (result == USB_STOR_XFER_STALLED) {
...@@ -980,7 +990,7 @@ int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -980,7 +990,7 @@ int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us)
/* get the status again */ /* get the status again */
US_DEBUGP("Attempting to get CSW (2nd try)...\n"); US_DEBUGP("Attempting to get CSW (2nd try)...\n");
result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
&bcs, US_BULK_CS_WRAP_LEN, NULL); bcs, US_BULK_CS_WRAP_LEN, NULL);
} }
/* if we still have a failure at this point, we're in trouble */ /* if we still have a failure at this point, we're in trouble */
...@@ -990,17 +1000,18 @@ int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us) ...@@ -990,17 +1000,18 @@ int usb_stor_Bulk_transport(Scsi_Cmnd *srb, struct us_data *us)
/* check bulk status */ /* check bulk status */
US_DEBUGP("Bulk status Sig 0x%x T 0x%x R %d Stat 0x%x\n", US_DEBUGP("Bulk status Sig 0x%x T 0x%x R %d Stat 0x%x\n",
le32_to_cpu(bcs.Signature), bcs.Tag, le32_to_cpu(bcs->Signature), bcs->Tag,
bcs.Residue, bcs.Status); bcs->Residue, bcs->Status);
if ((bcs.Signature != cpu_to_le32(US_BULK_CS_SIGN) && bcs.Signature != cpu_to_le32(US_BULK_CS_OLYMPUS_SIGN)) || if ((bcs->Signature != cpu_to_le32(US_BULK_CS_SIGN) &&
bcs.Tag != bcb.Tag || bcs->Signature != cpu_to_le32(US_BULK_CS_OLYMPUS_SIGN)) ||
bcs.Status > US_BULK_STAT_PHASE) { bcs->Tag != srb->serial_number ||
bcs->Status > US_BULK_STAT_PHASE) {
US_DEBUGP("Bulk logical error\n"); US_DEBUGP("Bulk logical error\n");
return USB_STOR_TRANSPORT_ERROR; return USB_STOR_TRANSPORT_ERROR;
} }
/* based on the status code, we report good or bad */ /* based on the status code, we report good or bad */
switch (bcs.Status) { switch (bcs->Status) {
case US_BULK_STAT_OK: case US_BULK_STAT_OK:
/* device babbled -- return fake sense data */ /* device babbled -- return fake sense data */
if (fake_sense) { if (fake_sense) {
...@@ -1065,6 +1076,10 @@ static int usb_stor_reset_common(struct us_data *us, ...@@ -1065,6 +1076,10 @@ static int usb_stor_reset_common(struct us_data *us,
schedule_timeout(HZ*6); schedule_timeout(HZ*6);
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
down(&us->dev_semaphore); down(&us->dev_semaphore);
if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
US_DEBUGP("Reset interrupted by disconnect\n");
return FAILED;
}
US_DEBUGP("Soft reset: clearing bulk-in endpoint halt\n"); US_DEBUGP("Soft reset: clearing bulk-in endpoint halt\n");
result = usb_stor_clear_halt(us, us->recv_bulk_pipe); result = usb_stor_clear_halt(us, us->recv_bulk_pipe);
...@@ -1083,18 +1098,18 @@ static int usb_stor_reset_common(struct us_data *us, ...@@ -1083,18 +1098,18 @@ static int usb_stor_reset_common(struct us_data *us,
/* This issues a CB[I] Reset to the device in question /* This issues a CB[I] Reset to the device in question
*/ */
#define CB_RESET_CMD_SIZE 12
int usb_stor_CB_reset(struct us_data *us) int usb_stor_CB_reset(struct us_data *us)
{ {
unsigned char cmd[12]; US_DEBUGP("%s called\n", __FUNCTION__);
US_DEBUGP("CB_reset() called\n");
memset(cmd, 0xFF, sizeof(cmd)); memset(us->iobuf, 0xFF, CB_RESET_CMD_SIZE);
cmd[0] = SEND_DIAGNOSTIC; us->iobuf[0] = SEND_DIAGNOSTIC;
cmd[1] = 4; us->iobuf[1] = 4;
return usb_stor_reset_common(us, US_CBI_ADSC, return usb_stor_reset_common(us, US_CBI_ADSC,
USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
0, us->ifnum, cmd, sizeof(cmd)); 0, us->ifnum, us->iobuf, CB_RESET_CMD_SIZE);
} }
/* This issues a Bulk-only Reset to the device in question, including /* This issues a Bulk-only Reset to the device in question, including
...@@ -1102,7 +1117,7 @@ int usb_stor_CB_reset(struct us_data *us) ...@@ -1102,7 +1117,7 @@ int usb_stor_CB_reset(struct us_data *us)
*/ */
int usb_stor_Bulk_reset(struct us_data *us) int usb_stor_Bulk_reset(struct us_data *us)
{ {
US_DEBUGP("Bulk reset requested\n"); US_DEBUGP("%s called\n", __FUNCTION__);
return usb_stor_reset_common(us, US_BULK_RESET_REQUEST, return usb_stor_reset_common(us, US_BULK_RESET_REQUEST,
USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
......
...@@ -252,6 +252,14 @@ UNUSUAL_DEV( 0x054c, 0x0025, 0x0100, 0x0100, ...@@ -252,6 +252,14 @@ UNUSUAL_DEV( 0x054c, 0x0025, 0x0100, 0x0100,
US_SC_UFI, US_PR_CB, NULL, US_SC_UFI, US_PR_CB, NULL,
US_FL_SINGLE_LUN ), US_FL_SINGLE_LUN ),
#ifdef CONFIG_USB_STORAGE_ISD200
UNUSUAL_DEV( 0x054c, 0x002b, 0x0100, 0x0110,
"Sony",
"Portable USB Harddrive V2",
US_SC_ISD200, US_PR_BULK, isd200_Initialization,
0 ),
#endif
UNUSUAL_DEV( 0x054c, 0x002d, 0x0100, 0x0100, UNUSUAL_DEV( 0x054c, 0x002d, 0x0100, 0x0100,
"Sony", "Sony",
"Memorystick MSAC-US1", "Memorystick MSAC-US1",
...@@ -295,26 +303,6 @@ UNUSUAL_DEV( 0x059f, 0xa601, 0x0200, 0x0200, ...@@ -295,26 +303,6 @@ UNUSUAL_DEV( 0x059f, 0xa601, 0x0200, 0x0200,
"USB Hard Disk", "USB Hard Disk",
US_SC_RBC, US_PR_CB, NULL, 0 ), US_SC_RBC, US_PR_CB, NULL, 0 ),
/* This Pentax still camera is not conformant
* to the USB storage specification: -
* - It does not like the INQUIRY command. So we must handle this command
* of the SCSI layer ourselves.
* Tested on Rev. 10.00 (0x1000)
* Submitted by James Courtier-Dutton <James@superbug.demon.co.uk>
*/
UNUSUAL_DEV( 0x0a17, 0x0004, 0x1000, 0x1000,
"Pentax",
"Optio 2/3/400",
US_SC_DEVICE, US_PR_DEVICE, NULL,
US_FL_FIX_INQUIRY ),
/* Submitted by Per Winkvist <per.winkvist@uk.com> */
UNUSUAL_DEV( 0x0a17, 0x006, 0x1000, 0x9009,
"Pentax",
"Optio S",
US_SC_8070, US_PR_CBI, NULL,
US_FL_FIX_INQUIRY ),
#ifdef CONFIG_USB_STORAGE_ISD200 #ifdef CONFIG_USB_STORAGE_ISD200
UNUSUAL_DEV( 0x05ab, 0x0031, 0x0100, 0x0110, UNUSUAL_DEV( 0x05ab, 0x0031, 0x0100, 0x0110,
"In-System", "In-System",
...@@ -339,12 +327,6 @@ UNUSUAL_DEV( 0x05ab, 0x5701, 0x0100, 0x0110, ...@@ -339,12 +327,6 @@ UNUSUAL_DEV( 0x05ab, 0x5701, 0x0100, 0x0110,
"USB Storage Adapter V2", "USB Storage Adapter V2",
US_SC_ISD200, US_PR_BULK, isd200_Initialization, US_SC_ISD200, US_PR_BULK, isd200_Initialization,
0 ), 0 ),
UNUSUAL_DEV( 0x054c, 0x002b, 0x0100, 0x0110,
"Sony",
"Portable USB Harddrive V2",
US_SC_ISD200, US_PR_BULK, isd200_Initialization,
0 ),
#endif #endif
#ifdef CONFIG_USB_STORAGE_JUMPSHOT #ifdef CONFIG_USB_STORAGE_JUMPSHOT
...@@ -385,12 +367,6 @@ UNUSUAL_DEV( 0x05e3, 0x0702, 0x0000, 0x0001, ...@@ -385,12 +367,6 @@ UNUSUAL_DEV( 0x05e3, 0x0702, 0x0000, 0x0001,
US_SC_DEVICE, US_PR_DEVICE, NULL, US_SC_DEVICE, US_PR_DEVICE, NULL,
US_FL_FIX_INQUIRY ), US_FL_FIX_INQUIRY ),
UNUSUAL_DEV( 0x05e3, 0x0700, 0x0000, 0x9999,
"Unknown",
"GL641USB based CF Card reader",
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_FIX_INQUIRY | US_FL_MODE_XLATE),
/* Reported by Hanno Boeck <hanno@gmx.de> /* Reported by Hanno Boeck <hanno@gmx.de>
* Taken from the Lycoris Kernel */ * Taken from the Lycoris Kernel */
UNUSUAL_DEV( 0x0636, 0x0003, 0x0000, 0x9999, UNUSUAL_DEV( 0x0636, 0x0003, 0x0000, 0x9999,
...@@ -520,13 +496,6 @@ UNUSUAL_DEV( 0x07c4, 0xa006, 0x0000, 0xffff, ...@@ -520,13 +496,6 @@ UNUSUAL_DEV( 0x07c4, 0xa006, 0x0000, 0xffff,
"Simple Tech/Datafab CF+SM Reader", "Simple Tech/Datafab CF+SM Reader",
US_SC_SCSI, US_PR_DATAFAB, NULL, US_SC_SCSI, US_PR_DATAFAB, NULL,
US_FL_MODE_XLATE ), US_FL_MODE_XLATE ),
/* Submitted by Olaf Hering <olh@suse.de> */
UNUSUAL_DEV( 0x07c4, 0xa109, 0x0000, 0xffff,
"Datafab Systems, Inc.",
"USB to CF + SM Combo (LC1)",
US_SC_SCSI, US_PR_DATAFAB, NULL,
US_FL_MODE_XLATE ),
#endif #endif
#ifdef CONFIG_USB_STORAGE_SDDR55 #ifdef CONFIG_USB_STORAGE_SDDR55
...@@ -538,6 +507,15 @@ UNUSUAL_DEV( 0x07c4, 0xa103, 0x0000, 0x9999, ...@@ -538,6 +507,15 @@ UNUSUAL_DEV( 0x07c4, 0xa103, 0x0000, 0x9999,
US_FL_FIX_INQUIRY ), US_FL_FIX_INQUIRY ),
#endif #endif
#ifdef CONFIG_USB_STORAGE_DATAFAB
/* Submitted by Olaf Hering <olh@suse.de> */
UNUSUAL_DEV( 0x07c4, 0xa109, 0x0000, 0xffff,
"Datafab Systems, Inc.",
"USB to CF + SM Combo (LC1)",
US_SC_SCSI, US_PR_DATAFAB, NULL,
US_FL_MODE_XLATE ),
#endif
/* Datafab KECF-USB / Sagatek DCS-CF / Simpletech Flashlink UCF-100 /* Datafab KECF-USB / Sagatek DCS-CF / Simpletech Flashlink UCF-100
* Only revision 1.13 tested (same for all of the above devices, * Only revision 1.13 tested (same for all of the above devices,
* based on the Datafab DF-UG-07 chip). Needed for US_FL_FIX_INQUIRY. * based on the Datafab DF-UG-07 chip). Needed for US_FL_FIX_INQUIRY.
...@@ -563,6 +541,22 @@ UNUSUAL_DEV( 0x07cf, 0x1001, 0x1000, 0x9009, ...@@ -563,6 +541,22 @@ UNUSUAL_DEV( 0x07cf, 0x1001, 0x1000, 0x9009,
US_SC_8070, US_PR_CB, NULL, US_SC_8070, US_PR_CB, NULL,
US_FL_FIX_INQUIRY ), US_FL_FIX_INQUIRY ),
/* Submitted by Hartmut Wahl <hwahl@hwahl.de>*/
UNUSUAL_DEV( 0x0839, 0x000a, 0x0001, 0x0001,
"Samsung",
"Digimax 410",
US_SC_DEVICE, US_PR_DEVICE, NULL,
US_FL_FIX_INQUIRY),
/* Aiptek PocketCAM 3Mega
* Nicolas DUPEUX <nicolas@dupeux.net>
*/
UNUSUAL_DEV( 0x08ca, 0x2011, 0x0000, 0x9999,
"AIPTEK",
"PocketCAM 3Mega",
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_MODE_XLATE ),
/* aeb */ /* aeb */
UNUSUAL_DEV( 0x090c, 0x1132, 0x0000, 0xffff, UNUSUAL_DEV( 0x090c, 0x1132, 0x0000, 0xffff,
"Feiya", "Feiya",
...@@ -570,13 +564,6 @@ UNUSUAL_DEV( 0x090c, 0x1132, 0x0000, 0xffff, ...@@ -570,13 +564,6 @@ UNUSUAL_DEV( 0x090c, 0x1132, 0x0000, 0xffff,
US_SC_SCSI, US_PR_BULK, NULL, US_SC_SCSI, US_PR_BULK, NULL,
US_FL_FIX_CAPACITY ), US_FL_FIX_CAPACITY ),
/* Submitted by Hartmut Wahl <hwahl@hwahl.de>*/
UNUSUAL_DEV( 0x0839, 0x000a, 0x0001, 0x0001,
"Samsung",
"Digimax 410",
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_FIX_INQUIRY),
UNUSUAL_DEV( 0x097a, 0x0001, 0x0000, 0x0001, UNUSUAL_DEV( 0x097a, 0x0001, 0x0000, 0x0001,
"Minds@Work", "Minds@Work",
"Digital Wallet", "Digital Wallet",
...@@ -589,21 +576,25 @@ UNUSUAL_DEV( 0x0a16, 0x8888, 0x0100, 0x0100, ...@@ -589,21 +576,25 @@ UNUSUAL_DEV( 0x0a16, 0x8888, 0x0100, 0x0100,
US_SC_SCSI, US_PR_BULK, NULL, US_SC_SCSI, US_PR_BULK, NULL,
US_FL_FIX_INQUIRY ), US_FL_FIX_INQUIRY ),
UNUSUAL_DEV( 0x0a16, 0x8888, 0x0100, 0x0100, /* This Pentax still camera is not conformant
"IBM", * to the USB storage specification: -
"IBM USB Memory Key", * - It does not like the INQUIRY command. So we must handle this command
US_SC_SCSI, US_PR_BULK, NULL, * of the SCSI layer ourselves.
* Tested on Rev. 10.00 (0x1000)
* Submitted by James Courtier-Dutton <James@superbug.demon.co.uk>
*/
UNUSUAL_DEV( 0x0a17, 0x0004, 0x1000, 0x1000,
"Pentax",
"Optio 2/3/400",
US_SC_DEVICE, US_PR_DEVICE, NULL,
US_FL_FIX_INQUIRY ), US_FL_FIX_INQUIRY ),
/* Pentax Optio S digital camera /* Submitted by Per Winkvist <per.winkvist@uk.com> */
* adapted from http://www2.goldfisch.at/knowledge/233 UNUSUAL_DEV( 0x0a17, 0x006, 0x1000, 0x9009,
* (Peter Pilsl <pilsl@goldfisch.at>)
* by Christoph Weidemann <cweidema@indiana.edu> */
UNUSUAL_DEV( 0x0a17, 0x0006, 0x0000, 0xffff,
"Pentax", "Pentax",
"Optio S", "Optio S",
US_SC_8070, US_PR_CB, NULL, US_SC_8070, US_PR_CBI, NULL,
US_FL_MODE_XLATE|US_FL_FIX_INQUIRY), US_FL_FIX_INQUIRY ),
#ifdef CONFIG_USB_STORAGE_ISD200 #ifdef CONFIG_USB_STORAGE_ISD200
UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110, UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110,
...@@ -613,13 +604,6 @@ UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110, ...@@ -613,13 +604,6 @@ UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110,
0 ), 0 ),
#endif #endif
/* EasyDisk support. Submitted by Stanislav Karchebny <berk@madfire.net> */
UNUSUAL_DEV( 0x1065, 0x2136, 0x0000, 0x0001,
"Global Channel Solutions",
"EasyDisk EDxxxx",
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_MODE_XLATE | US_FL_FIX_INQUIRY ),
/* Reported by Kevin Cernekee <kpc-usbdev@gelato.uiuc.edu> /* Reported by Kevin Cernekee <kpc-usbdev@gelato.uiuc.edu>
* Tested on hardware version 1.10. * Tested on hardware version 1.10.
* Entry is needed only for the initializer function override. * Entry is needed only for the initializer function override.
...@@ -648,13 +632,3 @@ UNUSUAL_DEV( 0x55aa, 0xa103, 0x0000, 0x9999, ...@@ -648,13 +632,3 @@ UNUSUAL_DEV( 0x55aa, 0xa103, 0x0000, 0x9999,
US_SC_SCSI, US_PR_SDDR55, NULL, US_SC_SCSI, US_PR_SDDR55, NULL,
US_FL_SINGLE_LUN), US_FL_SINGLE_LUN),
#endif #endif
/* Aiptek PocketCAM 3Mega
* Nicolas DUPEUX <nicolas@dupeux.net>
*/
UNUSUAL_DEV( 0x08ca, 0x2011, 0x0000, 0x9999,
"AIPTEK",
"PocketCAM 3Mega",
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_MODE_XLATE ),
...@@ -338,10 +338,16 @@ static int usb_stor_control_thread(void * __us) ...@@ -338,10 +338,16 @@ static int usb_stor_control_thread(void * __us)
/* lock the device pointers */ /* lock the device pointers */
down(&(us->dev_semaphore)); down(&(us->dev_semaphore));
/* don't do anything if we are disconnecting */
if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
US_DEBUGP("No command during disconnect\n");
us->srb->result = DID_BAD_TARGET << 16;
}
/* reject the command if the direction indicator /* reject the command if the direction indicator
* is UNKNOWN * is UNKNOWN
*/ */
if (us->srb->sc_data_direction == SCSI_DATA_UNKNOWN) { else if (us->srb->sc_data_direction == SCSI_DATA_UNKNOWN) {
US_DEBUGP("UNKNOWN data direction\n"); US_DEBUGP("UNKNOWN data direction\n");
us->srb->result = DID_ERROR << 16; us->srb->result = DID_ERROR << 16;
} }
...@@ -421,12 +427,44 @@ static int usb_stor_control_thread(void * __us) ...@@ -421,12 +427,44 @@ static int usb_stor_control_thread(void * __us)
* Device probing and disconnecting * Device probing and disconnecting
***********************************************************************/ ***********************************************************************/
/* Associate our private data with the USB device */
static int associate_dev(struct us_data *us, struct usb_interface *intf)
{
US_DEBUGP("-- %s\n", __FUNCTION__);
/* Fill in the device-related fields */
us->pusb_dev = interface_to_usbdev(intf);
us->pusb_intf = intf;
us->ifnum = intf->altsetting->desc.bInterfaceNumber;
/* Store our private data in the interface and increment the
* device's reference count */
usb_set_intfdata(intf, us);
usb_get_dev(us->pusb_dev);
/* Allocate the device-related DMA-mapped buffers */
us->cr = usb_buffer_alloc(us->pusb_dev, sizeof(*us->cr),
GFP_KERNEL, &us->cr_dma);
if (!us->cr) {
US_DEBUGP("usb_ctrlrequest allocation failed\n");
return -ENOMEM;
}
us->iobuf = usb_buffer_alloc(us->pusb_dev, US_IOBUF_SIZE,
GFP_KERNEL, &us->iobuf_dma);
if (!us->iobuf) {
US_DEBUGP("I/O buffer allocation failed\n");
return -ENOMEM;
}
return 0;
}
/* Get the unusual_devs entries and the string descriptors */ /* Get the unusual_devs entries and the string descriptors */
static void get_device_info(struct us_data *us, int id_index) static void get_device_info(struct us_data *us, int id_index)
{ {
struct usb_device *dev = us->pusb_dev; struct usb_device *dev = us->pusb_dev;
struct usb_host_interface *altsetting = struct usb_interface_descriptor *idesc =
&us->pusb_intf->altsetting[us->pusb_intf->act_altsetting]; &us->pusb_intf->altsetting[us->pusb_intf->act_altsetting].desc;
struct us_unusual_dev *unusual_dev = &us_unusual_dev_list[id_index]; struct us_unusual_dev *unusual_dev = &us_unusual_dev_list[id_index];
struct usb_device_id *id = &storage_usb_ids[id_index]; struct usb_device_id *id = &storage_usb_ids[id_index];
...@@ -438,10 +476,10 @@ static void get_device_info(struct us_data *us, int id_index) ...@@ -438,10 +476,10 @@ static void get_device_info(struct us_data *us, int id_index)
/* Store the entries */ /* Store the entries */
us->unusual_dev = unusual_dev; us->unusual_dev = unusual_dev;
us->subclass = (unusual_dev->useProtocol == US_SC_DEVICE) ? us->subclass = (unusual_dev->useProtocol == US_SC_DEVICE) ?
altsetting->desc.bInterfaceSubClass : idesc->bInterfaceSubClass :
unusual_dev->useProtocol; unusual_dev->useProtocol;
us->protocol = (unusual_dev->useTransport == US_PR_DEVICE) ? us->protocol = (unusual_dev->useTransport == US_PR_DEVICE) ?
altsetting->desc.bInterfaceProtocol : idesc->bInterfaceProtocol :
unusual_dev->useTransport; unusual_dev->useTransport;
us->flags = unusual_dev->flags; us->flags = unusual_dev->flags;
...@@ -455,20 +493,26 @@ static void get_device_info(struct us_data *us, int id_index) ...@@ -455,20 +493,26 @@ static void get_device_info(struct us_data *us, int id_index)
"an unneeded SubClass entry", "an unneeded SubClass entry",
"an unneeded Protocol entry", "an unneeded Protocol entry",
"unneeded SubClass and Protocol entries"}; "unneeded SubClass and Protocol entries"};
struct usb_device_descriptor *ddesc = &dev->descriptor;
int msg = -1; int msg = -1;
if (unusual_dev->useProtocol != US_SC_DEVICE && if (unusual_dev->useProtocol != US_SC_DEVICE &&
us->subclass == altsetting->desc.bInterfaceSubClass) us->subclass == idesc->bInterfaceSubClass)
msg += 1; msg += 1;
if (unusual_dev->useTransport != US_PR_DEVICE && if (unusual_dev->useTransport != US_PR_DEVICE &&
us->protocol == altsetting->desc.bInterfaceProtocol) us->protocol == idesc->bInterfaceProtocol)
msg += 2; msg += 2;
if (msg >= 0) if (msg >= 0)
printk(KERN_NOTICE USB_STORAGE "This device " printk(KERN_NOTICE USB_STORAGE "This device "
"(%04x,%04x) has %s in unusual_devs.h\n" "(%04x,%04x,%04x S %02x P %02x)"
" has %s in unusual_devs.h\n"
" Please send a copy of this message to " " Please send a copy of this message to "
"<linux-usb-devel@lists.sourceforge.net>\n", "<linux-usb-devel@lists.sourceforge.net>\n",
id->idVendor, id->idProduct, msgs[msg]); ddesc->idVendor, ddesc->idProduct,
ddesc->bcdDevice,
idesc->bInterfaceSubClass,
idesc->bInterfaceProtocol,
msgs[msg]);
} }
/* Read the device's string descriptors */ /* Read the device's string descriptors */
...@@ -485,15 +529,15 @@ static void get_device_info(struct us_data *us, int id_index) ...@@ -485,15 +529,15 @@ static void get_device_info(struct us_data *us, int id_index)
/* Use the unusual_dev strings if the device didn't provide them */ /* Use the unusual_dev strings if the device didn't provide them */
if (strlen(us->vendor) == 0) { if (strlen(us->vendor) == 0) {
if (unusual_dev->vendorName) if (unusual_dev->vendorName)
strncpy(us->vendor, unusual_dev->vendorName, strlcpy(us->vendor, unusual_dev->vendorName,
sizeof(us->vendor) - 1); sizeof(us->vendor));
else else
strcpy(us->vendor, "Unknown"); strcpy(us->vendor, "Unknown");
} }
if (strlen(us->product) == 0) { if (strlen(us->product) == 0) {
if (unusual_dev->productName) if (unusual_dev->productName)
strncpy(us->product, unusual_dev->productName, strlcpy(us->product, unusual_dev->productName,
sizeof(us->product) - 1); sizeof(us->product));
else else
strcpy(us->product, "Unknown"); strcpy(us->product, "Unknown");
} }
...@@ -714,18 +758,9 @@ static int usb_stor_acquire_resources(struct us_data *us) ...@@ -714,18 +758,9 @@ static int usb_stor_acquire_resources(struct us_data *us)
{ {
int p; int p;
/* Allocate the USB control blocks */
US_DEBUGP("Allocating usb_ctrlrequest\n");
us->dr = kmalloc(sizeof(*us->dr), GFP_KERNEL);
if (!us->dr) {
US_DEBUGP("allocation failed\n");
return -ENOMEM;
}
US_DEBUGP("Allocating URB\n");
us->current_urb = usb_alloc_urb(0, GFP_KERNEL); us->current_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!us->current_urb) { if (!us->current_urb) {
US_DEBUGP("allocation failed\n"); US_DEBUGP("URB allocation failed\n");
return -ENOMEM; return -ENOMEM;
} }
...@@ -778,8 +813,24 @@ static void dissociate_dev(struct us_data *us) ...@@ -778,8 +813,24 @@ static void dissociate_dev(struct us_data *us)
{ {
US_DEBUGP("-- %s\n", __FUNCTION__); US_DEBUGP("-- %s\n", __FUNCTION__);
down(&us->dev_semaphore); down(&us->dev_semaphore);
/* Free the device-related DMA-mapped buffers */
if (us->cr) {
usb_buffer_free(us->pusb_dev, sizeof(*us->cr), us->cr,
us->cr_dma);
us->cr = NULL;
}
if (us->iobuf) {
usb_buffer_free(us->pusb_dev, US_IOBUF_SIZE, us->iobuf,
us->iobuf_dma);
us->iobuf = NULL;
}
/* Remove our private data from the interface and decrement the
* device's reference count */
usb_set_intfdata(us->pusb_intf, NULL); usb_set_intfdata(us->pusb_intf, NULL);
usb_put_dev(us->pusb_dev); usb_put_dev(us->pusb_dev);
us->pusb_dev = NULL; us->pusb_dev = NULL;
us->pusb_intf = NULL; us->pusb_intf = NULL;
up(&us->dev_semaphore); up(&us->dev_semaphore);
...@@ -818,16 +869,11 @@ void usb_stor_release_resources(struct us_data *us) ...@@ -818,16 +869,11 @@ void usb_stor_release_resources(struct us_data *us)
us->extra_destructor(us->extra); us->extra_destructor(us->extra);
} }
/* Destroy the extra data */ /* Free the extra data and the URB */
if (us->extra) { if (us->extra)
kfree(us->extra); kfree(us->extra);
}
/* Free the USB control blocks */
if (us->current_urb) if (us->current_urb)
usb_free_urb(us->current_urb); usb_free_urb(us->current_urb);
if (us->dr)
kfree(us->dr);
/* Free the structure itself */ /* Free the structure itself */
kfree(us); kfree(us);
...@@ -857,15 +903,10 @@ static int storage_probe(struct usb_interface *intf, ...@@ -857,15 +903,10 @@ static int storage_probe(struct usb_interface *intf,
init_MUTEX_LOCKED(&(us->sema)); init_MUTEX_LOCKED(&(us->sema));
init_completion(&(us->notify)); init_completion(&(us->notify));
/* Fill in the device-related fields */ /* Associate the us_data structure with the USB device */
us->pusb_dev = interface_to_usbdev(intf); result = associate_dev(us, intf);
us->pusb_intf = intf; if (result)
us->ifnum = intf->altsetting->desc.bInterfaceNumber; goto BadDevice;
/* Store our private data in the interface and increment the
* device's reference count */
usb_set_intfdata(intf, us);
usb_get_dev(us->pusb_dev);
/* /*
* Get the unusual_devs entries and the descriptors * Get the unusual_devs entries and the descriptors
......
...@@ -92,6 +92,15 @@ struct us_unusual_dev { ...@@ -92,6 +92,15 @@ struct us_unusual_dev {
#define USB_STOR_STRING_LEN 32 #define USB_STOR_STRING_LEN 32
/*
* We provide a DMA-mapped I/O buffer for use with small USB transfers.
* It turns out that CB[I] needs a 12-byte buffer and Bulk-only needs a
* 31-byte buffer. But Freecom needs a 64-byte buffer, so that's the
* size we'll allocate.
*/
#define US_IOBUF_SIZE 64 /* Size of the DMA-mapped I/O buffer */
typedef int (*trans_cmnd)(Scsi_Cmnd*, struct us_data*); typedef int (*trans_cmnd)(Scsi_Cmnd*, struct us_data*);
typedef int (*trans_reset)(struct us_data*); typedef int (*trans_reset)(struct us_data*);
typedef void (*proto_cmnd)(Scsi_Cmnd*, struct us_data*); typedef void (*proto_cmnd)(Scsi_Cmnd*, struct us_data*);
...@@ -106,6 +115,7 @@ struct us_data { ...@@ -106,6 +115,7 @@ struct us_data {
struct semaphore dev_semaphore; /* protect pusb_dev */ struct semaphore dev_semaphore; /* protect pusb_dev */
struct usb_device *pusb_dev; /* this usb_device */ struct usb_device *pusb_dev; /* this usb_device */
struct usb_interface *pusb_intf; /* this interface */ struct usb_interface *pusb_intf; /* this interface */
struct us_unusual_dev *unusual_dev; /* device-filter entry */
unsigned long flags; /* from filter initially */ unsigned long flags; /* from filter initially */
unsigned int send_bulk_pipe; /* cached pipe values */ unsigned int send_bulk_pipe; /* cached pipe values */
unsigned int recv_bulk_pipe; unsigned int recv_bulk_pipe;
...@@ -139,20 +149,19 @@ struct us_data { ...@@ -139,20 +149,19 @@ struct us_data {
int pid; /* control thread */ int pid; /* control thread */
int sm_state; /* what we are doing */ int sm_state; /* what we are doing */
/* interrupt communications data */
unsigned char irqdata[2]; /* data from USB IRQ */
/* control and bulk communications data */ /* control and bulk communications data */
struct urb *current_urb; /* non-int USB requests */ struct urb *current_urb; /* USB requests */
struct usb_ctrlrequest *dr; /* control requests */ struct usb_ctrlrequest *cr; /* control requests */
struct usb_sg_request current_sg; /* scatter-gather USB */ struct usb_sg_request current_sg; /* scatter-gather req. */
unsigned char *iobuf; /* I/O buffer */
/* the semaphore for sleeping the control thread */ dma_addr_t cr_dma; /* buffer DMA addresses */
struct semaphore sema; /* to sleep thread on */ dma_addr_t iobuf_dma;
/* mutual exclusion structures */ /* mutual exclusion structures */
struct semaphore sema; /* to sleep thread on */
struct completion notify; /* thread begin/end */ struct completion notify; /* thread begin/end */
struct us_unusual_dev *unusual_dev; /* If unusual device */
/* subdriver information */
void *extra; /* Any extra data */ void *extra; /* Any extra data */
extra_data_destructor extra_destructor;/* extra data destructor */ extra_data_destructor extra_destructor;/* extra data destructor */
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment