/* * Basic HP/COMPAQ MSA 1000 support. This is only needed if your HW cannot be * upgraded. * * Copyright (C) 2006 Red Hat, Inc. All rights reserved. * Copyright (C) 2006 Mike Christie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #define HP_SW_NAME "hp_sw" #define HP_SW_TIMEOUT (60 * HZ) #define HP_SW_RETRIES 3 struct hp_sw_dh_data { unsigned char sense[SCSI_SENSE_BUFFERSIZE]; int retries; }; static inline struct hp_sw_dh_data *get_hp_sw_data(struct scsi_device *sdev) { struct scsi_dh_data *scsi_dh_data = sdev->scsi_dh_data; BUG_ON(scsi_dh_data == NULL); return ((struct hp_sw_dh_data *) scsi_dh_data->buf); } static int hp_sw_done(struct scsi_device *sdev) { struct hp_sw_dh_data *h = get_hp_sw_data(sdev); struct scsi_sense_hdr sshdr; int rc; sdev_printk(KERN_INFO, sdev, "hp_sw_done\n"); rc = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE, &sshdr); if (!rc) goto done; switch (sshdr.sense_key) { case NOT_READY: if ((sshdr.asc == 0x04) && (sshdr.ascq == 3)) { rc = SCSI_DH_RETRY; h->retries++; break; } /* fall through */ default: h->retries++; rc = SCSI_DH_IMM_RETRY; } done: if (rc == SCSI_DH_OK || rc == SCSI_DH_IO) h->retries = 0; else if (h->retries > HP_SW_RETRIES) { h->retries = 0; rc = SCSI_DH_IO; } return rc; } static int hp_sw_activate(struct scsi_device *sdev) { struct hp_sw_dh_data *h = get_hp_sw_data(sdev); struct request *req; int ret = SCSI_DH_RES_TEMP_UNAVAIL; req = blk_get_request(sdev->request_queue, WRITE, GFP_ATOMIC); if (!req) goto done; sdev_printk(KERN_INFO, sdev, "sending START_STOP."); req->cmd_type = REQ_TYPE_BLOCK_PC; req->cmd_flags |= REQ_FAILFAST; req->cmd_len = COMMAND_SIZE(START_STOP); memset(req->cmd, 0, MAX_COMMAND_SIZE); req->cmd[0] = START_STOP; req->cmd[4] = 1; /* Start spin cycle */ req->timeout = HP_SW_TIMEOUT; req->sense = h->sense; memset(req->sense, 0, SCSI_SENSE_BUFFERSIZE); req->sense_len = 0; ret = blk_execute_rq(req->q, NULL, req, 1); if (!ret) /* SUCCESS */ ret = hp_sw_done(sdev); else ret = SCSI_DH_IO; done: return ret; } static const struct { char *vendor; char *model; } hp_sw_dh_data_list[] = { {"COMPAQ", "MSA"}, {"HP", "HSV"}, {"DEC", "HSG80"}, {NULL, NULL}, }; static int hp_sw_bus_notify(struct notifier_block *, unsigned long, void *); static struct scsi_device_handler hp_sw_dh = { .name = HP_SW_NAME, .module = THIS_MODULE, .nb.notifier_call = hp_sw_bus_notify, .activate = hp_sw_activate, }; static int hp_sw_bus_notify(struct notifier_block *nb, unsigned long action, void *data) { struct device *dev = data; struct scsi_device *sdev; struct scsi_dh_data *scsi_dh_data; int i, found = 0; unsigned long flags; if (!scsi_is_sdev_device(dev)) return 0; sdev = to_scsi_device(dev); if (action == BUS_NOTIFY_ADD_DEVICE) { for (i = 0; hp_sw_dh_data_list[i].vendor; i++) { if (!strncmp(sdev->vendor, hp_sw_dh_data_list[i].vendor, strlen(hp_sw_dh_data_list[i].vendor)) && !strncmp(sdev->model, hp_sw_dh_data_list[i].model, strlen(hp_sw_dh_data_list[i].model))) { found = 1; break; } } if (!found) goto out; scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *) + sizeof(struct hp_sw_dh_data) , GFP_KERNEL); if (!scsi_dh_data) { sdev_printk(KERN_ERR, sdev, "Attach Failed %s.\n", HP_SW_NAME); goto out; } scsi_dh_data->scsi_dh = &hp_sw_dh; spin_lock_irqsave(sdev->request_queue->queue_lock, flags); sdev->scsi_dh_data = scsi_dh_data; spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags); try_module_get(THIS_MODULE); sdev_printk(KERN_NOTICE, sdev, "Attached %s.\n", HP_SW_NAME); } else if (action == BUS_NOTIFY_DEL_DEVICE) { if (sdev->scsi_dh_data == NULL || sdev->scsi_dh_data->scsi_dh != &hp_sw_dh) goto out; spin_lock_irqsave(sdev->request_queue->queue_lock, flags); scsi_dh_data = sdev->scsi_dh_data; sdev->scsi_dh_data = NULL; spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags); module_put(THIS_MODULE); sdev_printk(KERN_NOTICE, sdev, "Dettached %s.\n", HP_SW_NAME); kfree(scsi_dh_data); } out: return 0; } static int __init hp_sw_init(void) { return scsi_register_device_handler(&hp_sw_dh); } static void __exit hp_sw_exit(void) { scsi_unregister_device_handler(&hp_sw_dh); } module_init(hp_sw_init); module_exit(hp_sw_exit); MODULE_DESCRIPTION("HP MSA 1000"); MODULE_AUTHOR("Mike Christie