/* * Creation Date: <2003/12/11 21:23:54 samuel> * Time-stamp: <2004/01/07 19:38:45 samuel> * * * * SCSI device node * * Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 * */ #include "config.h" #include "libopenbios/bindings.h" #include "mol/mol.h" #include "scsi_sh.h" #include "osi_calls.h" #define MAX_TARGETS 32 typedef struct { int probed; int valid; /* a useable device found */ int is_cd; int blocksize; } target_info_t; static target_info_t scsi_devs[ MAX_TARGETS ]; typedef struct { int target; target_info_t *info; } instance_data_t; DECLARE_NODE( scsi, INSTALL_OPEN, sizeof(instance_data_t), "/pci/pci-bridge/mol-scsi/sd", "/mol/mol-scsi/sd" ); static int scsi_cmd_( instance_data_t *sd, const char *cmd, int cmdlen, char *dest, int len, int prelen, int postlen ) { char prebuf[4096], postbuf[4096]; scsi_req_t r[2]; /* the [2] is a hack to get space for the sg-list */ char sb[32]; /* memset( dest, 0, len ); */ if( (unsigned int)prelen > sizeof(prebuf) || (unsigned int)postlen > sizeof(postbuf) ) { printk("bad pre/post len %d %d\n", prelen, postlen ); return 1; } memset( r, 0, sizeof(r[0]) ); r->lun = 0; r->target = sd->target; r->is_write = 0; memcpy( r->cdb, cmd, cmdlen ); r->client_addr = (int)&r; r->cdb_len = cmdlen; r->sense[0].base = (int)&sb; r->sense[0].size = sizeof(sb); r->size = prelen + len + postlen; r->n_sg = 3; r->sglist.n_el = 3; r->sglist.vec[0].base = (int)prebuf; r->sglist.vec[0].size = prelen; r->sglist.vec[1].base = (int)dest; r->sglist.vec[1].size = len; r->sglist.vec[2].base = (int)postbuf; r->sglist.vec[2].size = postlen; if( OSI_SCSISubmit((int)&r) ) { printk("OSI_SCSISubmit: error!\n"); return 1; } while( !OSI_SCSIAck() ) OSI_USleep( 10 ); if( r->adapter_status ) return -1; if( r->scsi_status ) return ((sb[2] & 0xf) << 16) | (sb[12] << 8) | sb[13]; return 0; } static int scsi_cmd( instance_data_t *sd, const char *cmd, int cmdlen ) { return scsi_cmd_( sd, cmd, cmdlen, NULL, 0, 0, 0 ); } /* ( buf blk nblks -- actual ) */ static void scsi_read_blocks( instance_data_t *sd ) { int nblks = POP(); int blk = POP(); char *dest = (char*)POP(); unsigned char cmd[10]; int len = nblks * sd->info->blocksize; memset( dest, 0, len ); /* printk("READ: blk: %d length %d\n", blk, len ); */ memset( cmd, 0, sizeof(cmd) ); cmd[0] = 0x28; /* READ_10 */ cmd[2] = blk >> 24; cmd[3] = blk >> 16; cmd[4] = blk >> 8; cmd[5] = blk; cmd[7] = nblks >> 8; cmd[8] = nblks; if( scsi_cmd_(sd, cmd, 10, dest, len, 0, 0) ) { printk("read: scsi_cmd failed\n"); RET( -1 ); } PUSH( nblks ); } static int inquiry( instance_data_t *sd ) { char inquiry_cmd[6] = { 0x12, 0, 0, 0, 32, 0 }; char start_stop_unit_cmd[6] = { 0x1b, 0, 0, 0, 1, 0 }; char test_unit_ready_cmd[6] = { 0x00, 0, 0, 0, 0, 0 }; char prev_allow_medium_removal[6] = { 0x1e, 0, 0, 0, 1, 0 }; char set_cd_speed_cmd[12] = { 0xbb, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0 }; target_info_t *info = &scsi_devs[sd->target]; char ret[32]; int i, sense; if( sd->target >= MAX_TARGETS ) return -1; sd->info = info; if( info->probed ) return info->valid ? 0:-1; info->probed = 1; if( (sense=scsi_cmd_(sd, inquiry_cmd, 6, ret, 2, 0, 0)) ) { if( sense < 0 ) return -1; printk("INQUIRY failed\n"); return -1; } /* medium present? */ if( (scsi_cmd(sd, test_unit_ready_cmd, 6) >> 8) == 0x23a ) { printk("no media\n"); return -1; } info->is_cd = 0; info->blocksize = 512; if( ret[0] == 5 /* CD/DVD */ ) { info->blocksize = 2048; info->is_cd = 1; scsi_cmd( sd, prev_allow_medium_removal, 6 ); scsi_cmd( sd, set_cd_speed_cmd, 12 ); scsi_cmd( sd, start_stop_unit_cmd, 6 ); } else if( ret[0] == 0 /* DISK */ ) { scsi_cmd( sd, test_unit_ready_cmd, 6 ); scsi_cmd( sd, start_stop_unit_cmd, 6 ); } else { /* don't boot from this device (could be a scanner :-)) */ return -1; } /* wait for spin-up (or whatever) to complete */ for( i=0; ; i++ ) { if( i > 300 ) { printk("SCSI timeout (sense %x)\n", sense ); return -1; } sense = scsi_cmd( sd, test_unit_ready_cmd, 6 ); if( (sense & 0xf0000) == 0x20000 ) { OSI_USleep( 10000 ); continue; } break; } info->valid = 1; return 0; } /* ( -- success? ) */ static void scsi_open( instance_data_t *sd ) { static int once = 0; phandle_t ph; fword("my-unit"); sd->target = POP(); if( !once ) { once++; OSI_SCSIControl( SCSI_CTRL_INIT, 0 ); } /* obtiain device information */ if( inquiry(sd) ) RET(0); selfword("open-deblocker"); /* interpose disk-label */ ph = find_dev("/packages/disk-label"); fword("my-args"); PUSH_ph( ph ); fword("interpose"); PUSH( -1 ); } /* ( -- ) */ static void scsi_close( instance_data_t *pb ) { selfword("close-deblocker"); } /* ( -- bs ) */ static void scsi_block_size( instance_data_t *sd ) { PUSH( sd->info->blocksize ); } /* ( -- maxbytes ) */ static void scsi_max_transfer( instance_data_t *sd ) { PUSH( 1024*1024 ); } static void scsi_initialize( instance_data_t *sd ) { fword("is-deblocker"); } NODE_METHODS( scsi ) = { { NULL, scsi_initialize }, { "open", scsi_open }, { "close", scsi_close }, { "read-blocks", scsi_read_blocks }, { "block-size", scsi_block_size }, { "max-transfer", scsi_max_transfer }, }; void osiscsi_init( void ) { REGISTER_NODE( scsi ); }