--- lg-rpcmgr11.c Sun Feb 25 23:53:52 2007 +++ rpcmgr.c Sat Feb 24 18:56:13 2007 @@ -137,8 +137,9 @@ #include #include #include -#include -#include +#include +#include +#include #define PROG "rpcmgr" @@ -152,14 +153,30 @@ #define HITACHI_RPC2 0xe7 -/* SCSI buffers */ +#define INQUIRY (0x12) +#define TYPE_ROM (0x05) +#define MODE_SELECT_10 (0x55) + +size_t cdblen = 0; +#ifdef linux +/* used by Linux sgio only */ #define SG_SIZE sizeof(struct sg_header) char buf_msg[SG_SIZE + 200]; char buf_reply[SG_SIZE + 200]; struct sg_header* sg_msg = (struct sg_header*)buf_msg; -struct sg_header* sg_reply = (struct sg_header*)buf_reply; unsigned char* msg = buf_msg + SG_SIZE; + +/* used by rest of .c file */ +struct sg_header* sg_reply = (struct sg_header*)buf_reply; unsigned char* reply = buf_reply + SG_SIZE; +#else +#define READ_SZ (200) +struct sg_header { + int driver_status; +}; +struct sg_header sg_reply[1]; +unsigned char reply[READ_SZ]; +#endif typedef enum { RPC2_ENABLE, RPC2_DISABLE, RPC2_RESET_USER, RPC2_RESET_VENDOR } rpc2_command_t; typedef void (*rpc2_function_t)(int fd, rpc2_command_t command); @@ -180,7 +197,8 @@ { "HITACHI ", "DVD-ROM GD-3000 ", rpc2_hitachi }, { "HITACHI ", "DVD-ROM GD-5000 ", rpc2_hitachi }, { "LG ", "DVD-ROM DRD8080B", rpc2_lg }, - { "LG ", "DVD-ROM DRN8080B", rpc2_lg } + { "LG ", "DVD-ROM DRN8080B", rpc2_lg }, + { "LITE-ON ", "DVDRW SHM-165H6S", rpc2_hitachi } }; @@ -246,26 +264,93 @@ } /* Send SCSI command to drive and retrieve reply */ -void sgio(int fd, const unsigned char* cmd, int cmdlen, int replylen) +void sgio(int fd, unsigned char* cmd, int cmdlen, int replylen) { - int pack_len = SG_SIZE + cmdlen; - int reply_len = SG_SIZE + replylen; - memset(buf_msg, 0, pack_len); - sg_msg->reply_len = reply_len; - memcpy(msg, cmd, cmdlen); +#define SENSE_SZ (32) - memset(buf_reply, 0, reply_len); + struct uscsi_cmd ucmd; + unsigned char sense[SENSE_SZ]; + int ret; - if (write(fd, buf_msg, pack_len) != pack_len) { - perror("sg write"); - exit(1); + if (!cdblen) { + switch((cmd[0] >> 5) & 0x7) {/* command group */ + case 0: + cdblen = 6; + break; + case 1: + case 2: + case 6: + case 7: + cdblen = 10; + break; + case 3: + case 4: + case 5: + cdblen = 12; + break; + default: + assert(0); + } } - if (read(fd, buf_reply, reply_len) != reply_len) { - perror("sg read"); - exit(1); + memset(reply, 0, READ_SZ); + + memset(&ucmd, 0, sizeof(ucmd)); + ucmd.uscsi_cdb = (caddr_t) cmd; + ucmd.uscsi_cdblen = cdblen; + ucmd.uscsi_flags = (USCSI_DIAGNOSE|USCSI_ISOLATE|USCSI_RQENABLE); + ucmd.uscsi_rqbuf = (caddr_t) sense; + ucmd.uscsi_rqlen = SENSE_SZ; + ucmd.uscsi_buflen = cmdlen - cdblen; + if (!replylen) { + ucmd.uscsi_bufaddr = (caddr_t) (cmd + cdblen); + ucmd.uscsi_flags |= USCSI_WRITE; + } else { + /* + * uscsi doesn't have an API for doing a 'write' data phase and a 'read' + * data phase in the same command. linux sg does, and what's more makes + * it easy to ask for it accidentally. + * + * 1. you write 12 bytes for a command with a 10-byte CDB like + * READ_DVD_STRUCTURE + * 2. Linux runs the implied 2-byte data phase + * 3. you read more than sizeof(struct sg_header) bytes. whoops! you + * wanted a read data phase instead! this apparently still works. + * + * in uscsi there is no way to set separate data sizes for write and + * read, so even if uscsi_flags = USCSI_READ|USCSI_WRITE actually did + * anything, we can't even express the mistake accurately. Therefore we + * assume the caller wanted a read if he asks for both. + * + * If I were writing ``brandZ'' emulation for this, I'd refuse to do + * tagged command queueing, and I'd copyin data when the process calls write(), + * and actually execute the SCSI command when the process calls read(). I + * think that would break fewer programs. + */ + if (ucmd.uscsi_buflen != 0) + fprintf(stderr, "SCSI command 0x%02x is %d bytes long, but sgio was sent %d-byte cdb. truncating.\n", cmd[0], cdblen, cmdlen); + assert(replylen <= READ_SZ); + ucmd.uscsi_bufaddr = (caddr_t) reply; + ucmd.uscsi_buflen = replylen; + ucmd.uscsi_flags |= USCSI_READ; } + + ret = ioctl(fd, USCSICMD, &ucmd); + + if (!(ret == 0 || ret == EIO)) { + fprintf(stderr, "while sending SCSI command 0x%02x: ", cmd[0]); + perror("uscsi"); + } + + if (ucmd.uscsi_status) + sg_reply->driver_status = ucmd.uscsi_status; + else + sg_reply->driver_status = ret; + + /* XXX -- pretty-print sense */ + + cdblen = 0; } /* Drive model check */ @@ -346,8 +431,8 @@ { int i; for (i = 0; i < sizeof(rpcmgr_drives)/sizeof(struct drive_model); i++) { - if (strncmp(reply + 8, rpcmgr_drives[i].vendor, 8) == 0 - && strncmp(reply + 16, rpcmgr_drives[i].product, 16) == 0) + if (strncmp((char *) reply + 8, rpcmgr_drives[i].vendor, 8) == 0 + && strncmp((char *) reply + 16, rpcmgr_drives[i].product, 16) == 0) { rpcmgr_drives[i].rpc2_function(fd, command); return; @@ -412,7 +497,7 @@ exit(1); } - ioctl(fd, SG_NEXT_CMD_LEN, &cmdlen); + cdblen = cmdlen; sgio(fd, cmd, sizeof(cmd), 0); if (sg_reply->driver_status != 0x00) {