aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/md/dm-hw-handler.c
blob: ae63772e44c9c9f2b359e127704335f110aec12e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/*
 * 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);