|  | /* | 
|  | * Copyright (C) 2004 Red Hat, Inc. All rights reserved. | 
|  | * | 
|  | * This file is released under the GPL. | 
|  | * | 
|  | * Multipath hardware handler registration. | 
|  | */ | 
|  |  | 
|  | #include "dm.h" | 
|  | #include "dm-hw-handler.h" | 
|  |  | 
|  | #include <linux/slab.h> | 
|  |  | 
|  | struct hwh_internal { | 
|  | struct hw_handler_type hwht; | 
|  |  | 
|  | struct list_head list; | 
|  | long use; | 
|  | }; | 
|  |  | 
|  | #define hwht_to_hwhi(__hwht) container_of((__hwht), struct hwh_internal, hwht) | 
|  |  | 
|  | static LIST_HEAD(_hw_handlers); | 
|  | static DECLARE_RWSEM(_hwh_lock); | 
|  |  | 
|  | static struct hwh_internal *__find_hw_handler_type(const char *name) | 
|  | { | 
|  | struct hwh_internal *hwhi; | 
|  |  | 
|  | list_for_each_entry(hwhi, &_hw_handlers, list) { | 
|  | if (!strcmp(name, hwhi->hwht.name)) | 
|  | return hwhi; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static struct hwh_internal *get_hw_handler(const char *name) | 
|  | { | 
|  | struct hwh_internal *hwhi; | 
|  |  | 
|  | down_read(&_hwh_lock); | 
|  | hwhi = __find_hw_handler_type(name); | 
|  | if (hwhi) { | 
|  | if ((hwhi->use == 0) && !try_module_get(hwhi->hwht.module)) | 
|  | hwhi = NULL; | 
|  | else | 
|  | hwhi->use++; | 
|  | } | 
|  | up_read(&_hwh_lock); | 
|  |  | 
|  | return hwhi; | 
|  | } | 
|  |  | 
|  | struct hw_handler_type *dm_get_hw_handler(const char *name) | 
|  | { | 
|  | struct hwh_internal *hwhi; | 
|  |  | 
|  | if (!name) | 
|  | return NULL; | 
|  |  | 
|  | hwhi = get_hw_handler(name); | 
|  | if (!hwhi) { | 
|  | request_module("dm-%s", name); | 
|  | hwhi = get_hw_handler(name); | 
|  | } | 
|  |  | 
|  | return hwhi ? &hwhi->hwht : NULL; | 
|  | } | 
|  |  | 
|  | void dm_put_hw_handler(struct hw_handler_type *hwht) | 
|  | { | 
|  | struct hwh_internal *hwhi; | 
|  |  | 
|  | if (!hwht) | 
|  | return; | 
|  |  | 
|  | down_read(&_hwh_lock); | 
|  | hwhi = __find_hw_handler_type(hwht->name); | 
|  | if (!hwhi) | 
|  | goto out; | 
|  |  | 
|  | if (--hwhi->use == 0) | 
|  | module_put(hwhi->hwht.module); | 
|  |  | 
|  | BUG_ON(hwhi->use < 0); | 
|  |  | 
|  | out: | 
|  | up_read(&_hwh_lock); | 
|  | } | 
|  |  | 
|  | static struct hwh_internal *_alloc_hw_handler(struct hw_handler_type *hwht) | 
|  | { | 
|  | struct hwh_internal *hwhi = kzalloc(sizeof(*hwhi), GFP_KERNEL); | 
|  |  | 
|  | if (hwhi) | 
|  | hwhi->hwht = *hwht; | 
|  |  | 
|  | return hwhi; | 
|  | } | 
|  |  | 
|  | int dm_register_hw_handler(struct hw_handler_type *hwht) | 
|  | { | 
|  | int r = 0; | 
|  | struct hwh_internal *hwhi = _alloc_hw_handler(hwht); | 
|  |  | 
|  | if (!hwhi) | 
|  | return -ENOMEM; | 
|  |  | 
|  | down_write(&_hwh_lock); | 
|  |  | 
|  | if (__find_hw_handler_type(hwht->name)) { | 
|  | kfree(hwhi); | 
|  | r = -EEXIST; | 
|  | } else | 
|  | list_add(&hwhi->list, &_hw_handlers); | 
|  |  | 
|  | up_write(&_hwh_lock); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | int dm_unregister_hw_handler(struct hw_handler_type *hwht) | 
|  | { | 
|  | struct hwh_internal *hwhi; | 
|  |  | 
|  | down_write(&_hwh_lock); | 
|  |  | 
|  | hwhi = __find_hw_handler_type(hwht->name); | 
|  | if (!hwhi) { | 
|  | up_write(&_hwh_lock); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (hwhi->use) { | 
|  | up_write(&_hwh_lock); | 
|  | return -ETXTBSY; | 
|  | } | 
|  |  | 
|  | list_del(&hwhi->list); | 
|  |  | 
|  | up_write(&_hwh_lock); | 
|  |  | 
|  | kfree(hwhi); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | unsigned dm_scsi_err_handler(struct hw_handler *hwh, struct bio *bio) | 
|  | { | 
|  | #if 0 | 
|  | int sense_key, asc, ascq; | 
|  |  | 
|  | if (bio->bi_error & BIO_SENSE) { | 
|  | /* FIXME: This is just an initial guess. */ | 
|  | /* key / asc / ascq */ | 
|  | sense_key = (bio->bi_error >> 16) & 0xff; | 
|  | asc = (bio->bi_error >> 8) & 0xff; | 
|  | ascq = bio->bi_error & 0xff; | 
|  |  | 
|  | switch (sense_key) { | 
|  | /* This block as a whole comes from the device. | 
|  | * So no point retrying on another path. */ | 
|  | case 0x03:	/* Medium error */ | 
|  | case 0x05:	/* Illegal request */ | 
|  | case 0x07:	/* Data protect */ | 
|  | case 0x08:	/* Blank check */ | 
|  | case 0x0a:	/* copy aborted */ | 
|  | case 0x0c:	/* obsolete - no clue ;-) */ | 
|  | case 0x0d:	/* volume overflow */ | 
|  | case 0x0e:	/* data miscompare */ | 
|  | case 0x0f:	/* reserved - no idea either. */ | 
|  | return MP_ERROR_IO; | 
|  |  | 
|  | /* For these errors it's unclear whether they | 
|  | * come from the device or the controller. | 
|  | * So just lets try a different path, and if | 
|  | * it eventually succeeds, user-space will clear | 
|  | * the paths again... */ | 
|  | case 0x02:	/* Not ready */ | 
|  | case 0x04:	/* Hardware error */ | 
|  | case 0x09:	/* vendor specific */ | 
|  | case 0x0b:	/* Aborted command */ | 
|  | return MP_FAIL_PATH; | 
|  |  | 
|  | case 0x06:	/* Unit attention - might want to decode */ | 
|  | if (asc == 0x04 && ascq == 0x01) | 
|  | /* "Unit in the process of | 
|  | * becoming ready" */ | 
|  | return 0; | 
|  | return MP_FAIL_PATH; | 
|  |  | 
|  | /* FIXME: For Unit Not Ready we may want | 
|  | * to have a generic pg activation | 
|  | * feature (START_UNIT). */ | 
|  |  | 
|  | /* Should these two ever end up in the | 
|  | * error path? I don't think so. */ | 
|  | case 0x00:	/* No sense */ | 
|  | case 0x01:	/* Recovered error */ | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* We got no idea how to decode the other kinds of errors -> | 
|  | * assume generic error condition. */ | 
|  | return MP_FAIL_PATH; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(dm_register_hw_handler); | 
|  | EXPORT_SYMBOL_GPL(dm_unregister_hw_handler); | 
|  | EXPORT_SYMBOL_GPL(dm_scsi_err_handler); |