/* * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. By downloading, copying, installing or * using the software you agree to this license. If you do not agree to this license, do not download, install, * copy or use the software. * * Intel License Agreement * * Copyright (c) 2002, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: * * -Redistributions of source code must retain the above copyright notice, this list of conditions and the * following disclaimer. * * -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * -The name of Intel Corporation may not be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Linux SCSI upper layer driver for OSD */ #include <linux/config.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/mm.h> #include <linux/string.h> #include <linux/hdreg.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/smp.h> #include <asm/uaccess.h> #include <asm/system.h> #include <asm/io.h> #include <linux/blk.h> #include <linux/blkpg.h> #include "scsi.h" #include "hosts.h" #include "so.h" #include <scsi/scsi_ioctl.h> #include "constants.h" #include <scsi/scsicam.h> #include <linux/genhd.h> #include "iscsiutil.h" #include "osd.h" /* * Macros */ #define SCSI_OSD_MAJOR 232 /* major.h */ #define MAJOR_NR SCSI_OSD_MAJOR /* hosts.h */ #define DEVICE_NAME "scsiosd" /* blk.h */ #define TIMEOUT_VALUE (2*HZ) /* blk.h */ #define DEVICE_NR(device) MINOR(device) /* blk.h */ #define SCSI_OSDS_PER_MAJOR 256 #define MAX_RETRIES 5 #define SO_TIMEOUT (30 * HZ) #define SO_MOD_TIMEOUT (75 * HZ) /* * Globals */ struct hd_struct *so; static Scsi_Osd *rscsi_osds; static int *so_sizes; static int *so_blocksizes; static int *so_hardsizes; static int check_scsiosd_media_change(kdev_t); static int so_init_oneosd(int); /* * Function prototypes */ static int so_init(void); static void so_finish(void); static int so_attach(Scsi_Device *); static int so_detect(Scsi_Device *); static void so_detach(Scsi_Device *); static int so_init_command(Scsi_Cmnd *); static void rw_intr(Scsi_Cmnd * SCpnt); static int fop_revalidate_scsiosd(kdev_t); static int revalidate_scsiosd(kdev_t dev, int maxusage); /* * Templates */ static struct Scsi_Device_Template so_template = { name: "osd", tag: "so", scsi_type: TYPE_OSD, major: SCSI_OSD_MAJOR, blk: 1, detect: so_detect, init: so_init, finish: so_finish, attach: so_attach, detach: so_detach, init_command: so_init_command, }; /* * Functions */ static int so_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { kdev_t dev = inode->i_rdev; struct Scsi_Host * host; Scsi_Device * SDev; int osdinfo[4]; iscsi_trace(TRACE_OSDSO, "so_ioctl()\n"); SDev = rscsi_osds[DEVICE_NR(dev)].device; /* * If we are in the middle of error recovery, don't let anyone * else try and use this device. Also, if error recovery fails, it * may try and take the device offline, in which case all further * access to the device is prohibited. */ if( !scsi_block_when_processing_errors(SDev) ) { return -ENODEV; } switch (cmd) { case HDIO_GETGEO: /* Return BIOS osd parameters */ { struct hd_geometry *loc = (struct hd_geometry *) arg; if(!loc) return -EINVAL; host = rscsi_osds[DEVICE_NR(dev)].device->host; /* default to most commonly used values */ osdinfo[0] = 0x40; osdinfo[1] = 0x20; osdinfo[2] = rscsi_osds[DEVICE_NR(dev)].capacity >> 11; /* override with calculated, extended default, or driver values */ #if 0 if(host->hostt->bios_param != NULL) host->hostt->bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]); else scsicam_bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]); if (put_user(osdinfo[0], &loc->heads) || put_user(osdinfo[1], &loc->sectors) || put_user(osdinfo[2], &loc->cylinders) || put_user(so[MINOR(inode->i_rdev)].start_sect, &loc->start)) return -EFAULT; return 0; #else return -1; #endif } case HDIO_GETGEO_BIG: { struct hd_big_geometry *loc = (struct hd_big_geometry *) arg; if(!loc) return -EINVAL; host = rscsi_osds[DEVICE_NR(dev)].device->host; /* default to most commonly used values */ osdinfo[0] = 0x40; osdinfo[1] = 0x20; osdinfo[2] = rscsi_osds[DEVICE_NR(dev)].capacity >> 11; /* override with calculated, extended default, or driver values */ #if 0 if(host->hostt->bios_param != NULL) host->hostt->bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]); else scsicam_bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]); if (put_user(osdinfo[0], &loc->heads) || put_user(osdinfo[1], &loc->sectors) || put_user(osdinfo[2], (unsigned int *) &loc->cylinders) || put_user(so[MINOR(inode->i_rdev)].start_sect, &loc->start)) return -EFAULT; return 0; #else return -1; #endif } case BLKGETSIZE: /* Return device size */ if (!arg) return -EINVAL; return put_user(so[MINOR(inode->i_rdev)].nr_sects, (long *) arg); case BLKROSET: case BLKROGET: case BLKRASET: case BLKRAGET: case BLKFLSBUF: case BLKSSZGET: case BLKPG: case BLKELVGET: case BLKELVSET: return blk_ioctl(inode->i_rdev, cmd, arg); case BLKRRPART: /* Re-read partition tables */ if (!capable(CAP_SYS_ADMIN)) return -EACCES; return revalidate_scsiosd(dev, 1); default: return scsi_ioctl(rscsi_osds[DEVICE_NR(dev)].device , cmd, (void *) arg); } } static void so_devname(unsigned int index, char *buffer) { iscsi_trace(TRACE_OSDSO, "so_devname(%i)\n", index); sprintf(buffer, "so%i", index); } static request_queue_t *so_find_queue(kdev_t dev) { Scsi_Osd *dpnt; int target; iscsi_trace(TRACE_OSDSO, "so_find_queue()\n"); target = DEVICE_NR(dev); dpnt = &rscsi_osds[target]; if (!dpnt) { iscsi_trace_error("no such device\n"); return NULL; } return &dpnt->device->request_queue; } static int so_init_command(Scsi_Cmnd * SCpnt) { int block, this_count; Scsi_Osd *dpnt; char nbuff[6]; osd_args_t args; int index; iscsi_trace(TRACE_OSDSO, "so_init_command(MAJOR %i, MINOR %i)\n", MAJOR(SCpnt->request.rq_dev), MINOR(SCpnt->request.rq_dev)); index = MINOR(SCpnt->request.rq_dev); so_devname(index, nbuff); block = SCpnt->request.sector; this_count = SCpnt->request_bufflen >> 9; dpnt = &rscsi_osds[index]; if (index >= so_template.dev_max || !dpnt || !dpnt->device->online || block + SCpnt->request.nr_sectors > so[index].nr_sects) { iscsi_trace_error("index %i: request out of range: %i offset + %li count > %li total sectors\n", index, block, SCpnt->request.nr_sectors, so[index].nr_sects); return 0; } block += so[index].start_sect; if (dpnt->device->changed) { iscsi_trace_error("SCSI osd has been changed. Prohibiting further I/O\n"); return 0; } switch (SCpnt->request.cmd) { case WRITE: iscsi_trace(TRACE_OSDSO, "Translating BLOCK WRITE to OBJECT WRITE\n"); if (!dpnt->device->writeable) { iscsi_trace_error("device is not writable\n"); return 0; } iscsi_trace(TRACE_OSDSO, "Translating BLOCK WRITE (sector %i, len %i) to OBJECT WRITE\n", block, this_count); memset(&args, 0, sizeof(osd_args_t)); args.opcode = 0x7f; args.add_cdb_len = CONFIG_OSD_CDB_LEN-7; args.service_action = OSD_WRITE; args.GroupID = 0; args.UserID = 0; args.length = 512*this_count; args.offset = 512*block; OSD_ENCAP_CDB(&args, SCpnt->cmnd); SCpnt->sc_data_direction = SCSI_DATA_WRITE; SCpnt->cmd_len = CONFIG_OSD_CDB_LEN; SCpnt->result = 0; break; case READ: iscsi_trace(TRACE_OSDSO, "Translating BLOCK READ (sector %i, len %i) to OBJECT READ\n", block, this_count); memset(&args, 0, sizeof(osd_args_t)); args.opcode = 0x7f; args.add_cdb_len = CONFIG_OSD_CDB_LEN-7; args.service_action = OSD_READ; args.GroupID = 0; args.UserID = 0; args.length = 512*this_count; args.offset = 512*block; OSD_ENCAP_CDB(&args, SCpnt->cmnd); SCpnt->sc_data_direction = SCSI_DATA_READ; SCpnt->cmd_len = CONFIG_OSD_CDB_LEN; SCpnt->result = 0; break; default: panic("Unknown so command %d\n", SCpnt->request.cmd); } /* * We shouldn't disconnect in the middle of a sector, so with a dumb * host adapter, it's safe to assume that we can at least transfer * this many bytes between each connect / disconnect. */ SCpnt->transfersize = dpnt->device->sector_size; SCpnt->underflow = this_count << 9; SCpnt->allowed = MAX_RETRIES; SCpnt->timeout_per_command = (SCpnt->device->type == TYPE_OSD ? SO_TIMEOUT : SO_MOD_TIMEOUT); /* * This is the completion routine we use. This is matched in terms * of capability to this function. */ SCpnt->done = rw_intr; /* * This indicates that the command is ready from our end to be * queued. */ return 1; } static int so_open(struct inode *inode, struct file *filp) { int target; Scsi_Device * SDev; target = DEVICE_NR(inode->i_rdev); iscsi_trace(TRACE_OSDSO, "so_open()\n"); SCSI_LOG_HLQUEUE(1, printk("target=%d, max=%d\n", target, so_template.dev_max)); if (target >= so_template.dev_max || !rscsi_osds[target].device) return -ENXIO; /* No such device */ /* * If the device is in error recovery, wait until it is done. * If the device is offline, then disallow any access to it. */ if (!scsi_block_when_processing_errors(rscsi_osds[target].device)) { return -ENXIO; } /* * Make sure that only one process can do a check_change_osd at one time. * This is also used to lock out further access when the partition table * is being re-read. */ while (rscsi_osds[target].device->busy) barrier(); if (rscsi_osds[target].device->removable) { check_disk_change(inode->i_rdev); /* * If the drive is empty, just let the open fail. */ if (!rscsi_osds[target].ready) return -ENXIO; /* * Similarly, if the device has the write protect tab set, * have the open fail if the user expects to be able to write * to the thing. */ if ((rscsi_osds[target].write_prot) && (filp->f_mode & 2)) return -EROFS; } SDev = rscsi_osds[target].device; /* * It is possible that the osd changing stuff resulted in the device * being taken offline. If this is the case, report this to the user, * and don't pretend that * the open actually succeeded. */ if (!SDev->online) { return -ENXIO; } /* * See if we are requesting a non-existent partition. Do this * after checking for osd change. */ if (so_sizes[MINOR(inode->i_rdev)] == 0) return -ENXIO; if (SDev->removable) if (!SDev->access_count) if (scsi_block_when_processing_errors(SDev)) scsi_ioctl(SDev, SCSI_IOCTL_DOORLOCK, NULL); SDev->access_count++; if (SDev->host->hostt->module) __MOD_INC_USE_COUNT(SDev->host->hostt->module); if (so_template.module) __MOD_INC_USE_COUNT(so_template.module); return 0; } static int so_release(struct inode *inode, struct file *file) { int target; Scsi_Device * SDev; iscsi_trace(TRACE_OSDSO, "so_release()\n"); target = DEVICE_NR(inode->i_rdev); SDev = rscsi_osds[target].device; SDev->access_count--; if (SDev->removable) { if (!SDev->access_count) if (scsi_block_when_processing_errors(SDev)) scsi_ioctl(SDev, SCSI_IOCTL_DOORUNLOCK, NULL); } if (SDev->host->hostt->module) __MOD_DEC_USE_COUNT(SDev->host->hostt->module); if (so_template.module) __MOD_DEC_USE_COUNT(so_template.module); return 0; } static struct block_device_operations so_fops = { open: so_open, release: so_release, ioctl: so_ioctl, check_media_change: check_scsiosd_media_change, revalidate: fop_revalidate_scsiosd }; /* * If we need more than one SCSI osd major (i.e. more than * 16 SCSI osds), we'll have to kmalloc() more gendisks later. */ static struct gendisk so_gendisk = { SCSI_OSD_MAJOR, /* Major number */ "so", /* Major name */ 0, /* Bits to shift to get real from partition */ 1, /* Number of partitions per real */ NULL, /* hd struct */ NULL, /* block sizes */ 0, /* number */ NULL, /* internal */ NULL, /* next */ &so_fops, /* file operations */ }; /* * rw_intr is the interrupt routine for the device driver. * It will be notified on the end of a SCSI read / write, and * will take one of several actions based on success or failure. */ static void rw_intr(Scsi_Cmnd * SCpnt) { int result = SCpnt->result; char nbuff[6]; int this_count = SCpnt->bufflen >> 9; int good_sectors = (result == 0 ? this_count : 0); int block_sectors = 1; so_devname(DEVICE_NR(SCpnt->request.rq_dev), nbuff); iscsi_trace(TRACE_OSDSO, "rw_intr(/dev/%s, host %d, result 0x%x)\n", nbuff, SCpnt->host->host_no, result); /* Handle MEDIUM ERRORs that indicate partial success. Since this is a relatively rare error condition, no care is taken to avoid unnecessary additional work such as memcpy's that could be avoided. */ /* An error occurred */ if (driver_byte(result) != 0) { /* Sense data is valid */ if (SCpnt->sense_buffer[0] == 0xF0 && SCpnt->sense_buffer[2] == MEDIUM_ERROR) { long error_sector = (SCpnt->sense_buffer[3] << 24) | (SCpnt->sense_buffer[4] << 16) | (SCpnt->sense_buffer[5] << 8) | SCpnt->sense_buffer[6]; if (SCpnt->request.bh != NULL) block_sectors = SCpnt->request.bh->b_size >> 9; switch (SCpnt->device->sector_size) { case 1024: error_sector <<= 1; if (block_sectors < 2) block_sectors = 2; break; case 2048: error_sector <<= 2; if (block_sectors < 4) block_sectors = 4; break; case 4096: error_sector <<=3; if (block_sectors < 8) block_sectors = 8; break; case 256: error_sector >>= 1; break; default: break; } error_sector -= so[MINOR(SCpnt->request.rq_dev)].start_sect; error_sector &= ~(block_sectors - 1); good_sectors = error_sector - SCpnt->request.sector; if (good_sectors < 0 || good_sectors >= this_count) good_sectors = 0; } if (SCpnt->sense_buffer[2] == ILLEGAL_REQUEST) { if (SCpnt->device->ten == 1) { if (SCpnt->cmnd[0] == READ_10 || SCpnt->cmnd[0] == WRITE_10) SCpnt->device->ten = 0; } } } /* * This calls the generic completion function, now that we know * how many actual sectors finished, and how many sectors we need * to say have failed. */ scsi_io_completion(SCpnt, good_sectors, block_sectors); } /* * requeue_so_request() is the request handler function for the so driver. * Its function in life is to take block device requests, and translate * them to SCSI commands. */ static int check_scsiosd_media_change(kdev_t full_dev) { int retval; int target; int flag = 0; Scsi_Device * SDev; iscsi_trace(TRACE_OSDSO, "check_scsiosd_media_change()\n"); target = DEVICE_NR(full_dev); SDev = rscsi_osds[target].device; if (target >= so_template.dev_max || !SDev) { printk("SCSI osd request error: invalid device.\n"); return 0; } if (!SDev->removable) return 0; /* * If the device is offline, don't send any commands - just pretend as * if the command failed. If the device ever comes back online, we * can deal with it then. It is only because of unrecoverable errors * that we would ever take a device offline in the first place. */ if (SDev->online == FALSE) { rscsi_osds[target].ready = 0; SDev->changed = 1; return 1; /* This will force a flush, if called from * check_disk_change */ } /* Using Start/Stop enables differentiation between drive with * no cartridge loaded - NOT READY, drive with changed cartridge - * UNIT ATTENTION, or with same cartridge - GOOD STATUS. * This also handles drives that auto spin down. eg iomega jaz 1GB * as this will spin up the drive. */ retval = -ENODEV; if (scsi_block_when_processing_errors(SDev)) retval = scsi_ioctl(SDev, SCSI_IOCTL_START_UNIT, NULL); if (retval) { /* Unable to test, unit probably not ready. * This usually means there is no disc in the * drive. Mark as changed, and we will figure * it out later once the drive is available * again. */ rscsi_osds[target].ready = 0; SDev->changed = 1; return 1; /* This will force a flush, if called from * check_disk_change */ } /* * for removable scsi osd ( FLOPTICAL ) we have to recognise the * presence of osd in the drive. This is kept in the Scsi_Osd * struct and tested at open ! Daniel Roche ( dan@lectra.fr ) */ rscsi_osds[target].ready = 1; /* FLOPTICAL */ retval = SDev->changed; if (!flag) SDev->changed = 0; return retval; } static int so_init_oneosd(int i) { unsigned char cmd[10]; char nbuff[6]; Scsi_Request *SRpnt; iscsi_trace(TRACE_OSDSO, "so_init_oneosd(%i)\n", i); so_devname(i, nbuff); if (rscsi_osds[i].device->online == FALSE) { iscsi_trace_error("device is offline??\n"); return i; } /* * TEST_UNIT_READY */ SRpnt = scsi_allocate_request(rscsi_osds[i].device); cmd[0] = TEST_UNIT_READY; cmd[1] = (rscsi_osds[i].device->lun << 5) & 0xe0; memset((void *) &cmd[2], 0, 8); SRpnt->sr_cmd_len = 0; SRpnt->sr_sense_buffer[0] = 0; SRpnt->sr_sense_buffer[2] = 0; SRpnt->sr_data_direction = SCSI_DATA_READ; scsi_wait_req (SRpnt, (void *) cmd, NULL, 0, SO_TIMEOUT, MAX_RETRIES); if (SRpnt->sr_result!=0) { iscsi_trace_error("OSD not ready\n"); return i; } /* Initialize device */ rscsi_osds[i].capacity = 1048576*512; rscsi_osds[i].device->changed = 0; rscsi_osds[i].ready = 0; rscsi_osds[i].write_prot = 0; rscsi_osds[i].device->removable = 0; SRpnt->sr_device->ten = 1; SRpnt->sr_device->remap = 1; SRpnt->sr_device->sector_size = 512; /* printk("%s : block size assumed to be 512 bytes, osd size 1GB. \n", nbuff); */ /* Wake up a process waiting for device */ scsi_release_request(SRpnt); /* Cleanup */ SRpnt = NULL; return i; } /* * The so_init() function looks at all SCSI drives present, determines * their size, and reads partition table entries for them. */ static int so_registered; static int so_init() { int i; iscsi_trace(TRACE_OSDSO, "so_init()\n"); if (so_template.dev_noticed == 0) { iscsi_trace_error("no OSDs noticed\n"); return 0; } if (!rscsi_osds) { printf("%i osds detected \n", so_template.dev_noticed); so_template.dev_max = so_template.dev_noticed; } if (so_template.dev_max > SCSI_OSDS_PER_MAJOR) { iscsi_trace_error("so_template.dev_max (%i) > SCSI_OSDS_PER_MAJOR\n", so_template.dev_max); so_template.dev_max = SCSI_OSDS_PER_MAJOR; } if (!so_registered) { if (devfs_register_blkdev(SCSI_OSD_MAJOR, "so", &so_fops)) { printk("Unable to get major %d for SCSI osd\n", SCSI_OSD_MAJOR); return 1; } so_registered++; } /* No loadable devices yet */ if (rscsi_osds) return 0; /* Real devices */ if ((rscsi_osds = kmalloc(so_template.dev_max * sizeof(Scsi_Osd), GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_devfs; } memset(rscsi_osds, 0, so_template.dev_max * sizeof(Scsi_Osd)); so_gendisk.real_devices = (void *) rscsi_osds; /* Partition sizes */ if ((so_sizes=kmalloc(so_template.dev_max*sizeof(int), GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_rscsi_osds; } memset(so_sizes, 0, so_template.dev_max*sizeof(int)); so_gendisk.sizes = so_sizes; /* Block sizes */ if ((so_blocksizes=kmalloc(so_template.dev_max*sizeof(int), GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_so_sizes; } blksize_size[SCSI_OSD_MAJOR] = so_blocksizes; /* Sector sizes */ if ((so_hardsizes=kmalloc(so_template.dev_max*sizeof(int), GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_so_blocksizes; } hardsect_size[SCSI_OSD_MAJOR] = so_hardsizes; /* Partitions */ if ((so=kmalloc(so_template.dev_max*sizeof(struct hd_struct), GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_so_hardsizes; } memset(so, 0, so_template.dev_max*sizeof(struct hd_struct)); so_gendisk.part = so; /* Initialize Things */ for (i=0; i<so_template.dev_max; i++) { so_blocksizes[i] = 1024; /* minimum request/block size */ so_sizes[i] = 1; /* number of blocks */ so_hardsizes[i] = 512; /* sector size */ so[i].nr_sects = 2; /* number of sectors */ } /* ??? */ if ((so_gendisk.de_arr=kmalloc(SCSI_OSDS_PER_MAJOR*sizeof(*so_gendisk.de_arr), GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_so; } memset(so_gendisk.de_arr, 0, SCSI_OSDS_PER_MAJOR * sizeof *so_gendisk.de_arr); /* Flags */ if ((so_gendisk.flags=kmalloc(SCSI_OSDS_PER_MAJOR*sizeof(*so_gendisk.flags),GFP_ATOMIC))==NULL) { iscsi_trace_error("kmalloc() failed\n"); goto cleanup_so_gendisk_de_arr; } memset(so_gendisk.flags, 0, SCSI_OSDS_PER_MAJOR * sizeof *so_gendisk.flags); if (so_gendisk.next) { iscsi_trace_error("Why is this not NULL?\n"); so_gendisk.next = NULL; } return 0; cleanup_so_gendisk_de_arr: kfree(so_gendisk.de_arr); cleanup_so: kfree(so); cleanup_so_hardsizes: kfree(so_hardsizes); cleanup_so_blocksizes: kfree(so_blocksizes); cleanup_so_sizes: kfree(so_sizes); cleanup_rscsi_osds: kfree(rscsi_osds); cleanup_devfs: devfs_unregister_blkdev(SCSI_OSD_MAJOR, "so"); so_registered--; return 1; } static void so_finish() { struct gendisk *gendisk; int i; iscsi_trace(TRACE_OSDSO, "so_finish()\n"); blk_dev[SCSI_OSD_MAJOR].queue = so_find_queue; for (gendisk = gendisk_head; gendisk != NULL; gendisk = gendisk->next) if (gendisk == &so_gendisk) break; if (gendisk == NULL) { so_gendisk.next = gendisk_head; gendisk_head = &so_gendisk; } for (i = 0; i < so_template.dev_max; ++i) if (!rscsi_osds[i].capacity && rscsi_osds[i].device) { so_init_oneosd(i); if (!rscsi_osds[i].has_part_table) { so_sizes[i] = rscsi_osds[i].capacity; register_disk(&so_gendisk, MKDEV(MAJOR(i),MINOR(i)), 1, &so_fops, rscsi_osds[i].capacity); rscsi_osds[i].has_part_table = 1; } } /* No read-ahead right not */ read_ahead[SCSI_OSD_MAJOR] = 0; return; } static int so_detect(Scsi_Device * SDp) { char nbuff[6]; iscsi_trace(TRACE_OSDSO, "so_detect()\n"); if (SDp->type != TYPE_OSD && SDp->type != TYPE_MOD) return 0; so_devname(so_template.dev_noticed++, nbuff); printk("Detected scsi %sosd %s at scsi%d, channel %d, id %d, lun %d\n", SDp->removable ? "removable " : "", nbuff, SDp->host->host_no, SDp->channel, SDp->id, SDp->lun); return 1; } static int so_attach(Scsi_Device * SDp) { unsigned int devnum; Scsi_Osd *dpnt; int i; iscsi_trace(TRACE_OSDSO, "so_attach(SDpnt 0x%p)\n", SDp); if (SDp->type != TYPE_OSD && SDp->type != TYPE_MOD) return 0; if (so_template.nr_dev >= so_template.dev_max) { SDp->attached--; return 1; } for (dpnt = rscsi_osds, i = 0; i < so_template.dev_max; i++, dpnt++) if (!dpnt->device) break; if (i >= so_template.dev_max) panic("scsi_devices corrupt (so)"); rscsi_osds[i].device = SDp; rscsi_osds[i].has_part_table = 0; so_template.nr_dev++; so_gendisk.nr_real++; devnum = i % SCSI_OSDS_PER_MAJOR; so_gendisk.de_arr[devnum] = SDp->de; if (SDp->removable) so_gendisk.flags[devnum] |= GENHD_FL_REMOVABLE; return 0; } #define DEVICE_BUSY rscsi_osds[target].device->busy #define USAGE rscsi_osds[target].device->access_count #define CAPACITY rscsi_osds[target].capacity #define MAYBE_REINIT so_init_oneosd(target) /* This routine is called to flush all partitions and partition tables * for a changed scsi osd, and then re-read the new partition table. * If we are revalidating a osd because of a media change, then we * enter with usage == 0. If we are using an ioctl, we automatically have * usage == 1 (we need an open channel to use an ioctl :-), so this * is our limit. */ int revalidate_scsiosd(kdev_t dev, int maxusage) { int target; int max_p; int start; int i; iscsi_trace(TRACE_OSDSO, "revalidate_scsiosd()\n"); target = DEVICE_NR(dev); if (DEVICE_BUSY || USAGE > maxusage) { printk("Device busy for revalidation (usage=%d)\n", USAGE); return -EBUSY; } DEVICE_BUSY = 1; max_p = so_gendisk.max_p; start = target << so_gendisk.minor_shift; for (i = max_p - 1; i >= 0; i--) { int index = start + i; /* invalidate_device(MKDEV(MAJOR(index),MINOR(index)), 1); */ so_gendisk.part[index].start_sect = 0; so_gendisk.part[index].nr_sects = 0; /* * Reset the blocksize for everything so that we can read * the partition table. Technically we will determine the * correct block size when we revalidate, but we do this just * to make sure that everything remains consistent. */ so_blocksizes[index] = 1024; if (rscsi_osds[target].device->sector_size == 2048) so_blocksizes[index] = 2048; else so_blocksizes[index] = 1024; } #ifdef MAYBE_REINIT MAYBE_REINIT; #endif grok_partitions(&so_gendisk, target % SCSI_OSDS_PER_MAJOR, 1, CAPACITY); DEVICE_BUSY = 0; return 0; } static int fop_revalidate_scsiosd(kdev_t dev) { iscsi_trace(TRACE_OSDSO, "fop_revalidate_scsiosd()\n"); return revalidate_scsiosd(dev, 0); } static void so_detach(Scsi_Device * SDp) { Scsi_Osd *dpnt; int i, j; int max_p; int start; iscsi_trace(TRACE_OSDSO, "so_detach()\n"); for (dpnt = rscsi_osds, i = 0; i < so_template.dev_max; i++, dpnt++) if (dpnt->device == SDp) { /* If we are disconnecting a osd driver, sync and invalidate * everything */ max_p = so_gendisk.max_p; start = i << so_gendisk.minor_shift; for (j = max_p - 1; j >= 0; j--) { int index = start + j; /* invalidate_device(MKDEV(MAJOR(index),MINOR(index)), 1); */ so_gendisk.part[index].start_sect = 0; so_gendisk.part[index].nr_sects = 0; so_sizes[index] = 0; } devfs_register_partitions (&so_gendisk, MINOR(start), 1); /* unregister_disk() */ dpnt->has_part_table = 0; dpnt->device = NULL; dpnt->capacity = 0; SDp->attached--; so_template.dev_noticed--; so_template.nr_dev--; so_gendisk.nr_real--; return; } return; } static int __init init_so(void) { iscsi_trace(TRACE_OSDSO, "init_so()\n"); so_template.module = THIS_MODULE; return scsi_register_module(MODULE_SCSI_DEV, &so_template); } static void __exit exit_so(void) { struct gendisk **prev_sogd_link; struct gendisk *sogd; int removed = 0; iscsi_trace(TRACE_OSDSO, "exit_so()\n"); scsi_unregister_module(MODULE_SCSI_DEV, &so_template); devfs_unregister_blkdev(SCSI_OSD_MAJOR, "so"); so_registered--; if (rscsi_osds != NULL) { kfree(rscsi_osds); kfree(so_sizes); kfree(so_blocksizes); kfree(so_hardsizes); kfree((char *) so); /* * Now remove &so_gendisk from the linked list */ prev_sogd_link = &gendisk_head; while ((sogd = *prev_sogd_link) != NULL) { if (sogd >= &so_gendisk && sogd <= &so_gendisk) { removed++; *prev_sogd_link = sogd->next; continue; } prev_sogd_link = &sogd->next; } if (removed != 1) printk("%s %d &so_gendisk in osd chain", removed > 1 ? "total" : "just", removed); } blk_size[SCSI_OSD_MAJOR] = NULL; hardsect_size[SCSI_OSD_MAJOR] = NULL; read_ahead[SCSI_OSD_MAJOR] = 0; so_template.dev_max = 0; } module_init(init_so); module_exit(exit_so);