| /* | 
 |  * 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); | 
 |  | 
 | 	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); |