📄 ide-scsi.c
字号:
/* * linux/drivers/scsi/ide-scsi.c Version 0.9 Jul 4, 1999 * * Copyright (C) 1996 - 1999 Gadi Oxman <gadio@netvision.net.il> *//* * Emulation of a SCSI host adapter for IDE ATAPI devices. * * With this driver, one can use the Linux SCSI drivers instead of the * native IDE ATAPI drivers. * * Ver 0.1 Dec 3 96 Initial version. * Ver 0.2 Jan 26 97 Fixed bug in cleanup_module() and added emulation * of MODE_SENSE_6/MODE_SELECT_6 for cdroms. Thanks * to Janos Farkas for pointing this out. * Avoid using bitfields in structures for m68k. * Added Scatter/Gather and DMA support. * Ver 0.4 Dec 7 97 Add support for ATAPI PD/CD drives. * Use variable timeout for each command. * Ver 0.5 Jan 2 98 Fix previous PD/CD support. * Allow disabling of SCSI-6 to SCSI-10 transformation. * Ver 0.6 Jan 27 98 Allow disabling of SCSI command translation layer * for access through /dev/sg. * Fix MODE_SENSE_6/MODE_SELECT_6/INQUIRY translation. * Ver 0.7 Dec 04 98 Ignore commands where lun != 0 to avoid multiple * detection of devices with CONFIG_SCSI_MULTI_LUN * Ver 0.8 Feb 05 99 Optical media need translation too. Reverse 0.7. * Ver 0.9 Jul 04 99 Fix a bug in SG_SET_TRANSFORM. */#define IDESCSI_VERSION "0.9"#include <linux/module.h>#include <linux/types.h>#include <linux/string.h>#include <linux/kernel.h>#include <linux/mm.h>#include <linux/ioport.h>#include <linux/blkdev.h>#include <linux/errno.h>#include <linux/hdreg.h>#include <linux/malloc.h>#include <linux/ide.h>#include <asm/io.h>#include <asm/bitops.h>#include <asm/uaccess.h>#include "scsi.h"#include "hosts.h"#include "sd.h"#include "ide-scsi.h"#include <scsi/sg.h>#define IDESCSI_DEBUG_LOG 0typedef struct idescsi_pc_s { u8 c[12]; /* Actual packet bytes */ int request_transfer; /* Bytes to transfer */ int actually_transferred; /* Bytes actually transferred */ int buffer_size; /* Size of our data buffer */ struct request *rq; /* The corresponding request */ byte *buffer; /* Data buffer */ byte *current_position; /* Pointer into the above buffer */ struct scatterlist *sg; /* Scatter gather table */ int b_count; /* Bytes transferred from current entry */ Scsi_Cmnd *scsi_cmd; /* SCSI command */ void (*done)(Scsi_Cmnd *); /* Scsi completion routine */ unsigned long flags; /* Status/Action flags */ unsigned long timeout; /* Command timeout */} idescsi_pc_t;/* * Packet command status bits. */#define PC_DMA_IN_PROGRESS 0 /* 1 while DMA in progress */#define PC_WRITING 1 /* Data direction */#define PC_TRANSFORM 2 /* transform SCSI commands *//* * SCSI command transformation layer */#define IDESCSI_TRANSFORM 0 /* Enable/Disable transformation */#define IDESCSI_SG_TRANSFORM 1 /* /dev/sg transformation *//* * Log flags */#define IDESCSI_LOG_CMD 0 /* Log SCSI commands */typedef struct { ide_drive_t *drive; idescsi_pc_t *pc; /* Current packet command */ unsigned long flags; /* Status/Action flags */ unsigned long transform; /* SCSI cmd translation layer */ unsigned long log; /* log flags */} idescsi_scsi_t;/* * Per ATAPI device status bits. */#define IDESCSI_DRQ_INTERRUPT 0 /* DRQ interrupt device *//* * ide-scsi requests. */#define IDESCSI_PC_RQ 90/* * Bits of the interrupt reason register. */#define IDESCSI_IREASON_COD 0x1 /* Information transferred is command */#define IDESCSI_IREASON_IO 0x2 /* The device requests us to read */static void idescsi_discard_data (ide_drive_t *drive, unsigned int bcount){ while (bcount--) IN_BYTE (IDE_DATA_REG);}static void idescsi_output_zeros (ide_drive_t *drive, unsigned int bcount){ while (bcount--) OUT_BYTE (0, IDE_DATA_REG);}/* * PIO data transfer routines using the scatter gather table. */static void idescsi_input_buffers (ide_drive_t *drive, idescsi_pc_t *pc, unsigned int bcount){ int count; while (bcount) { if (pc->sg - (struct scatterlist *) pc->scsi_cmd->request_buffer > pc->scsi_cmd->use_sg) { printk (KERN_ERR "ide-scsi: scatter gather table too small, discarding data\n"); idescsi_discard_data (drive, bcount); return; } count = IDE_MIN (pc->sg->length - pc->b_count, bcount); atapi_input_bytes (drive, pc->sg->address + pc->b_count, count); bcount -= count; pc->b_count += count; if (pc->b_count == pc->sg->length) { pc->sg++; pc->b_count = 0; } }}static void idescsi_output_buffers (ide_drive_t *drive, idescsi_pc_t *pc, unsigned int bcount){ int count; while (bcount) { if (pc->sg - (struct scatterlist *) pc->scsi_cmd->request_buffer > pc->scsi_cmd->use_sg) { printk (KERN_ERR "ide-scsi: scatter gather table too small, padding with zeros\n"); idescsi_output_zeros (drive, bcount); return; } count = IDE_MIN (pc->sg->length - pc->b_count, bcount); atapi_output_bytes (drive, pc->sg->address + pc->b_count, count); bcount -= count; pc->b_count += count; if (pc->b_count == pc->sg->length) { pc->sg++; pc->b_count = 0; } }}/* * Most of the SCSI commands are supported directly by ATAPI devices. * idescsi_transform_pc handles the few exceptions. */static inline void idescsi_transform_pc1 (ide_drive_t *drive, idescsi_pc_t *pc){ u8 *c = pc->c, *scsi_buf = pc->buffer, *sc = pc->scsi_cmd->cmnd; char *atapi_buf; if (!test_bit(PC_TRANSFORM, &pc->flags)) return; if (drive->media == ide_cdrom || drive->media == ide_optical) { if (c[0] == READ_6 || c[0] == WRITE_6) { c[8] = c[4]; c[5] = c[3]; c[4] = c[2]; c[3] = c[1] & 0x1f; c[2] = 0; c[1] &= 0xe0; c[0] += (READ_10 - READ_6); } if (c[0] == MODE_SENSE || c[0] == MODE_SELECT) { if (!scsi_buf) return; if ((atapi_buf = kmalloc(pc->buffer_size + 4, GFP_ATOMIC)) == NULL) return; memset(atapi_buf, 0, pc->buffer_size + 4); memset (c, 0, 12); c[0] = sc[0] | 0x40; c[1] = sc[1]; c[2] = sc[2]; c[8] = sc[4] + 4; c[9] = sc[5]; if (sc[4] + 4 > 255) c[7] = sc[4] + 4 - 255; if (c[0] == MODE_SELECT_10) { atapi_buf[1] = scsi_buf[0]; /* Mode data length */ atapi_buf[2] = scsi_buf[1]; /* Medium type */ atapi_buf[3] = scsi_buf[2]; /* Device specific parameter */ atapi_buf[7] = scsi_buf[3]; /* Block descriptor length */ memcpy(atapi_buf + 8, scsi_buf + 4, pc->buffer_size - 4); } pc->buffer = atapi_buf; pc->request_transfer += 4; pc->buffer_size += 4; } }}static inline void idescsi_transform_pc2 (ide_drive_t *drive, idescsi_pc_t *pc){ u8 *atapi_buf = pc->buffer; u8 *sc = pc->scsi_cmd->cmnd; u8 *scsi_buf = pc->scsi_cmd->request_buffer; if (!test_bit(PC_TRANSFORM, &pc->flags)) return; if (drive->media == ide_cdrom || drive->media == ide_optical) { if (pc->c[0] == MODE_SENSE_10 && sc[0] == MODE_SENSE) { scsi_buf[0] = atapi_buf[1]; /* Mode data length */ scsi_buf[1] = atapi_buf[2]; /* Medium type */ scsi_buf[2] = atapi_buf[3]; /* Device specific parameter */ scsi_buf[3] = atapi_buf[7]; /* Block descriptor length */ memcpy(scsi_buf + 4, atapi_buf + 8, pc->request_transfer - 8); } if (pc->c[0] == INQUIRY) { scsi_buf[2] |= 2; /* ansi_revision */ scsi_buf[3] = (scsi_buf[3] & 0xf0) | 2; /* response data format */ } } if (atapi_buf && atapi_buf != scsi_buf) kfree(atapi_buf);}static inline void idescsi_free_bh (struct buffer_head *bh){ struct buffer_head *bhp; while (bh) { bhp = bh; bh = bh->b_reqnext; kfree (bhp); }}static void hexdump(u8 *x, int len){ int i; printk("[ "); for (i = 0; i < len; i++) printk("%x ", x[i]); printk("]\n");}static void idescsi_end_request (byte uptodate, ide_hwgroup_t *hwgroup){ ide_drive_t *drive = hwgroup->drive; idescsi_scsi_t *scsi = drive->driver_data; struct request *rq = hwgroup->rq; idescsi_pc_t *pc = (idescsi_pc_t *) rq->buffer; int log = test_bit(IDESCSI_LOG_CMD, &scsi->log); u8 *scsi_buf; unsigned long flags; if (rq->cmd != IDESCSI_PC_RQ) { ide_end_request (uptodate, hwgroup); return; } ide_end_drive_cmd (drive, 0, 0); if (rq->errors >= ERROR_MAX) { pc->scsi_cmd->result = DID_ERROR << 16; if (log) printk ("ide-scsi: %s: I/O error for %lu\n", drive->name, pc->scsi_cmd->serial_number); } else if (rq->errors) { pc->scsi_cmd->result = (CHECK_CONDITION << 1) | (DID_OK << 16); if (log) printk ("ide-scsi: %s: check condition for %lu\n", drive->name, pc->scsi_cmd->serial_number); } else { pc->scsi_cmd->result = DID_OK << 16; idescsi_transform_pc2 (drive, pc); if (log) { printk ("ide-scsi: %s: suc %lu", drive->name, pc->scsi_cmd->serial_number); if (!test_bit(PC_WRITING, &pc->flags) && pc->actually_transferred && pc->actually_transferred <= 1024 && pc->buffer) { printk(", rst = "); scsi_buf = pc->scsi_cmd->request_buffer; hexdump(scsi_buf, IDE_MIN(16, pc->scsi_cmd->request_bufflen)); } else printk("\n"); } } spin_lock_irqsave(&io_request_lock,flags); pc->done(pc->scsi_cmd); spin_unlock_irqrestore(&io_request_lock,flags); idescsi_free_bh (rq->bh); kfree(pc); kfree(rq); scsi->pc = NULL;}static inline unsigned long get_timeout(idescsi_pc_t *pc){ return IDE_MAX(WAIT_CMD, pc->timeout - jiffies);}/* * Our interrupt handler. */static ide_startstop_t idescsi_pc_intr (ide_drive_t *drive){ idescsi_scsi_t *scsi = drive->driver_data; byte status, ireason; int bcount; idescsi_pc_t *pc=scsi->pc; struct request *rq = pc->rq; unsigned int temp;#if IDESCSI_DEBUG_LOG printk (KERN_INFO "ide-scsi: Reached idescsi_pc_intr interrupt handler\n");#endif /* IDESCSI_DEBUG_LOG */ if (test_and_clear_bit (PC_DMA_IN_PROGRESS, &pc->flags)) {#if IDESCSI_DEBUG_LOG printk ("ide-scsi: %s: DMA complete\n", drive->name);#endif /* IDESCSI_DEBUG_LOG */ pc->actually_transferred=pc->request_transfer; (void) (HWIF(drive)->dmaproc(ide_dma_end, drive)); } status = GET_STAT(); /* Clear the interrupt */ if ((status & DRQ_STAT) == 0) { /* No more interrupts */ if (test_bit(IDESCSI_LOG_CMD, &scsi->log)) printk (KERN_INFO "Packet command completed, %d bytes transferred\n", pc->actually_transferred); ide__sti(); if (status & ERR_STAT) rq->errors++; idescsi_end_request (1, HWGROUP(drive)); return ide_stopped; } bcount = IN_BYTE (IDE_BCOUNTH_REG) << 8 | IN_BYTE (IDE_BCOUNTL_REG); ireason = IN_BYTE (IDE_IREASON_REG); if (ireason & IDESCSI_IREASON_COD) { printk (KERN_ERR "ide-scsi: CoD != 0 in idescsi_pc_intr\n"); return ide_do_reset (drive); } if (ireason & IDESCSI_IREASON_IO) { temp = pc->actually_transferred + bcount; if ( temp > pc->request_transfer) { if (temp > pc->buffer_size) { printk (KERN_ERR "ide-scsi: The scsi wants to send us more data than expected - discarding data\n"); temp = pc->buffer_size - pc->actually_transferred; if (temp) { clear_bit(PC_WRITING, &pc->flags); if (pc->sg) idescsi_input_buffers(drive, pc, temp); else atapi_input_bytes(drive, pc->current_position, temp); printk(KERN_ERR "ide-scsi: transferred %d of %d bytes\n", temp, bcount); } pc->actually_transferred += temp; pc->current_position += temp; idescsi_discard_data (drive,bcount - temp); ide_set_handler(drive, &idescsi_pc_intr, get_timeout(pc), NULL); return ide_started; }#if IDESCSI_DEBUG_LOG printk (KERN_NOTICE "ide-scsi: The scsi wants to send us more data than expected - allowing transfer\n");#endif /* IDESCSI_DEBUG_LOG */ } } if (ireason & IDESCSI_IREASON_IO) { clear_bit(PC_WRITING, &pc->flags); if (pc->sg) idescsi_input_buffers (drive, pc, bcount); else atapi_input_bytes (drive,pc->current_position,bcount); } else { set_bit(PC_WRITING, &pc->flags); if (pc->sg) idescsi_output_buffers (drive, pc, bcount); else atapi_output_bytes (drive,pc->current_position,bcount); } pc->actually_transferred+=bcount; /* Update the current position */ pc->current_position+=bcount; ide_set_handler(drive, &idescsi_pc_intr, get_timeout(pc), NULL); /* And set the interrupt handler again */ return ide_started;}static ide_startstop_t idescsi_transfer_pc (ide_drive_t *drive){ idescsi_scsi_t *scsi = drive->driver_data; idescsi_pc_t *pc = scsi->pc; byte ireason; ide_startstop_t startstop; if (ide_wait_stat (&startstop,drive,DRQ_STAT,BUSY_STAT,WAIT_READY)) { printk (KERN_ERR "ide-scsi: Strange, packet command initiated yet DRQ isn't asserted\n"); return startstop; } ireason = IN_BYTE (IDE_IREASON_REG); if ((ireason & (IDESCSI_IREASON_IO | IDESCSI_IREASON_COD)) != IDESCSI_IREASON_COD) { printk (KERN_ERR "ide-scsi: (IO,CoD) != (0,1) while issuing a packet command\n"); return ide_do_reset (drive); } ide_set_handler(drive, &idescsi_pc_intr, get_timeout(pc), NULL); /* Set the interrupt routine */ atapi_output_bytes (drive, scsi->pc->c, 12); /* Send the actual packet */ return ide_started;}/* * Issue a packet command */static ide_startstop_t idescsi_issue_pc (ide_drive_t *drive, idescsi_pc_t *pc){ idescsi_scsi_t *scsi = drive->driver_data; int bcount; struct request *rq = pc->rq; int dma_ok = 0; scsi->pc=pc; /* Set the current packet command */ pc->actually_transferred=0; /* We haven't transferred any data yet */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -