forked from luck/tmp_suning_uos_patched
e80b0fade0
This patch adds another usb-storage subdriver, which supports two fairly old dual-XD/SmartMedia reader-writers (USB1.1 devices). This driver was written by Daniel Drake <dsd@gentoo.org> -- he notes that he wrote this driver without specs, however a vendor-supplied GPL driver for the previous generation of products ("sma03") did prove to be quite useful, as did the sddr09 driver which also has to deal with low-level physical block layout on SmartMedia. The original patch has been reformed by me, as it clashed with the libusual patches. We really need to consolidate some of this common SmartMedia code, and get together with the MTD guys to share it with them as well. Signed-off-by: Daniel Drake <dsd@gentoo.org> Signed-off-by: Matthew Dharm <mdharm-usb@one-eyed-alien.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1120 lines
30 KiB
C
1120 lines
30 KiB
C
/*
|
|
* Driver for Alauda-based card readers
|
|
*
|
|
* Current development and maintenance by:
|
|
* (c) 2005 Daniel Drake <dsd@gentoo.org>
|
|
*
|
|
* The 'Alauda' is a chip manufacturered by RATOC for OEM use.
|
|
*
|
|
* Alauda implements a vendor-specific command set to access two media reader
|
|
* ports (XD, SmartMedia). This driver converts SCSI commands to the commands
|
|
* which are accepted by these devices.
|
|
*
|
|
* The driver was developed through reverse-engineering, with the help of the
|
|
* sddr09 driver which has many similarities, and with some help from the
|
|
* (very old) vendor-supplied GPL sma03 driver.
|
|
*
|
|
* For protocol info, see http://alauda.sourceforge.net
|
|
*
|
|
* 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 <scsi/scsi.h>
|
|
#include <scsi/scsi_cmnd.h>
|
|
#include <scsi/scsi_device.h>
|
|
|
|
#include "usb.h"
|
|
#include "transport.h"
|
|
#include "protocol.h"
|
|
#include "debug.h"
|
|
#include "alauda.h"
|
|
|
|
#define short_pack(lsb,msb) ( ((u16)(lsb)) | ( ((u16)(msb))<<8 ) )
|
|
#define LSB_of(s) ((s)&0xFF)
|
|
#define MSB_of(s) ((s)>>8)
|
|
|
|
#define MEDIA_PORT(us) us->srb->device->lun
|
|
#define MEDIA_INFO(us) ((struct alauda_info *)us->extra)->port[MEDIA_PORT(us)]
|
|
|
|
#define PBA_LO(pba) ((pba & 0xF) << 5)
|
|
#define PBA_HI(pba) (pba >> 3)
|
|
#define PBA_ZONE(pba) (pba >> 11)
|
|
|
|
/*
|
|
* Media handling
|
|
*/
|
|
|
|
struct alauda_card_info {
|
|
unsigned char id; /* id byte */
|
|
unsigned char chipshift; /* 1<<cs bytes total capacity */
|
|
unsigned char pageshift; /* 1<<ps bytes in a page */
|
|
unsigned char blockshift; /* 1<<bs pages per block */
|
|
unsigned char zoneshift; /* 1<<zs blocks per zone */
|
|
};
|
|
|
|
static struct alauda_card_info alauda_card_ids[] = {
|
|
/* NAND flash */
|
|
{ 0x6e, 20, 8, 4, 8}, /* 1 MB */
|
|
{ 0xe8, 20, 8, 4, 8}, /* 1 MB */
|
|
{ 0xec, 20, 8, 4, 8}, /* 1 MB */
|
|
{ 0x64, 21, 8, 4, 9}, /* 2 MB */
|
|
{ 0xea, 21, 8, 4, 9}, /* 2 MB */
|
|
{ 0x6b, 22, 9, 4, 9}, /* 4 MB */
|
|
{ 0xe3, 22, 9, 4, 9}, /* 4 MB */
|
|
{ 0xe5, 22, 9, 4, 9}, /* 4 MB */
|
|
{ 0xe6, 23, 9, 4, 10}, /* 8 MB */
|
|
{ 0x73, 24, 9, 5, 10}, /* 16 MB */
|
|
{ 0x75, 25, 9, 5, 10}, /* 32 MB */
|
|
{ 0x76, 26, 9, 5, 10}, /* 64 MB */
|
|
{ 0x79, 27, 9, 5, 10}, /* 128 MB */
|
|
{ 0x71, 28, 9, 5, 10}, /* 256 MB */
|
|
|
|
/* MASK ROM */
|
|
{ 0x5d, 21, 9, 4, 8}, /* 2 MB */
|
|
{ 0xd5, 22, 9, 4, 9}, /* 4 MB */
|
|
{ 0xd6, 23, 9, 4, 10}, /* 8 MB */
|
|
{ 0x57, 24, 9, 4, 11}, /* 16 MB */
|
|
{ 0x58, 25, 9, 4, 12}, /* 32 MB */
|
|
{ 0,}
|
|
};
|
|
|
|
static struct alauda_card_info *alauda_card_find_id(unsigned char id) {
|
|
int i;
|
|
|
|
for (i = 0; alauda_card_ids[i].id != 0; i++)
|
|
if (alauda_card_ids[i].id == id)
|
|
return &(alauda_card_ids[i]);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ECC computation.
|
|
*/
|
|
|
|
static unsigned char parity[256];
|
|
static unsigned char ecc2[256];
|
|
|
|
static void nand_init_ecc(void) {
|
|
int i, j, a;
|
|
|
|
parity[0] = 0;
|
|
for (i = 1; i < 256; i++)
|
|
parity[i] = (parity[i&(i-1)] ^ 1);
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
a = 0;
|
|
for (j = 0; j < 8; j++) {
|
|
if (i & (1<<j)) {
|
|
if ((j & 1) == 0)
|
|
a ^= 0x04;
|
|
if ((j & 2) == 0)
|
|
a ^= 0x10;
|
|
if ((j & 4) == 0)
|
|
a ^= 0x40;
|
|
}
|
|
}
|
|
ecc2[i] = ~(a ^ (a<<1) ^ (parity[i] ? 0xa8 : 0));
|
|
}
|
|
}
|
|
|
|
/* compute 3-byte ecc on 256 bytes */
|
|
static void nand_compute_ecc(unsigned char *data, unsigned char *ecc) {
|
|
int i, j, a;
|
|
unsigned char par, bit, bits[8];
|
|
|
|
par = 0;
|
|
for (j = 0; j < 8; j++)
|
|
bits[j] = 0;
|
|
|
|
/* collect 16 checksum bits */
|
|
for (i = 0; i < 256; i++) {
|
|
par ^= data[i];
|
|
bit = parity[data[i]];
|
|
for (j = 0; j < 8; j++)
|
|
if ((i & (1<<j)) == 0)
|
|
bits[j] ^= bit;
|
|
}
|
|
|
|
/* put 4+4+4 = 12 bits in the ecc */
|
|
a = (bits[3] << 6) + (bits[2] << 4) + (bits[1] << 2) + bits[0];
|
|
ecc[0] = ~(a ^ (a<<1) ^ (parity[par] ? 0xaa : 0));
|
|
|
|
a = (bits[7] << 6) + (bits[6] << 4) + (bits[5] << 2) + bits[4];
|
|
ecc[1] = ~(a ^ (a<<1) ^ (parity[par] ? 0xaa : 0));
|
|
|
|
ecc[2] = ecc2[par];
|
|
}
|
|
|
|
static int nand_compare_ecc(unsigned char *data, unsigned char *ecc) {
|
|
return (data[0] == ecc[0] && data[1] == ecc[1] && data[2] == ecc[2]);
|
|
}
|
|
|
|
static void nand_store_ecc(unsigned char *data, unsigned char *ecc) {
|
|
memcpy(data, ecc, 3);
|
|
}
|
|
|
|
/*
|
|
* Alauda driver
|
|
*/
|
|
|
|
/*
|
|
* Forget our PBA <---> LBA mappings for a particular port
|
|
*/
|
|
static void alauda_free_maps (struct alauda_media_info *media_info)
|
|
{
|
|
unsigned int shift = media_info->zoneshift
|
|
+ media_info->blockshift + media_info->pageshift;
|
|
unsigned int num_zones = media_info->capacity >> shift;
|
|
unsigned int i;
|
|
|
|
if (media_info->lba_to_pba != NULL)
|
|
for (i = 0; i < num_zones; i++) {
|
|
kfree(media_info->lba_to_pba[i]);
|
|
media_info->lba_to_pba[i] = NULL;
|
|
}
|
|
|
|
if (media_info->pba_to_lba != NULL)
|
|
for (i = 0; i < num_zones; i++) {
|
|
kfree(media_info->pba_to_lba[i]);
|
|
media_info->pba_to_lba[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns 2 bytes of status data
|
|
* The first byte describes media status, and second byte describes door status
|
|
*/
|
|
static int alauda_get_media_status(struct us_data *us, unsigned char *data)
|
|
{
|
|
int rc;
|
|
unsigned char command;
|
|
|
|
if (MEDIA_PORT(us) == ALAUDA_PORT_XD)
|
|
command = ALAUDA_GET_XD_MEDIA_STATUS;
|
|
else
|
|
command = ALAUDA_GET_SM_MEDIA_STATUS;
|
|
|
|
rc = usb_stor_ctrl_transfer(us, us->recv_ctrl_pipe,
|
|
command, 0xc0, 0, 1, data, 2);
|
|
|
|
US_DEBUGP("alauda_get_media_status: Media status %02X %02X\n",
|
|
data[0], data[1]);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Clears the "media was changed" bit so that we know when it changes again
|
|
* in the future.
|
|
*/
|
|
static int alauda_ack_media(struct us_data *us)
|
|
{
|
|
unsigned char command;
|
|
|
|
if (MEDIA_PORT(us) == ALAUDA_PORT_XD)
|
|
command = ALAUDA_ACK_XD_MEDIA_CHANGE;
|
|
else
|
|
command = ALAUDA_ACK_SM_MEDIA_CHANGE;
|
|
|
|
return usb_stor_ctrl_transfer(us, us->send_ctrl_pipe,
|
|
command, 0x40, 0, 1, NULL, 0);
|
|
}
|
|
|
|
/*
|
|
* Retrieves a 4-byte media signature, which indicates manufacturer, capacity,
|
|
* and some other details.
|
|
*/
|
|
static int alauda_get_media_signature(struct us_data *us, unsigned char *data)
|
|
{
|
|
unsigned char command;
|
|
|
|
if (MEDIA_PORT(us) == ALAUDA_PORT_XD)
|
|
command = ALAUDA_GET_XD_MEDIA_SIG;
|
|
else
|
|
command = ALAUDA_GET_SM_MEDIA_SIG;
|
|
|
|
return usb_stor_ctrl_transfer(us, us->recv_ctrl_pipe,
|
|
command, 0xc0, 0, 0, data, 4);
|
|
}
|
|
|
|
/*
|
|
* Resets the media status (but not the whole device?)
|
|
*/
|
|
static int alauda_reset_media(struct us_data *us)
|
|
{
|
|
unsigned char *command = us->iobuf;
|
|
|
|
memset(command, 0, 9);
|
|
command[0] = ALAUDA_BULK_CMD;
|
|
command[1] = ALAUDA_BULK_RESET_MEDIA;
|
|
command[8] = MEDIA_PORT(us);
|
|
|
|
return usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
|
|
command, 9, NULL);
|
|
}
|
|
|
|
/*
|
|
* Examines the media and deduces capacity, etc.
|
|
*/
|
|
static int alauda_init_media(struct us_data *us)
|
|
{
|
|
unsigned char *data = us->iobuf;
|
|
int ready = 0;
|
|
struct alauda_card_info *media_info;
|
|
unsigned int num_zones;
|
|
|
|
while (ready == 0) {
|
|
msleep(20);
|
|
|
|
if (alauda_get_media_status(us, data) != USB_STOR_XFER_GOOD)
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
|
|
if (data[0] & 0x10)
|
|
ready = 1;
|
|
}
|
|
|
|
US_DEBUGP("alauda_init_media: We are ready for action!\n");
|
|
|
|
if (alauda_ack_media(us) != USB_STOR_XFER_GOOD)
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
|
|
msleep(10);
|
|
|
|
if (alauda_get_media_status(us, data) != USB_STOR_XFER_GOOD)
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
|
|
if (data[0] != 0x14) {
|
|
US_DEBUGP("alauda_init_media: Media not ready after ack\n");
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
|
|
if (alauda_get_media_signature(us, data) != USB_STOR_XFER_GOOD)
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
|
|
US_DEBUGP("alauda_init_media: Media signature: %02X %02X %02X %02X\n",
|
|
data[0], data[1], data[2], data[3]);
|
|
media_info = alauda_card_find_id(data[1]);
|
|
if (media_info == NULL) {
|
|
printk("alauda_init_media: Unrecognised media signature: "
|
|
"%02X %02X %02X %02X\n",
|
|
data[0], data[1], data[2], data[3]);
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
|
|
MEDIA_INFO(us).capacity = 1 << media_info->chipshift;
|
|
US_DEBUGP("Found media with capacity: %ldMB\n",
|
|
MEDIA_INFO(us).capacity >> 20);
|
|
|
|
MEDIA_INFO(us).pageshift = media_info->pageshift;
|
|
MEDIA_INFO(us).blockshift = media_info->blockshift;
|
|
MEDIA_INFO(us).zoneshift = media_info->zoneshift;
|
|
|
|
MEDIA_INFO(us).pagesize = 1 << media_info->pageshift;
|
|
MEDIA_INFO(us).blocksize = 1 << media_info->blockshift;
|
|
MEDIA_INFO(us).zonesize = 1 << media_info->zoneshift;
|
|
|
|
MEDIA_INFO(us).uzonesize = ((1 << media_info->zoneshift) / 128) * 125;
|
|
MEDIA_INFO(us).blockmask = MEDIA_INFO(us).blocksize - 1;
|
|
|
|
num_zones = MEDIA_INFO(us).capacity >> (MEDIA_INFO(us).zoneshift
|
|
+ MEDIA_INFO(us).blockshift + MEDIA_INFO(us).pageshift);
|
|
MEDIA_INFO(us).pba_to_lba = kcalloc(num_zones, sizeof(u16*), GFP_NOIO);
|
|
MEDIA_INFO(us).lba_to_pba = kcalloc(num_zones, sizeof(u16*), GFP_NOIO);
|
|
|
|
if (alauda_reset_media(us) != USB_STOR_XFER_GOOD)
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
/*
|
|
* Examines the media status and does the right thing when the media has gone,
|
|
* appeared, or changed.
|
|
*/
|
|
static int alauda_check_media(struct us_data *us)
|
|
{
|
|
struct alauda_info *info = (struct alauda_info *) us->extra;
|
|
unsigned char status[2];
|
|
int rc;
|
|
|
|
rc = alauda_get_media_status(us, status);
|
|
|
|
/* Check for no media or door open */
|
|
if ((status[0] & 0x80) || ((status[0] & 0x1F) == 0x10)
|
|
|| ((status[1] & 0x01) == 0)) {
|
|
US_DEBUGP("alauda_check_media: No media, or door open\n");
|
|
alauda_free_maps(&MEDIA_INFO(us));
|
|
info->sense_key = 0x02;
|
|
info->sense_asc = 0x3A;
|
|
info->sense_ascq = 0x00;
|
|
return USB_STOR_TRANSPORT_FAILED;
|
|
}
|
|
|
|
/* Check for media change */
|
|
if (status[0] & 0x08) {
|
|
US_DEBUGP("alauda_check_media: Media change detected\n");
|
|
alauda_free_maps(&MEDIA_INFO(us));
|
|
alauda_init_media(us);
|
|
|
|
info->sense_key = UNIT_ATTENTION;
|
|
info->sense_asc = 0x28;
|
|
info->sense_ascq = 0x00;
|
|
return USB_STOR_TRANSPORT_FAILED;
|
|
}
|
|
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
/*
|
|
* Checks the status from the 2nd status register
|
|
* Returns 3 bytes of status data, only the first is known
|
|
*/
|
|
static int alauda_check_status2(struct us_data *us)
|
|
{
|
|
int rc;
|
|
unsigned char command[] = {
|
|
ALAUDA_BULK_CMD, ALAUDA_BULK_GET_STATUS2,
|
|
0, 0, 0, 0, 3, 0, MEDIA_PORT(us)
|
|
};
|
|
unsigned char data[3];
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
|
|
command, 9, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
|
|
data, 3, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
US_DEBUGP("alauda_check_status2: %02X %02X %02X\n", data[0], data[1], data[2]);
|
|
if (data[0] & ALAUDA_STATUS_ERROR)
|
|
return USB_STOR_XFER_ERROR;
|
|
|
|
return USB_STOR_XFER_GOOD;
|
|
}
|
|
|
|
/*
|
|
* Gets the redundancy data for the first page of a PBA
|
|
* Returns 16 bytes.
|
|
*/
|
|
static int alauda_get_redu_data(struct us_data *us, u16 pba, unsigned char *data)
|
|
{
|
|
int rc;
|
|
unsigned char command[] = {
|
|
ALAUDA_BULK_CMD, ALAUDA_BULK_GET_REDU_DATA,
|
|
PBA_HI(pba), PBA_ZONE(pba), 0, PBA_LO(pba), 0, 0, MEDIA_PORT(us)
|
|
};
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
|
|
command, 9, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
return usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
|
|
data, 16, NULL);
|
|
}
|
|
|
|
/*
|
|
* Finds the first unused PBA in a zone
|
|
* Returns the absolute PBA of an unused PBA, or 0 if none found.
|
|
*/
|
|
static u16 alauda_find_unused_pba(struct alauda_media_info *info,
|
|
unsigned int zone)
|
|
{
|
|
u16 *pba_to_lba = info->pba_to_lba[zone];
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < info->zonesize; i++)
|
|
if (pba_to_lba[i] == UNDEF)
|
|
return (zone << info->zoneshift) + i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Reads the redundancy data for all PBA's in a zone
|
|
* Produces lba <--> pba mappings
|
|
*/
|
|
static int alauda_read_map(struct us_data *us, unsigned int zone)
|
|
{
|
|
unsigned char *data = us->iobuf;
|
|
int result;
|
|
int i, j;
|
|
unsigned int zonesize = MEDIA_INFO(us).zonesize;
|
|
unsigned int uzonesize = MEDIA_INFO(us).uzonesize;
|
|
unsigned int lba_offset, lba_real, blocknum;
|
|
unsigned int zone_base_lba = zone * uzonesize;
|
|
unsigned int zone_base_pba = zone * zonesize;
|
|
u16 *lba_to_pba = kcalloc(zonesize, sizeof(u16), GFP_NOIO);
|
|
u16 *pba_to_lba = kcalloc(zonesize, sizeof(u16), GFP_NOIO);
|
|
if (lba_to_pba == NULL || pba_to_lba == NULL) {
|
|
result = USB_STOR_TRANSPORT_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
US_DEBUGP("alauda_read_map: Mapping blocks for zone %d\n", zone);
|
|
|
|
/* 1024 PBA's per zone */
|
|
for (i = 0; i < zonesize; i++)
|
|
lba_to_pba[i] = pba_to_lba[i] = UNDEF;
|
|
|
|
for (i = 0; i < zonesize; i++) {
|
|
blocknum = zone_base_pba + i;
|
|
|
|
result = alauda_get_redu_data(us, blocknum, data);
|
|
if (result != USB_STOR_XFER_GOOD) {
|
|
result = USB_STOR_TRANSPORT_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
/* special PBAs have control field 0^16 */
|
|
for (j = 0; j < 16; j++)
|
|
if (data[j] != 0)
|
|
goto nonz;
|
|
pba_to_lba[i] = UNUSABLE;
|
|
US_DEBUGP("alauda_read_map: PBA %d has no logical mapping\n", blocknum);
|
|
continue;
|
|
|
|
nonz:
|
|
/* unwritten PBAs have control field FF^16 */
|
|
for (j = 0; j < 16; j++)
|
|
if (data[j] != 0xff)
|
|
goto nonff;
|
|
continue;
|
|
|
|
nonff:
|
|
/* normal PBAs start with six FFs */
|
|
if (j < 6) {
|
|
US_DEBUGP("alauda_read_map: PBA %d has no logical mapping: "
|
|
"reserved area = %02X%02X%02X%02X "
|
|
"data status %02X block status %02X\n",
|
|
blocknum, data[0], data[1], data[2], data[3],
|
|
data[4], data[5]);
|
|
pba_to_lba[i] = UNUSABLE;
|
|
continue;
|
|
}
|
|
|
|
if ((data[6] >> 4) != 0x01) {
|
|
US_DEBUGP("alauda_read_map: PBA %d has invalid address "
|
|
"field %02X%02X/%02X%02X\n",
|
|
blocknum, data[6], data[7], data[11], data[12]);
|
|
pba_to_lba[i] = UNUSABLE;
|
|
continue;
|
|
}
|
|
|
|
/* check even parity */
|
|
if (parity[data[6] ^ data[7]]) {
|
|
printk("alauda_read_map: Bad parity in LBA for block %d"
|
|
" (%02X %02X)\n", i, data[6], data[7]);
|
|
pba_to_lba[i] = UNUSABLE;
|
|
continue;
|
|
}
|
|
|
|
lba_offset = short_pack(data[7], data[6]);
|
|
lba_offset = (lba_offset & 0x07FF) >> 1;
|
|
lba_real = lba_offset + zone_base_lba;
|
|
|
|
/*
|
|
* Every 1024 physical blocks ("zone"), the LBA numbers
|
|
* go back to zero, but are within a higher block of LBA's.
|
|
* Also, there is a maximum of 1000 LBA's per zone.
|
|
* In other words, in PBA 1024-2047 you will find LBA 0-999
|
|
* which are really LBA 1000-1999. This allows for 24 bad
|
|
* or special physical blocks per zone.
|
|
*/
|
|
|
|
if (lba_offset >= uzonesize) {
|
|
printk("alauda_read_map: Bad low LBA %d for block %d\n",
|
|
lba_real, blocknum);
|
|
continue;
|
|
}
|
|
|
|
if (lba_to_pba[lba_offset] != UNDEF) {
|
|
printk("alauda_read_map: LBA %d seen for PBA %d and %d\n",
|
|
lba_real, lba_to_pba[lba_offset], blocknum);
|
|
continue;
|
|
}
|
|
|
|
pba_to_lba[i] = lba_real;
|
|
lba_to_pba[lba_offset] = blocknum;
|
|
continue;
|
|
}
|
|
|
|
MEDIA_INFO(us).lba_to_pba[zone] = lba_to_pba;
|
|
MEDIA_INFO(us).pba_to_lba[zone] = pba_to_lba;
|
|
result = 0;
|
|
goto out;
|
|
|
|
error:
|
|
kfree(lba_to_pba);
|
|
kfree(pba_to_lba);
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Checks to see whether we have already mapped a certain zone
|
|
* If we haven't, the map is generated
|
|
*/
|
|
static void alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone)
|
|
{
|
|
if (MEDIA_INFO(us).lba_to_pba[zone] == NULL
|
|
|| MEDIA_INFO(us).pba_to_lba[zone] == NULL)
|
|
alauda_read_map(us, zone);
|
|
}
|
|
|
|
/*
|
|
* Erases an entire block
|
|
*/
|
|
static int alauda_erase_block(struct us_data *us, u16 pba)
|
|
{
|
|
int rc;
|
|
unsigned char command[] = {
|
|
ALAUDA_BULK_CMD, ALAUDA_BULK_ERASE_BLOCK, PBA_HI(pba),
|
|
PBA_ZONE(pba), 0, PBA_LO(pba), 0x02, 0, MEDIA_PORT(us)
|
|
};
|
|
unsigned char buf[2];
|
|
|
|
US_DEBUGP("alauda_erase_block: Erasing PBA %d\n", pba);
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
|
|
command, 9, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
|
|
buf, 2, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
US_DEBUGP("alauda_erase_block: Erase result: %02X %02X\n",
|
|
buf[0], buf[1]);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Reads data from a certain offset page inside a PBA, including interleaved
|
|
* redundancy data. Returns (pagesize+64)*pages bytes in data.
|
|
*/
|
|
static int alauda_read_block_raw(struct us_data *us, u16 pba,
|
|
unsigned int page, unsigned int pages, unsigned char *data)
|
|
{
|
|
int rc;
|
|
unsigned char command[] = {
|
|
ALAUDA_BULK_CMD, ALAUDA_BULK_READ_BLOCK, PBA_HI(pba),
|
|
PBA_ZONE(pba), 0, PBA_LO(pba) + page, pages, 0, MEDIA_PORT(us)
|
|
};
|
|
|
|
US_DEBUGP("alauda_read_block: pba %d page %d count %d\n",
|
|
pba, page, pages);
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
|
|
command, 9, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
return usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
|
|
data, (MEDIA_INFO(us).pagesize + 64) * pages, NULL);
|
|
}
|
|
|
|
/*
|
|
* Reads data from a certain offset page inside a PBA, excluding redundancy
|
|
* data. Returns pagesize*pages bytes in data. Note that data must be big enough
|
|
* to hold (pagesize+64)*pages bytes of data, but you can ignore those 'extra'
|
|
* trailing bytes outside this function.
|
|
*/
|
|
static int alauda_read_block(struct us_data *us, u16 pba,
|
|
unsigned int page, unsigned int pages, unsigned char *data)
|
|
{
|
|
int i, rc;
|
|
unsigned int pagesize = MEDIA_INFO(us).pagesize;
|
|
|
|
rc = alauda_read_block_raw(us, pba, page, pages, data);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
/* Cut out the redundancy data */
|
|
for (i = 0; i < pages; i++) {
|
|
int dest_offset = i * pagesize;
|
|
int src_offset = i * (pagesize + 64);
|
|
memmove(data + dest_offset, data + src_offset, pagesize);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Writes an entire block of data and checks status after write.
|
|
* Redundancy data must be already included in data. Data should be
|
|
* (pagesize+64)*blocksize bytes in length.
|
|
*/
|
|
static int alauda_write_block(struct us_data *us, u16 pba, unsigned char *data)
|
|
{
|
|
int rc;
|
|
struct alauda_info *info = (struct alauda_info *) us->extra;
|
|
unsigned char command[] = {
|
|
ALAUDA_BULK_CMD, ALAUDA_BULK_WRITE_BLOCK, PBA_HI(pba),
|
|
PBA_ZONE(pba), 0, PBA_LO(pba), 32, 0, MEDIA_PORT(us)
|
|
};
|
|
|
|
US_DEBUGP("alauda_write_block: pba %d\n", pba);
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
|
|
command, 9, NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
rc = usb_stor_bulk_transfer_buf(us, info->wr_ep, data,
|
|
(MEDIA_INFO(us).pagesize + 64) * MEDIA_INFO(us).blocksize,
|
|
NULL);
|
|
if (rc != USB_STOR_XFER_GOOD)
|
|
return rc;
|
|
|
|
return alauda_check_status2(us);
|
|
}
|
|
|
|
/*
|
|
* Write some data to a specific LBA.
|
|
*/
|
|
static int alauda_write_lba(struct us_data *us, u16 lba,
|
|
unsigned int page, unsigned int pages,
|
|
unsigned char *ptr, unsigned char *blockbuffer)
|
|
{
|
|
u16 pba, lbap, new_pba;
|
|
unsigned char *bptr, *cptr, *xptr;
|
|
unsigned char ecc[3];
|
|
int i, result;
|
|
unsigned int uzonesize = MEDIA_INFO(us).uzonesize;
|
|
unsigned int zonesize = MEDIA_INFO(us).zonesize;
|
|
unsigned int pagesize = MEDIA_INFO(us).pagesize;
|
|
unsigned int blocksize = MEDIA_INFO(us).blocksize;
|
|
unsigned int lba_offset = lba % uzonesize;
|
|
unsigned int new_pba_offset;
|
|
unsigned int zone = lba / uzonesize;
|
|
|
|
alauda_ensure_map_for_zone(us, zone);
|
|
|
|
pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset];
|
|
if (pba == 1) {
|
|
/* Maybe it is impossible to write to PBA 1.
|
|
Fake success, but don't do anything. */
|
|
printk("alauda_write_lba: avoid writing to pba 1\n");
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
new_pba = alauda_find_unused_pba(&MEDIA_INFO(us), zone);
|
|
if (!new_pba) {
|
|
printk("alauda_write_lba: Out of unused blocks\n");
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
|
|
/* read old contents */
|
|
if (pba != UNDEF) {
|
|
result = alauda_read_block_raw(us, pba, 0,
|
|
blocksize, blockbuffer);
|
|
if (result != USB_STOR_XFER_GOOD)
|
|
return result;
|
|
} else {
|
|
memset(blockbuffer, 0, blocksize * (pagesize + 64));
|
|
}
|
|
|
|
lbap = (lba_offset << 1) | 0x1000;
|
|
if (parity[MSB_of(lbap) ^ LSB_of(lbap)])
|
|
lbap ^= 1;
|
|
|
|
/* check old contents and fill lba */
|
|
for (i = 0; i < blocksize; i++) {
|
|
bptr = blockbuffer + (i * (pagesize + 64));
|
|
cptr = bptr + pagesize;
|
|
nand_compute_ecc(bptr, ecc);
|
|
if (!nand_compare_ecc(cptr+13, ecc)) {
|
|
US_DEBUGP("Warning: bad ecc in page %d- of pba %d\n",
|
|
i, pba);
|
|
nand_store_ecc(cptr+13, ecc);
|
|
}
|
|
nand_compute_ecc(bptr + (pagesize / 2), ecc);
|
|
if (!nand_compare_ecc(cptr+8, ecc)) {
|
|
US_DEBUGP("Warning: bad ecc in page %d+ of pba %d\n",
|
|
i, pba);
|
|
nand_store_ecc(cptr+8, ecc);
|
|
}
|
|
cptr[6] = cptr[11] = MSB_of(lbap);
|
|
cptr[7] = cptr[12] = LSB_of(lbap);
|
|
}
|
|
|
|
/* copy in new stuff and compute ECC */
|
|
xptr = ptr;
|
|
for (i = page; i < page+pages; i++) {
|
|
bptr = blockbuffer + (i * (pagesize + 64));
|
|
cptr = bptr + pagesize;
|
|
memcpy(bptr, xptr, pagesize);
|
|
xptr += pagesize;
|
|
nand_compute_ecc(bptr, ecc);
|
|
nand_store_ecc(cptr+13, ecc);
|
|
nand_compute_ecc(bptr + (pagesize / 2), ecc);
|
|
nand_store_ecc(cptr+8, ecc);
|
|
}
|
|
|
|
result = alauda_write_block(us, new_pba, blockbuffer);
|
|
if (result != USB_STOR_XFER_GOOD)
|
|
return result;
|
|
|
|
new_pba_offset = new_pba - (zone * zonesize);
|
|
MEDIA_INFO(us).pba_to_lba[zone][new_pba_offset] = lba;
|
|
MEDIA_INFO(us).lba_to_pba[zone][lba_offset] = new_pba;
|
|
US_DEBUGP("alauda_write_lba: Remapped LBA %d to PBA %d\n",
|
|
lba, new_pba);
|
|
|
|
if (pba != UNDEF) {
|
|
unsigned int pba_offset = pba - (zone * zonesize);
|
|
result = alauda_erase_block(us, pba);
|
|
if (result != USB_STOR_XFER_GOOD)
|
|
return result;
|
|
MEDIA_INFO(us).pba_to_lba[zone][pba_offset] = UNDEF;
|
|
}
|
|
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
/*
|
|
* Read data from a specific sector address
|
|
*/
|
|
static int alauda_read_data(struct us_data *us, unsigned long address,
|
|
unsigned int sectors)
|
|
{
|
|
unsigned char *buffer;
|
|
u16 lba, max_lba;
|
|
unsigned int page, len, index, offset;
|
|
unsigned int blockshift = MEDIA_INFO(us).blockshift;
|
|
unsigned int pageshift = MEDIA_INFO(us).pageshift;
|
|
unsigned int blocksize = MEDIA_INFO(us).blocksize;
|
|
unsigned int pagesize = MEDIA_INFO(us).pagesize;
|
|
unsigned int uzonesize = MEDIA_INFO(us).uzonesize;
|
|
int result;
|
|
|
|
/*
|
|
* Since we only read in one block at a time, we have to create
|
|
* a bounce buffer and move the data a piece at a time between the
|
|
* bounce buffer and the actual transfer buffer.
|
|
* We make this buffer big enough to hold temporary redundancy data,
|
|
* which we use when reading the data blocks.
|
|
*/
|
|
|
|
len = min(sectors, blocksize) * (pagesize + 64);
|
|
buffer = kmalloc(len, GFP_NOIO);
|
|
if (buffer == NULL) {
|
|
printk("alauda_read_data: Out of memory\n");
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
|
|
/* Figure out the initial LBA and page */
|
|
lba = address >> blockshift;
|
|
page = (address & MEDIA_INFO(us).blockmask);
|
|
max_lba = MEDIA_INFO(us).capacity >> (blockshift + pageshift);
|
|
|
|
result = USB_STOR_TRANSPORT_GOOD;
|
|
index = offset = 0;
|
|
|
|
while (sectors > 0) {
|
|
unsigned int zone = lba / uzonesize; /* integer division */
|
|
unsigned int lba_offset = lba - (zone * uzonesize);
|
|
unsigned int pages;
|
|
u16 pba;
|
|
alauda_ensure_map_for_zone(us, zone);
|
|
|
|
/* Not overflowing capacity? */
|
|
if (lba >= max_lba) {
|
|
US_DEBUGP("Error: Requested lba %u exceeds "
|
|
"maximum %u\n", lba, max_lba);
|
|
result = USB_STOR_TRANSPORT_ERROR;
|
|
break;
|
|
}
|
|
|
|
/* Find number of pages we can read in this block */
|
|
pages = min(sectors, blocksize - page);
|
|
len = pages << pageshift;
|
|
|
|
/* Find where this lba lives on disk */
|
|
pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset];
|
|
|
|
if (pba == UNDEF) { /* this lba was never written */
|
|
US_DEBUGP("Read %d zero pages (LBA %d) page %d\n",
|
|
pages, lba, page);
|
|
|
|
/* This is not really an error. It just means
|
|
that the block has never been written.
|
|
Instead of returning USB_STOR_TRANSPORT_ERROR
|
|
it is better to return all zero data. */
|
|
|
|
memset(buffer, 0, len);
|
|
} else {
|
|
US_DEBUGP("Read %d pages, from PBA %d"
|
|
" (LBA %d) page %d\n",
|
|
pages, pba, lba, page);
|
|
|
|
result = alauda_read_block(us, pba, page, pages, buffer);
|
|
if (result != USB_STOR_TRANSPORT_GOOD)
|
|
break;
|
|
}
|
|
|
|
/* Store the data in the transfer buffer */
|
|
usb_stor_access_xfer_buf(buffer, len, us->srb,
|
|
&index, &offset, TO_XFER_BUF);
|
|
|
|
page = 0;
|
|
lba++;
|
|
sectors -= pages;
|
|
}
|
|
|
|
kfree(buffer);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Write data to a specific sector address
|
|
*/
|
|
static int alauda_write_data(struct us_data *us, unsigned long address,
|
|
unsigned int sectors)
|
|
{
|
|
unsigned char *buffer, *blockbuffer;
|
|
unsigned int page, len, index, offset;
|
|
unsigned int blockshift = MEDIA_INFO(us).blockshift;
|
|
unsigned int pageshift = MEDIA_INFO(us).pageshift;
|
|
unsigned int blocksize = MEDIA_INFO(us).blocksize;
|
|
unsigned int pagesize = MEDIA_INFO(us).pagesize;
|
|
u16 lba, max_lba;
|
|
int result;
|
|
|
|
/*
|
|
* Since we don't write the user data directly to the device,
|
|
* we have to create a bounce buffer and move the data a piece
|
|
* at a time between the bounce buffer and the actual transfer buffer.
|
|
*/
|
|
|
|
len = min(sectors, blocksize) * pagesize;
|
|
buffer = kmalloc(len, GFP_NOIO);
|
|
if (buffer == NULL) {
|
|
printk("alauda_write_data: Out of memory\n");
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
|
|
/*
|
|
* We also need a temporary block buffer, where we read in the old data,
|
|
* overwrite parts with the new data, and manipulate the redundancy data
|
|
*/
|
|
blockbuffer = kmalloc((pagesize + 64) * blocksize, GFP_NOIO);
|
|
if (blockbuffer == NULL) {
|
|
printk("alauda_write_data: Out of memory\n");
|
|
kfree(buffer);
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
|
|
/* Figure out the initial LBA and page */
|
|
lba = address >> blockshift;
|
|
page = (address & MEDIA_INFO(us).blockmask);
|
|
max_lba = MEDIA_INFO(us).capacity >> (pageshift + blockshift);
|
|
|
|
result = USB_STOR_TRANSPORT_GOOD;
|
|
index = offset = 0;
|
|
|
|
while (sectors > 0) {
|
|
/* Write as many sectors as possible in this block */
|
|
unsigned int pages = min(sectors, blocksize - page);
|
|
len = pages << pageshift;
|
|
|
|
/* Not overflowing capacity? */
|
|
if (lba >= max_lba) {
|
|
US_DEBUGP("alauda_write_data: Requested lba %u exceeds "
|
|
"maximum %u\n", lba, max_lba);
|
|
result = USB_STOR_TRANSPORT_ERROR;
|
|
break;
|
|
}
|
|
|
|
/* Get the data from the transfer buffer */
|
|
usb_stor_access_xfer_buf(buffer, len, us->srb,
|
|
&index, &offset, FROM_XFER_BUF);
|
|
|
|
result = alauda_write_lba(us, lba, page, pages, buffer,
|
|
blockbuffer);
|
|
if (result != USB_STOR_TRANSPORT_GOOD)
|
|
break;
|
|
|
|
page = 0;
|
|
lba++;
|
|
sectors -= pages;
|
|
}
|
|
|
|
kfree(buffer);
|
|
kfree(blockbuffer);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Our interface with the rest of the world
|
|
*/
|
|
|
|
static void alauda_info_destructor(void *extra)
|
|
{
|
|
struct alauda_info *info = (struct alauda_info *) extra;
|
|
int port;
|
|
|
|
if (!info)
|
|
return;
|
|
|
|
for (port = 0; port < 2; port++) {
|
|
struct alauda_media_info *media_info = &info->port[port];
|
|
|
|
alauda_free_maps(media_info);
|
|
kfree(media_info->lba_to_pba);
|
|
kfree(media_info->pba_to_lba);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize alauda_info struct and find the data-write endpoint
|
|
*/
|
|
int init_alauda(struct us_data *us)
|
|
{
|
|
struct alauda_info *info;
|
|
struct usb_host_interface *altsetting = us->pusb_intf->cur_altsetting;
|
|
nand_init_ecc();
|
|
|
|
us->extra = kzalloc(sizeof(struct alauda_info), GFP_NOIO);
|
|
if (!us->extra) {
|
|
US_DEBUGP("init_alauda: Gah! Can't allocate storage for"
|
|
"alauda info struct!\n");
|
|
return USB_STOR_TRANSPORT_ERROR;
|
|
}
|
|
info = (struct alauda_info *) us->extra;
|
|
us->extra_destructor = alauda_info_destructor;
|
|
|
|
info->wr_ep = usb_sndbulkpipe(us->pusb_dev,
|
|
altsetting->endpoint[0].desc.bEndpointAddress
|
|
& USB_ENDPOINT_NUMBER_MASK);
|
|
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
int alauda_transport(struct scsi_cmnd *srb, struct us_data *us)
|
|
{
|
|
int rc;
|
|
struct alauda_info *info = (struct alauda_info *) us->extra;
|
|
unsigned char *ptr = us->iobuf;
|
|
static unsigned char inquiry_response[36] = {
|
|
0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
if (srb->cmnd[0] == INQUIRY) {
|
|
US_DEBUGP("alauda_transport: INQUIRY. "
|
|
"Returning bogus response.\n");
|
|
memcpy(ptr, inquiry_response, sizeof(inquiry_response));
|
|
fill_inquiry_response(us, ptr, 36);
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
if (srb->cmnd[0] == TEST_UNIT_READY) {
|
|
US_DEBUGP("alauda_transport: TEST_UNIT_READY.\n");
|
|
return alauda_check_media(us);
|
|
}
|
|
|
|
if (srb->cmnd[0] == READ_CAPACITY) {
|
|
unsigned int num_zones;
|
|
unsigned long capacity;
|
|
|
|
rc = alauda_check_media(us);
|
|
if (rc != USB_STOR_TRANSPORT_GOOD)
|
|
return rc;
|
|
|
|
num_zones = MEDIA_INFO(us).capacity >> (MEDIA_INFO(us).zoneshift
|
|
+ MEDIA_INFO(us).blockshift + MEDIA_INFO(us).pageshift);
|
|
|
|
capacity = num_zones * MEDIA_INFO(us).uzonesize
|
|
* MEDIA_INFO(us).blocksize;
|
|
|
|
/* Report capacity and page size */
|
|
((__be32 *) ptr)[0] = cpu_to_be32(capacity - 1);
|
|
((__be32 *) ptr)[1] = cpu_to_be32(512);
|
|
|
|
usb_stor_set_xfer_buf(ptr, 8, srb);
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
if (srb->cmnd[0] == READ_10) {
|
|
unsigned int page, pages;
|
|
|
|
rc = alauda_check_media(us);
|
|
if (rc != USB_STOR_TRANSPORT_GOOD)
|
|
return rc;
|
|
|
|
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]);
|
|
|
|
US_DEBUGP("alauda_transport: READ_10: page %d pagect %d\n",
|
|
page, pages);
|
|
|
|
return alauda_read_data(us, page, pages);
|
|
}
|
|
|
|
if (srb->cmnd[0] == WRITE_10) {
|
|
unsigned int page, pages;
|
|
|
|
rc = alauda_check_media(us);
|
|
if (rc != USB_STOR_TRANSPORT_GOOD)
|
|
return rc;
|
|
|
|
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]);
|
|
|
|
US_DEBUGP("alauda_transport: WRITE_10: page %d pagect %d\n",
|
|
page, pages);
|
|
|
|
return alauda_write_data(us, page, pages);
|
|
}
|
|
|
|
if (srb->cmnd[0] == REQUEST_SENSE) {
|
|
US_DEBUGP("alauda_transport: REQUEST_SENSE.\n");
|
|
|
|
memset(ptr, 0, 18);
|
|
ptr[0] = 0xF0;
|
|
ptr[2] = info->sense_key;
|
|
ptr[7] = 11;
|
|
ptr[12] = info->sense_asc;
|
|
ptr[13] = info->sense_ascq;
|
|
usb_stor_set_xfer_buf(ptr, 18, srb);
|
|
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) {
|
|
/* sure. whatever. not like we can stop the user from popping
|
|
the media out of the device (no locking doors, etc) */
|
|
return USB_STOR_TRANSPORT_GOOD;
|
|
}
|
|
|
|
US_DEBUGP("alauda_transport: Gah! Unknown command: %d (0x%x)\n",
|
|
srb->cmnd[0], srb->cmnd[0]);
|
|
info->sense_key = 0x05;
|
|
info->sense_asc = 0x20;
|
|
info->sense_ascq = 0x00;
|
|
return USB_STOR_TRANSPORT_FAILED;
|
|
}
|
|
|