/* -*- linux-c -*- * Copyright (c) 2004-2006 Winbond Electronics Corp. All rights reserved. * * The contents of this file are subject to the Open * Software License version 1.1 that can be found at * http://www.opensource.org/licenses/osl-1.1.txt and is included herein * by reference. * * Alternatively, the contents of this file may be used under the terms * of the GNU General Public License version 2 (the "GPL") as distributed * in the kernel source COPYING file, in which case the provisions of * the GPL are applicable instead of the above. If you wish to allow * the use of your version of this file only under the terms of the * GPL and not to allow others to use your version of this file under * the OSL, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the GPL. * If you do not delete the provisions above, a recipient may use your * version of this file under either the OSL or the GPL. * Maintained by: Weiming Shuai Jin Yuan Mu Dezheng Shen $Id: wbscsi.c,v 1.8.2.7 2006/06/22 06:06:12 sjin Exp $ $Name: $ --- don't modify lines above --- Description: Enviornment: (1) Redhat 9 kernel (2) original Linux kernel 32-bit 2.6.9-1.667 on Intel */ const static char *WKEEP_THIS_LINE = "$Id: wbscsi.c,v 1.8.2.7 2006/06/22 06:06:12 sjin Exp $"; #include "wbscsi.h" #include "wbvirtualdev.h" wb_host_data_t *g_wb_host_data; struct Scsi_Host *g_wb_host; static int __init wb_init_host_data(wb_host_data_t * host_data) { int ret; ENTER(); ret = 0; memset(host_data, 0, sizeof(wb_host_data_t)); host_data->cmd_wrapper = &host_data->space_cmd_wrapper; host_data->semaphore = &host_data->space_semaphore; sema_init(host_data->semaphore, 0); host_data->sys_state = WB_SYS_STATE_TEST; host_data->medium_valid = FALSE; host_data->notice_upper_scsi_layer = FALSE; //FIXME if (0 == WB_UNCHECKED_ISA_DMA) { // PCI DMA } else { // ISA DMA } LEAVE(); return ret; } static void *__init wb_detect(Scsi_Host_Template * tmpt) { void *pdev; int num_host; struct Scsi_Host *host; wb_host_data_t *host_data; ENTER(); pdev = NULL; num_host = 0; tmpt->proc_name = "Winbond Storage Driver"; // now support only ONE device // register Winbond controller as a SCSI host if (0 == (host = scsi_host_alloc(tmpt, sizeof(wb_host_data_t)))) { WB_PRINTK_ERROR("can't allocate scsi host \n"); goto lab_end; } g_wb_host = host; host->max_id = WB_MAX_TARGETS; host->max_lun = WB_MAX_LUNS; host->this_id = tmpt->this_id; host->max_sectors = WB_MAX_SECTORS; host_data = (wb_host_data_t *) host->hostdata; g_wb_host_data = host_data; if (wb_init_host_data(host_data)) { WB_PRINTK_ERROR("initialize wb_init_host_data failed!\n"); goto lab_unregister_host; } pdev = wbvdev_detect(); if (NULL == pdev) { goto lab_unregister_host; } num_host = 1; kernel_thread(wb_helper_kernel_thread, host, CLONE_FS | CLONE_FILES); schedule(); goto lab_end; lab_unregister_host: scsi_host_put(host); lab_end: LEAVE(); return pdev; } static void wb_stop_helper_kernel_thread(wb_host_data_t * host_data) { int times; ENTER(); times = 0; // protocol to tell helper thread to terminate host_data->sys_state = WB_SYS_STATE_RMMOD; up(host_data->semaphore); schedule(); while ((WB_SYS_STATE_RMMOD_ACK != host_data->sys_state) && (times++ != 1000)) { mdelay(100); WB_PRINTK_THREAD("waiting for helper thread to terminate\n"); schedule(); } LEAVE(); } static int __exit wb_release(struct Scsi_Host *host) { wb_host_data_t *host_data; ENTER(); host_data = (wb_host_data_t *) host->hostdata; // terminate the helper kernel thread wb_stop_helper_kernel_thread(host_data); wbvdev_release(); scsi_remove_host(host); LEAVE(); return 0; } static const char *wb_info(struct Scsi_Host *host) { ENTER(); LEAVE(); return WKEEP_THIS_LINE; } static int wb_process_scsi_cmd(Scsi_Cmnd * cmd) { unsigned long flags; wb_host_data_t *host_data; ENTER(); host_data = g_wb_host_data; wb_chk_valid_medium(host_data); wb_unit_ready(cmd); switch (cmd->cmnd[0]) { case START_STOP: WB_PRINTK_SCSI("START_STOP\n"); wb_scsi_start_stop(cmd); break; case REQUEST_SENSE: WB_PRINTK_SCSI("REQUEST_SENSE\n"); wb_scsi_request_sense(cmd); break; case ALLOW_MEDIUM_REMOVAL: WB_PRINTK_SCSI("ALLOW_MEDIUM_REMOVAL\n"); wb_scsi_allow_medium_removal(cmd); break; case TEST_UNIT_READY: WB_PRINTK_SCSI("TEST_UNIT_READY\n"); wb_scsi_test_unit_ready(cmd); break; case INQUIRY: WB_PRINTK_SCSI("INQUIRY\n"); wb_scsi_inquiry(cmd); break; case READ_6: WB_PRINTK_ERROR_SCSI("READ_6\n"); break; case READ_10: WB_PRINTK_SCSI("READ_10\n"); wb_scsi_read_wrt(cmd, TRUE); break; case WRITE_6: WB_PRINTK_ERROR_SCSI("WRITE_6\n"); break; case WRITE_10: WB_PRINTK_SCSI("WRITE_10\n"); wb_scsi_read_wrt(cmd, FALSE); break; // MODE_SENSE is 6-byte command case MODE_SENSE: WB_PRINTK_SCSI("MODE_SENSE\n"); wb_scsi_mode_sense(cmd); break; case READ_CAPACITY: WB_PRINTK_SCSI("READ_CAPACITY\n"); wb_scsi_read_capacity(cmd); break; case FORMAT_UNIT: WB_PRINTK_SCSI("FORMAT_UNIT\n"); wb_scsi_format_unit(cmd); break; // haven't handled all necessary cases yet default: WB_PRINTK_ERROR_SCSI("UNKNOWN command(0x%x)\n", cmd->cmnd[0]); wb_make_sense_buffer(cmd, ILLEGAL_REQUEST, 0x20, 0x00); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); break; } // end switch spin_lock_irqsave(g_wb_host->host_lock, flags); cmd->scsi_done(cmd); spin_unlock_irqrestore(g_wb_host->host_lock, flags); LEAVE(); return 0; } static int wb_helper_kernel_thread(void *param) { struct task_struct *current_task; wb_host_data_t *host_data; struct Scsi_Host *host; ENTER(); host = (struct Scsi_Host *)param; host_data = g_wb_host_data; current_task = current; lock_kernel(); daemonize("helperthread"); current->flags |= PF_NOFREEZE; unlock_kernel(); strcpy(current_task->comm, "tcsKernel"); while (1) { down(host_data->semaphore); WB_PRINTK_THREAD("helper kernel thread is running again!!\n"); switch (host_data->sys_state) { case WB_SYS_STATE_TEST: WB_PRINTK_THREAD("WB_SYS_STATE_TEST\n"); break; case WB_SYS_STATE_RMMOD: WB_PRINTK_THREAD("WB_SYS_STATE_RMMOD\n"); host_data->sys_state = WB_SYS_STATE_RMMOD_ACK; goto lab_end; break; case WB_SYS_STATE_QUEUE_CMD_OK: WB_PRINTK_THREAD("WB_SYS_STATE_QUEUE_CMD\n"); wb_process_scsi_cmd(host_data->cmd_wrapper->cmd); break; default: WB_PRINTK_ERROR("Oops,host_data->sys_state=0x%x\n", host_data->sys_state); break; } WB_PRINTK_THREAD("helper kernel thread is idle!!\n"); } lab_end: LEAVE(); return 0; } static int wb_queue_cmd(Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *)) { wb_host_data_t *host_data; ENTER(); host_data = g_wb_host_data; host_data->cmd_wrapper->cmd = cmd; cmd->scsi_done = done; cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, GOOD); host_data->sys_state = WB_SYS_STATE_QUEUE_CMD_OK; up(host_data->semaphore); LEAVE(); return 0; } static int wb_eh_abort(Scsi_Cmnd * cmnd) { ENTER(); LEAVE(); return 0; } static int wb_eh_reset(Scsi_Cmnd * cmnd) { ENTER(); LEAVE(); return 0; } static void wb_make_sense_buffer(Scsi_Cmnd * cmd, int key, int asc, int ascq) { char *sense_buffer; ENTER(); switch (key) { case ILLEGAL_REQUEST: WB_PRINTK("sense key ILLEGAL_REQUEST\n"); break; case NOT_READY: WB_PRINTK("sense key NOT_READY\n"); break; case MEDIUM_ERROR: WB_PRINTK("sense key MEDIUM_ERROR\n"); break; case UNIT_ATTENTION: WB_PRINTK("sense key UNIT_ATTENTION\n"); break; case ABORTED_COMMAND: WB_PRINTK("sense key ABORTED_COMMAND\n"); break; default: WB_PRINTK_ERROR("Unknown SCSI sense key %X!!!\n", key); break; } sense_buffer = cmd->sense_buffer; memset(sense_buffer, 0, SCSI_SENSE_BUFFERSIZE); sense_buffer[0] = 0x70; // Error Code sense_buffer[2] = key; // Sense Key sense_buffer[7] = 0x0A; // Additional Sense Length sense_buffer[12] = asc; // ASC = Invalid OpCode sense_buffer[13] = ascq; // ASCQ LEAVE(); } static int wb_get_medium_valid(wb_host_data_t * host_data) { ENTER(); LEAVE(); return host_data->medium_valid; } static void wb_set_medium_valid(wb_host_data_t * host_data, unsigned char bool) { ENTER(); host_data->medium_valid = bool; LEAVE(); } static int wb_get_notice_upper_scsi_layer(wb_host_data_t * host_data) { ENTER(); LEAVE(); return host_data->notice_upper_scsi_layer; } static void wb_set_notice_upper_scsi_layer(wb_host_data_t * host_data, unsigned char bool) { ENTER(); host_data->notice_upper_scsi_layer = bool; LEAVE(); } static void wb_chk_valid_medium(wb_host_data_t * host_data) { ENTER(); if (FALSE == wbvdev_get_state(WB_VIRTUAL_STATE_PRESENT)) { WB_PRINTK("Card does not exist\n"); } else if (TRUE == wbvdev_get_state(WB_VIRTUAL_STATE_CHANGED)) { wbvdev_set_state(WB_VIRTUAL_STATE_CHANGED, FALSE); if (0 == wbvdev_init_card()) { wb_set_medium_valid(host_data, TRUE); } else { wb_set_medium_valid(host_data, FALSE); } wb_set_notice_upper_scsi_layer(host_data, TRUE); } LEAVE(); return; } static void wb_scsi_format_unit(Scsi_Cmnd * cmd) { } /* wb_transfer_internal to transfer buffers wr_flag = 0 : driver buffer to command buffer (Read) wr_flag = 1 : command buffer to driver buffer (Write) */ static void wb_transfer_internal(Scsi_Cmnd * incmd, void *data, u32 len, u8 wr_flag) { struct scsi_cmnd *cmd = incmd; void *buf; u32 transfer_len; if (cmd->use_sg) { struct scatterlist *sg; sg = (struct scatterlist *)cmd->request_buffer; buf = kmap_atomic(sg->page, KM_IRQ0) + sg->offset; transfer_len = min(sg->length, len); } else { buf = cmd->request_buffer; transfer_len = min(cmd->request_bufflen, len); } if (wr_flag) { memcpy(data, buf, transfer_len); } else { memcpy(buf, data, transfer_len); } if (cmd->use_sg) { struct scatterlist *sg; sg = (struct scatterlist *)cmd->request_buffer; kunmap_atomic(buf - sg->offset, KM_IRQ0); } } static void wb_scsi_inquiry(Scsi_Cmnd * cmd) { wb_scsi_inquiry_data_t inquiry; ENTER(); inquiry.dev_type = 0; inquiry.removable_medium = 1; inquiry.ver = 2; inquiry.response_data_format = 0x02; inquiry.additional_len = 31 * 3; strncpy(inquiry.vendor_id, "Winbond", 8); strncpy(inquiry.prod_id, "PCI MassStorage", 16); strncpy(inquiry.prod_rev_level, "1.0", 4); wb_transfer_internal(cmd, &inquiry, sizeof(inquiry), 0); // even if the card doesn't exist, we still return TRUE cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, GOOD); memset(cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); LEAVE(); } static void wb_scsi_test_unit_ready(Scsi_Cmnd * cmd) { ENTER(); if (1 == wb_unit_ready(cmd)) { wb_make_sense_buffer(cmd, NOT_READY, 0x3a, 0x00); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); } LEAVE(); } static void wb_scsi_start_stop(Scsi_Cmnd * cmd) { wb_host_data_t *host_data; ENTER(); host_data = g_wb_host_data; // start bit = off if (0 == (cmd->cmnd[4] & 0x01)) { wbvdev_set_state(WB_VIRTUAL_STATE_PRESENT, FALSE); wb_set_medium_valid(host_data, FALSE); wbvdev_set_state(WB_VIRTUAL_STATE_CHANGED, TRUE); } else if (1 == wb_unit_ready(cmd)) { // start bit = on, turn on card //wb_make_sense_buffer( cmd, NOT_READY, 0x3a, 0x00 ); //cmd->result = WB_CMD_RESULT( DID_OK, COMMAND_COMPLETE, CHECK_CONDITION ); } LEAVE(); } static void wb_scsi_read_capacity(Scsi_Cmnd * cmd) { wb_host_data_t *host_data; wb_scsi_capacity_t cap; ENTER(); host_data = g_wb_host_data; if (wb_unit_ready(cmd)) { goto lab_end; } cap.lba = cpu_to_be32(wbvdev_get_capacity() - 1); cap.len = cpu_to_be32((unsigned int)WB_LOGICAL_BLK_SIZE); wb_transfer_internal(cmd, &cap, sizeof(cap), 0); lab_end: LEAVE(); return; } static void wb_scsi_mode_sense(Scsi_Cmnd * cmd) { wb_host_data_t *host_data; wb_scsi_mode_page_header_t modepage; ENTER(); host_data = g_wb_host_data; if (wbvdev_get_state(WB_VIRTUAL_STATE_PRESENT)) { modepage.data_len = 3; modepage.blk_desc_len = 0; if (wbvdev_get_state(WB_VIRTUAL_STATE_PROTECTED)) { modepage.dev_spec_param = 0x80; } else { modepage.dev_spec_param = 0; } } else { // card is not in wb_make_sense_buffer(cmd, NOT_READY, 0x3a, 0x00); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); WB_PRINTK("Card may not be inserted!\n"); } wb_transfer_internal(cmd, &modepage, sizeof(wb_scsi_mode_page_header_t), 0); LEAVE(); } static void wb_scsi_allow_medium_removal(Scsi_Cmnd * cmd) { ENTER(); //prevent removal cmnd is illegal since card can be removable if ((cmd->cmnd[4] & 0x01)) { // AdditionalSenseCode: SCSI_ADSENSE_ILLEGAL_COMMAND wb_make_sense_buffer(cmd, ILLEGAL_REQUEST, 0x20, 0x00); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); } LEAVE(); } static void wb_copy_scratch_to_sgtable(char *scratch, Scsi_Cmnd * cmd) { struct scatterlist *sg_table; int count; unsigned char *sg_buf; ENTER(); sg_table = (struct scatterlist *)cmd->request_buffer; for (count = 0; count < cmd->use_sg; count++) { sg_buf = kmap_atomic(sg_table[count].page, KM_IRQ0) + sg_table[count].offset; memcpy(sg_buf, scratch, sg_table[count].length); kunmap_atomic(sg_buf, KM_IRQ0); scratch += sg_table[count].length; } LEAVE(); } static void wb_copy_sgtable_to_scratch(Scsi_Cmnd * cmd, char *scratch) { struct scatterlist *sg_table; int count; unsigned char *sg_buf; ENTER(); sg_table = (struct scatterlist *)cmd->request_buffer; for (count = 0; count < cmd->use_sg; count++) { sg_buf = kmap_atomic(sg_table[count].page, KM_IRQ0) + sg_table[count].offset; memcpy(scratch, sg_buf, sg_table[count].length); kunmap_atomic(sg_buf, KM_IRQ0); scratch += sg_table[count].length; } LEAVE(); } static void wb_scsi_read_wrt(Scsi_Cmnd * cmd, unsigned char read_cmd) { unsigned int start_lba; unsigned int length; char *buff; wb_host_data_t *host_data; ENTER(); buff = NULL; host_data = g_wb_host_data; if (wb_unit_ready(cmd)) { goto lab_end; } // calculate LBA in sectors start_lba = WB_COMPUTE_START_LBA(cmd); WB_PRINTK_SCSI ("[start_lba = 0x%x bufferlen = %d bytes use_sg = %x]\n", start_lba, cmd->bufflen, cmd->use_sg); if ((start_lba > wbvdev_get_capacity())) { WB_PRINTK_ERROR_SCSI("start address out of bound\n"); // AdditionalSenseCode: SCSI_ADSENSE_INVALID_CDB wb_make_sense_buffer(cmd, ILLEGAL_REQUEST, 0x24, 0x00); goto lab_end; } // get buffer length length = cmd->request_bufflen; // get buffer pointer // if use sgtable,copy the data from sg buffer to buffer we allocated if (0 != cmd->use_sg) { buff = host_data->scratch; if (read_cmd == FALSE) { wb_copy_sgtable_to_scratch(cmd, host_data->scratch); } } else { buff = cmd->request_buffer; } // read data from or write data to card if (wbvdev_read_wrt(start_lba, length, buff, read_cmd)) { cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); //wb_make_sense_buffer( cmd,MEDIUM_ERROR, 0x11, 0x00 ); wb_make_sense_buffer(cmd, MEDIUM_ERROR, 0x17, 0x01); //wb_make_sense_buffer( cmd,ABORTED_COMMAND, 0x17, 0x01 ); } // if use sgtabble,copy the data from buffer we allocated to sg buffer if (0 != cmd->use_sg) { if (TRUE == read_cmd) wb_copy_scratch_to_sgtable(host_data->scratch, cmd); } lab_end: LEAVE(); } static void wb_scsi_request_sense(Scsi_Cmnd * cmd) { ENTER(); LEAVE(); } // check card state and set corresponding flags static int wb_unit_ready(Scsi_Cmnd * cmd) { int ret; wb_host_data_t *host_data; ENTER(); host_data = g_wb_host_data; ret = 1; if (TRUE == wbvdev_get_state(WB_VIRTUAL_STATE_PRESENT)) { if (TRUE == wb_get_medium_valid(host_data)) { if (FALSE == wb_get_notice_upper_scsi_layer(host_data)) { ret = 0; } else { // for each card inserted/removed, only notice upper SCSI layer once wb_set_notice_upper_scsi_layer(host_data, FALSE); // ASC/ASCQ: not ready, media may have changed //wb_make_sense_buffer( cmd, UNIT_ATTENTION, 0x28, 0x00 ); wb_make_sense_buffer(cmd, UNIT_ATTENTION, 0x28, 0x00); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); } } else { // ASC/ASCQ: incompatible media installed wb_make_sense_buffer(cmd, MEDIUM_ERROR, 0x30, 0x00); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); } } else { // ASC/ASCQ: medium not present wb_make_sense_buffer(cmd, UNIT_ATTENTION, 0x28, 0x00); // wb_make_sense_buffer( cmd, NOT_READY, 0x3a, 0x00 ); cmd->result = WB_CMD_RESULT(DID_OK, COMMAND_COMPLETE, CHECK_CONDITION); } LEAVE(); return ret; } MODULE_LICENSE("GPL"); static Scsi_Host_Template driver_template = WB_DRIVER; static int __init wb_init(void) { struct device *pdev; int ret = -1; ENTER(); pdev = wb_detect(&driver_template); if (!pdev) { goto lab_end; } if (scsi_add_host(g_wb_host, pdev)) { scsi_host_put(g_wb_host); goto lab_end; } scsi_scan_host(g_wb_host); ret = 0; LEAVE(); lab_end: return ret; } static void __exit wb_exit(void) { ENTER(); wb_release(g_wb_host); scsi_host_put(g_wb_host); LEAVE(); } module_init(wb_init); module_exit(wb_exit);