#include "usb_scsi.h"
#include <malloc.h>
#include "device_info.h"
#include "proto_module.h"
#define FREECOM_MODULE_NAME "freecom"
#define FREECOM_PROTOCOL_MODULE_NAME \
MODULE_PREFIX FREECOM_MODULE_NAME PROTOCOL_SUFFIX
typedef struct {
uint8 type;
#define FCMT_ATAPI 0x21
#define FCMT_STATUS 0x20
#define FCMT_IN 0x81
#define FCMT_OUT 0x01
#define FCMT_REG_IN 0xC0
#define FCMT_REG_OUT 0x40
#define FCMR_DATA 0x10
#define FCMR_CMD 0x16
#define FCMR_INT 0x17
uint8 timeout;
#define FCMTO_DEF 0xfe
#define FCMTO_TU 0x14
union{
uint8 cmd[12];
uint32 count;
uint16 reg_val;
uint8 pad[62];
}_PACKED data;
}_PACKED fcm_command;
typedef struct {
uint8 status;
#define FCMS_BSY 0x80
#define FCMS_DRDY 0x40
#define FCMS_DMA 0x20
#define FCMS_SEEK 0x10
#define FCMS_DRQ 0x08
#define FCMS_CORR 0x04
#define FCMS_INTR 0x02
#define FCMS_CHECK 0x01
uint8 reason;
#define FCMR_REL 0x04
#define FCMR_IO 0x02
#define FCMR_COD 0x01
uint16 count;
}_PACKED fcm_status;
#define FREECOM_USB_TIMEOUT 30000000
void trace_status(usb_device_info *udi, const fcm_status *st)
{
char ch_status[] = "BDFSRCIE";
char ch_reason[] = ".....RIC";
int i = 0;
for(; i < 8; i++){
if(!(st->status & (1 << i)))
ch_status[7 - i] = '.';
if(!(st->reason & (1 << i)))
ch_reason[7 - i] = '.';
}
PTRACE(udi, "FCM:Status:{%s; Reason:%s; Count:%d}\n",
ch_status, ch_reason, st->count);
}
status_t
freecom_initialize(usb_device_info *udi)
{
status_t status = B_OK;
#define INIT_BUFFER_SIZE 0x20
char buffer[INIT_BUFFER_SIZE + 1];
size_t len = 0;
if(B_OK != (status = (*udi->usb_m->send_request)(udi->device,
USB_REQTYPE_DEVICE_IN | USB_REQTYPE_VENDOR,
0x4c, 0x4346, 0x0, INIT_BUFFER_SIZE,
buffer, &len)))
{
PTRACE_ALWAYS(udi, "FCM:init[%d]: init failed: %08x\n", udi->dev_num, status);
} else {
buffer[len] = 0;
PTRACE(udi, "FCM:init[%d]: init '%s' OK\n", udi->dev_num, buffer);
}
if(B_OK != (status = (*udi->usb_m->send_request)(udi->device,
USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_VENDOR,
0x4d, 0x24d8, 0x0, 0, 0, &len)))
{
PTRACE_ALWAYS(udi, "FCM:init[%d]: reset on failed:%08x\n", udi->dev_num, status);
}
snooze(250000);
if(B_OK != (status = (*udi->usb_m->send_request)(udi->device,
USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_VENDOR,
0x4d, 0x24f8, 0x0, 0, 0, &len)))
{
PTRACE_ALWAYS(udi, "FCM:init[%d]: reset off failed:%08x\n", udi->dev_num, status);
}
snooze(3 * 1000000);
return status;
}
status_t
freecom_reset(usb_device_info *udi)
{
status_t status = B_OK;
return status;
}
static void usb_callback(void *cookie,
uint32 status,
void *data,
uint32 actual_len)
{
if(cookie){
usb_device_info *udi = (usb_device_info *)cookie;
udi->status = status;
udi->data = data;
udi->actual_len = actual_len;
if(udi->status != B_CANCELED)
release_sem(udi->trans_sem);
}
}
static status_t
queue_bulk(usb_device_info *udi,
void *buffer,
size_t len,
bool b_in)
{
status_t status = B_OK;
usb_pipe pipe = b_in ? udi->pipe_in : udi->pipe_out;
status = (*udi->usb_m->queue_bulk)(pipe, buffer, len, usb_callback, udi);
if(status != B_OK){
PTRACE_ALWAYS(udi, "FCM:queue_bulk:failed:%08x\n", status);
} else {
status = acquire_sem_etc(udi->trans_sem, 1, B_RELATIVE_TIMEOUT, FREECOM_USB_TIMEOUT);
if(status != B_OK){
PTRACE_ALWAYS(udi, "FCM:queue_bulk:acquire_sem_etc failed:%08x\n", status);
(*udi->usb_m->cancel_queued_transfers)(pipe);
}
}
return status;
}
static status_t
write_command(usb_device_info *udi, uint8 type, uint8 *cmd, uint8 timeout)
{
status_t status = B_OK;
fcm_command fc = {
.type = type,
.timeout = FCMTO_DEF,
};
if(0 != cmd){
memcpy(fc.data.cmd, cmd, sizeof(fc.data.cmd));
if(cmd[0] == 0x00){
fc.timeout = FCMTO_TU;
}
}
PTRACE(udi, "FCM:FC:{Type:0x%02x; TO:%d;}\n", fc.type, fc.timeout);
udi->trace_bytes("FCM:FC::Cmd:\n", fc.data.cmd, sizeof(fc.data.cmd));
udi->trace_bytes("FCM:FC::Pad:\n", fc.data.pad, sizeof(fc.data.pad));
PTRACE(udi,"sizeof:%d\n",sizeof(fc));
status = queue_bulk(udi, &fc, sizeof(fc), false);
if(status != B_OK || udi->status != B_OK){
PTRACE_ALWAYS(udi, "FCM:write_command failed:status:%08x usb status:%08x\n",
status, udi->status);
status = B_CMD_WIRE_FAILED;
}
return status;
}
static status_t
read_status(usb_device_info *udi, fcm_status *fst)
{
status_t status = B_OK;
do{
memset(fst, 0, sizeof(fcm_status));
status = queue_bulk(udi, fst, sizeof(fcm_status), true);
if(status != B_OK || udi->status != B_OK){
PTRACE_ALWAYS(udi, "FCM:read_status failed:"
"status:%08x usb status:%08x\n", status, udi->status);
status = B_CMD_WIRE_FAILED;
break;
}
if(udi->actual_len != sizeof(fcm_status)){
PTRACE_ALWAYS(udi, "FCM:read_status failed:requested:%d, readed %d bytes\n",
sizeof(fcm_status), udi->actual_len);
status = B_CMD_WIRE_FAILED;
break;
}
trace_status(udi, fst);
if(fst->status & FCMS_BSY){
PTRACE(udi, "FCM:read_status:Timeout. Poll device with another status request.\n");
if(B_OK != (status = write_command(udi, FCMT_STATUS, 0, 0))){
PTRACE_ALWAYS(udi, "FCM:read_status failed:write_command_block failed %08x\n", status);
break;
}
}
}while(fst->status & FCMS_BSY);
return status;
}
static status_t
request_transfer(usb_device_info *udi, uint8 type, uint32 length, uint8 timeout)
{
status_t status = B_OK;
fcm_command fc = {
.type = type,
.timeout = timeout,
};
fc.data.count = length;
PTRACE(udi, "FCM:FC:{Type:0x%02x; TO:%d; Count:%d}\n", fc.type, fc.timeout, fc.data.count);
udi->trace_bytes("FCM:FC::Pad:\n", fc.data.pad, sizeof(fc.data.pad));
PTRACE(udi,"sizeof:%d\n",sizeof(fc));
status = queue_bulk(udi, &fc, sizeof(fc), false);
if(status != B_OK || udi->status != B_OK){
PTRACE_ALWAYS(udi, "FCM:request_transfer:fc send failed:"
"status:%08x usb status:%08x\n", status, udi->status);
status = B_CMD_WIRE_FAILED;
}
return status;
}
static status_t
transfer_sg(usb_device_info *udi,
iovec *data_sg,
int32 sglist_count,
int32 offset,
int32 *block_len,
bool b_in)
{
status_t status = B_OK;
usb_pipe pipe = (b_in) ? udi->pipe_in : udi->pipe_out;
int32 off = offset;
int32 to_xfer = *block_len;
int i = 0;
for(; i < sglist_count && to_xfer > 0; i++) {
if(off < data_sg[i].iov_len) {
int len = min(to_xfer, (data_sg[i].iov_len - off));
char *buf = ((char *)data_sg[i].iov_base) + off;
if(B_OK == (status = (*udi->usb_m->queue_bulk)(pipe, buf, len, usb_callback, udi))) {
status = acquire_sem_etc(udi->trans_sem, 1, B_RELATIVE_TIMEOUT, FREECOM_USB_TIMEOUT);
if(status == B_OK){
status = udi->status;
if(B_OK != status){
PTRACE_ALWAYS(udi, "FCM:transfer_sg:usb transfer status is not OK:%08x\n", status);
}
} else {
PTRACE_ALWAYS(udi, "FCM:transfer_sg:acquire_sem_etc failed:%08x\n", status);
goto finalize;
}
} else {
PTRACE_ALWAYS(udi, "FCM:transfer_sg:queue_bulk failed:%08x\n", status);
goto finalize;
}
if(len != udi->actual_len){
PTRACE_ALWAYS(udi, "FCM:transfer_sg:WARNING!!!Length reported:%d required:%d\n",
udi->actual_len, len);
}
to_xfer -= len;
off = 0;
} else {
off -= data_sg[i].iov_len;
}
}
finalize:
*block_len -= to_xfer;
return (B_OK != status) ? B_CMD_WIRE_FAILED : status;
}
static status_t
transfer_data(usb_device_info *udi,
iovec *data_sg,
int32 sglist_count,
int32 transfer_length,
int32 *residue,
fcm_status *fst,
bool b_in)
{
status_t status = B_OK;
int32 readed_len = 0;
uint8 xfer_type = b_in ? FCMT_IN : FCMT_OUT;
int32 block_len = (FCMR_COD == (fst->reason & FCMR_COD)) ?
transfer_length : fst->count;
if(block_len > transfer_length){
PTRACE_ALWAYS(udi, "FCM:transfer_data:TRUNCATED! "
"requested:%d available:%d.\n", transfer_length, block_len);
block_len = transfer_length;
}
while(readed_len < transfer_length && block_len > 0){
if(B_OK != (status = request_transfer(udi, xfer_type, block_len, 0))){
goto finalize;
}
if(FCMS_DRQ != (fst->status & FCMS_DRQ)){
PTRACE_ALWAYS(udi, "FCM:transfer_data:device doesn't want to transfer anything!!!\n");
status = B_CMD_FAILED;
goto finalize;
}
if(!((fst->reason & FCMR_REL) == 0 &&
(fst->reason & FCMR_IO) == ((b_in) ? FCMR_IO : 0) &&
(fst->reason & FCMR_COD) == 0))
{
PTRACE_ALWAYS(udi, "FCM:transfer_data:device doesn't ready to transfer?\n");
status = B_CMD_FAILED;
goto finalize;
}
if(B_OK != (status = transfer_sg(udi, data_sg,
sglist_count, readed_len, &block_len, b_in)))
{
goto finalize;
}
if(B_OK != (status = read_status(udi, fst))){
goto finalize;
}
if(fst->status & FCMS_CHECK){
PTRACE(udi, "FCM:transfer_data:data transfer failed?\n", fst->status);
status = B_CMD_FAILED;
goto finalize;
}
readed_len += block_len;
if((fst->reason & FCMR_COD) == FCMR_COD){
#if 0
if(readed_len < transfer_length){
PTRACE_ALWAYS(udi, "FCM:transfer_data:Device had LESS data that we wanted.\n");
}
#endif
break;
} else {
if(readed_len >= transfer_length){
PTRACE_ALWAYS(udi, "FCM:transfer_data:Device had MORE data that we wanted.\n");
break;
}
block_len = fst->count;
}
}
*residue -= readed_len;
if(*residue < 0)
*residue = 0;
PTRACE(udi,"FCM:transfer_data:was requested:%d, residue:%d\n", transfer_length, *residue);
finalize:
return status;
}
void
freecom_transfer(usb_device_info *udi,
uint8 *cmd,
uint8 cmdlen,
iovec*sg_data,
int32 sg_count,
int32 transfer_len,
EDirection dir,
CCB_SCSIIO *ccbio,
ud_transfer_callback cb)
{
status_t command_status = B_CMD_WIRE_FAILED;
int32 residue = transfer_len;
fcm_status fst = {0};
#if 1
if((MODE_SENSE_10 == cmd[0] && 0x0e == cmd[2]) ||
(MODE_SELECT_10 == cmd[0] && 0x10 == cmd[1]))
{
uint8 *cmd_org = 0;
if(ccbio->cam_ch.cam_flags & CAM_CDB_POINTER){
cmd_org = ccbio->cam_cdb_io.cam_cdb_ptr;
}else{
cmd_org = ccbio->cam_cdb_io.cam_cdb_bytes;
}
if(cmd_org[0] != cmd[0]){
if(MODE_SENSE_10 == cmd[0]){
scsi_mode_param_header_10 *hdr = (scsi_mode_param_header_10*)sg_data[0].iov_base;
memset(hdr, 0, sizeof(scsi_mode_param_header_10));
hdr->mode_data_len[1] = 6;
PTRACE(udi, "FCM:freecom_transfer:MODE_SENSE_10 ignored %02x->02x\n", cmd_org[0], cmd[0]);
} else {
PTRACE(udi, "FCM:freecom_transfer:MODE_SELECT_10 ignored %02x->%02x\n", cmd_org[0], cmd[0]);
}
command_status = B_OK;
goto finalize;
}
}
#endif
if(B_OK != (command_status = write_command(udi, FCMT_ATAPI, cmd, 0))){
PTRACE_ALWAYS(udi, "FCM:freecom_transfer:"
"send command failed. Status:%08x\n", command_status);
goto finalize;
}
if(B_OK != (command_status = read_status(udi, &fst))){
PTRACE_ALWAYS(udi, "FCM:freecom_transfer:"
"read status failed. Status:%08x\n", command_status);
goto finalize;
}
if(fst.status & FCMS_CHECK){
PTRACE_ALWAYS(udi, "FCM:freecom_transfer:FST.Status is not OK\n");
command_status = B_CMD_FAILED;
goto finalize;
}
if(transfer_len != 0x0 && dir != eDirNone){
command_status = transfer_data(udi, sg_data, sg_count, transfer_len,
&residue, &fst, (eDirIn == dir));
}
finalize:
cb(udi, ccbio, residue, command_status);
}
static status_t
std_ops(int32 op, ...)
{
switch(op) {
case B_MODULE_INIT:
return B_OK;
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
static protocol_module_info freecom_protocol_module = {
{ FREECOM_PROTOCOL_MODULE_NAME, 0, std_ops },
freecom_initialize,
freecom_reset,
freecom_transfer,
};
_EXPORT protocol_module_info *modules[] = {
&freecom_protocol_module,
NULL
};