| /* |
| * 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); |
| |
| 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); |
| |
| if (hwhi->use < 0) |
| BUG(); |
| |
| out: |
| up_read(&_hwh_lock); |
| } |
| |
| static struct hwh_internal *_alloc_hw_handler(struct hw_handler_type *hwht) |
| { |
| struct hwh_internal *hwhi = kmalloc(sizeof(*hwhi), GFP_KERNEL); |
| |
| if (hwhi) { |
| memset(hwhi, 0, sizeof(*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); |