]>
Commit | Line | Data |
---|---|---|
f6dd337e MC |
1 | /* |
2 | * Basic HP/COMPAQ MSA 1000 support. This is only needed if your HW cannot be | |
3 | * upgraded. | |
4 | * | |
5 | * Copyright (C) 2006 Red Hat, Inc. All rights reserved. | |
6 | * Copyright (C) 2006 Mike Christie | |
2aef6d5c | 7 | * Copyright (C) 2008 Hannes Reinecke <hare@suse.de> |
f6dd337e MC |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2, or (at your option) | |
12 | * any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; see the file COPYING. If not, write to | |
21 | * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
22 | */ | |
23 | ||
5a0e3ad6 | 24 | #include <linux/slab.h> |
acf3368f | 25 | #include <linux/module.h> |
f6dd337e MC |
26 | #include <scsi/scsi.h> |
27 | #include <scsi/scsi_dbg.h> | |
28 | #include <scsi/scsi_eh.h> | |
29 | #include <scsi/scsi_dh.h> | |
30 | ||
2aef6d5c | 31 | #define HP_SW_NAME "hp_sw" |
f6dd337e | 32 | |
2aef6d5c HR |
33 | #define HP_SW_TIMEOUT (60 * HZ) |
34 | #define HP_SW_RETRIES 3 | |
35 | ||
36 | #define HP_SW_PATH_UNINITIALIZED -1 | |
37 | #define HP_SW_PATH_ACTIVE 0 | |
38 | #define HP_SW_PATH_PASSIVE 1 | |
f6dd337e MC |
39 | |
40 | struct hp_sw_dh_data { | |
41 | unsigned char sense[SCSI_SENSE_BUFFERSIZE]; | |
2aef6d5c | 42 | int path_state; |
f6dd337e | 43 | int retries; |
4e2ef86c CS |
44 | int retry_cnt; |
45 | struct scsi_device *sdev; | |
46 | activate_complete callback_fn; | |
47 | void *callback_data; | |
f6dd337e MC |
48 | }; |
49 | ||
4e2ef86c CS |
50 | static int hp_sw_start_stop(struct hp_sw_dh_data *); |
51 | ||
f6dd337e MC |
52 | static inline struct hp_sw_dh_data *get_hp_sw_data(struct scsi_device *sdev) |
53 | { | |
54 | struct scsi_dh_data *scsi_dh_data = sdev->scsi_dh_data; | |
55 | BUG_ON(scsi_dh_data == NULL); | |
56 | return ((struct hp_sw_dh_data *) scsi_dh_data->buf); | |
57 | } | |
58 | ||
2aef6d5c HR |
59 | /* |
60 | * tur_done - Handle TEST UNIT READY return status | |
61 | * @sdev: sdev the command has been sent to | |
62 | * @errors: blk error code | |
63 | * | |
64 | * Returns SCSI_DH_DEV_OFFLINED if the sdev is on the passive path | |
65 | */ | |
66 | static int tur_done(struct scsi_device *sdev, unsigned char *sense) | |
f6dd337e | 67 | { |
f6dd337e | 68 | struct scsi_sense_hdr sshdr; |
2aef6d5c | 69 | int ret; |
f6dd337e | 70 | |
2aef6d5c HR |
71 | ret = scsi_normalize_sense(sense, SCSI_SENSE_BUFFERSIZE, &sshdr); |
72 | if (!ret) { | |
73 | sdev_printk(KERN_WARNING, sdev, | |
74 | "%s: sending tur failed, no sense available\n", | |
75 | HP_SW_NAME); | |
76 | ret = SCSI_DH_IO; | |
f6dd337e | 77 | goto done; |
2aef6d5c | 78 | } |
f6dd337e | 79 | switch (sshdr.sense_key) { |
2aef6d5c HR |
80 | case UNIT_ATTENTION: |
81 | ret = SCSI_DH_IMM_RETRY; | |
82 | break; | |
f6dd337e | 83 | case NOT_READY: |
2aef6d5c HR |
84 | if ((sshdr.asc == 0x04) && (sshdr.ascq == 2)) { |
85 | /* | |
86 | * LUN not ready - Initialization command required | |
87 | * | |
88 | * This is the passive path | |
89 | */ | |
90 | ret = SCSI_DH_DEV_OFFLINED; | |
f6dd337e MC |
91 | break; |
92 | } | |
2aef6d5c | 93 | /* Fallthrough */ |
f6dd337e | 94 | default: |
2aef6d5c HR |
95 | sdev_printk(KERN_WARNING, sdev, |
96 | "%s: sending tur failed, sense %x/%x/%x\n", | |
97 | HP_SW_NAME, sshdr.sense_key, sshdr.asc, | |
98 | sshdr.ascq); | |
99 | break; | |
f6dd337e MC |
100 | } |
101 | ||
102 | done: | |
2aef6d5c HR |
103 | return ret; |
104 | } | |
105 | ||
106 | /* | |
107 | * hp_sw_tur - Send TEST UNIT READY | |
108 | * @sdev: sdev command should be sent to | |
109 | * | |
110 | * Use the TEST UNIT READY command to determine | |
111 | * the path state. | |
112 | */ | |
113 | static int hp_sw_tur(struct scsi_device *sdev, struct hp_sw_dh_data *h) | |
114 | { | |
115 | struct request *req; | |
116 | int ret; | |
117 | ||
febd7a5c | 118 | retry: |
2aef6d5c | 119 | req = blk_get_request(sdev->request_queue, WRITE, GFP_NOIO); |
a492f075 | 120 | if (IS_ERR(req)) |
2aef6d5c HR |
121 | return SCSI_DH_RES_TEMP_UNAVAIL; |
122 | ||
f27b087b | 123 | blk_rq_set_block_pc(req); |
6000a368 MC |
124 | req->cmd_flags |= REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT | |
125 | REQ_FAILFAST_DRIVER; | |
2aef6d5c | 126 | req->cmd_len = COMMAND_SIZE(TEST_UNIT_READY); |
2aef6d5c HR |
127 | req->cmd[0] = TEST_UNIT_READY; |
128 | req->timeout = HP_SW_TIMEOUT; | |
129 | req->sense = h->sense; | |
130 | memset(req->sense, 0, SCSI_SENSE_BUFFERSIZE); | |
131 | req->sense_len = 0; | |
132 | ||
2aef6d5c HR |
133 | ret = blk_execute_rq(req->q, NULL, req, 1); |
134 | if (ret == -EIO) { | |
135 | if (req->sense_len > 0) { | |
136 | ret = tur_done(sdev, h->sense); | |
137 | } else { | |
138 | sdev_printk(KERN_WARNING, sdev, | |
139 | "%s: sending tur failed with %x\n", | |
140 | HP_SW_NAME, req->errors); | |
141 | ret = SCSI_DH_IO; | |
142 | } | |
143 | } else { | |
144 | h->path_state = HP_SW_PATH_ACTIVE; | |
145 | ret = SCSI_DH_OK; | |
146 | } | |
febd7a5c AB |
147 | if (ret == SCSI_DH_IMM_RETRY) { |
148 | blk_put_request(req); | |
2aef6d5c | 149 | goto retry; |
febd7a5c | 150 | } |
2aef6d5c HR |
151 | if (ret == SCSI_DH_DEV_OFFLINED) { |
152 | h->path_state = HP_SW_PATH_PASSIVE; | |
153 | ret = SCSI_DH_OK; | |
154 | } | |
155 | ||
156 | blk_put_request(req); | |
157 | ||
158 | return ret; | |
159 | } | |
160 | ||
161 | /* | |
162 | * start_done - Handle START STOP UNIT return status | |
163 | * @sdev: sdev the command has been sent to | |
164 | * @errors: blk error code | |
165 | */ | |
166 | static int start_done(struct scsi_device *sdev, unsigned char *sense) | |
167 | { | |
168 | struct scsi_sense_hdr sshdr; | |
169 | int rc; | |
170 | ||
171 | rc = scsi_normalize_sense(sense, SCSI_SENSE_BUFFERSIZE, &sshdr); | |
172 | if (!rc) { | |
173 | sdev_printk(KERN_WARNING, sdev, | |
174 | "%s: sending start_stop_unit failed, " | |
175 | "no sense available\n", | |
176 | HP_SW_NAME); | |
177 | return SCSI_DH_IO; | |
178 | } | |
179 | switch (sshdr.sense_key) { | |
180 | case NOT_READY: | |
181 | if ((sshdr.asc == 0x04) && (sshdr.ascq == 3)) { | |
182 | /* | |
183 | * LUN not ready - manual intervention required | |
184 | * | |
185 | * Switch-over in progress, retry. | |
186 | */ | |
187 | rc = SCSI_DH_RETRY; | |
188 | break; | |
189 | } | |
190 | /* fall through */ | |
191 | default: | |
192 | sdev_printk(KERN_WARNING, sdev, | |
193 | "%s: sending start_stop_unit failed, sense %x/%x/%x\n", | |
194 | HP_SW_NAME, sshdr.sense_key, sshdr.asc, | |
195 | sshdr.ascq); | |
f6dd337e MC |
196 | rc = SCSI_DH_IO; |
197 | } | |
2aef6d5c | 198 | |
f6dd337e MC |
199 | return rc; |
200 | } | |
201 | ||
4e2ef86c CS |
202 | static void start_stop_endio(struct request *req, int error) |
203 | { | |
204 | struct hp_sw_dh_data *h = req->end_io_data; | |
205 | unsigned err = SCSI_DH_OK; | |
206 | ||
207 | if (error || host_byte(req->errors) != DID_OK || | |
208 | msg_byte(req->errors) != COMMAND_COMPLETE) { | |
209 | sdev_printk(KERN_WARNING, h->sdev, | |
210 | "%s: sending start_stop_unit failed with %x\n", | |
211 | HP_SW_NAME, req->errors); | |
212 | err = SCSI_DH_IO; | |
213 | goto done; | |
214 | } | |
215 | ||
216 | if (req->sense_len > 0) { | |
217 | err = start_done(h->sdev, h->sense); | |
218 | if (err == SCSI_DH_RETRY) { | |
219 | err = SCSI_DH_IO; | |
220 | if (--h->retry_cnt) { | |
221 | blk_put_request(req); | |
222 | err = hp_sw_start_stop(h); | |
223 | if (err == SCSI_DH_OK) | |
224 | return; | |
225 | } | |
226 | } | |
227 | } | |
228 | done: | |
7a1e9d82 MS |
229 | req->end_io_data = NULL; |
230 | __blk_put_request(req->q, req); | |
4e2ef86c CS |
231 | if (h->callback_fn) { |
232 | h->callback_fn(h->callback_data, err); | |
233 | h->callback_fn = h->callback_data = NULL; | |
234 | } | |
235 | return; | |
236 | ||
237 | } | |
238 | ||
2aef6d5c HR |
239 | /* |
240 | * hp_sw_start_stop - Send START STOP UNIT command | |
241 | * @sdev: sdev command should be sent to | |
242 | * | |
243 | * Sending START STOP UNIT activates the SP. | |
244 | */ | |
4e2ef86c | 245 | static int hp_sw_start_stop(struct hp_sw_dh_data *h) |
f6dd337e | 246 | { |
f6dd337e | 247 | struct request *req; |
f6dd337e | 248 | |
4e2ef86c | 249 | req = blk_get_request(h->sdev->request_queue, WRITE, GFP_ATOMIC); |
a492f075 | 250 | if (IS_ERR(req)) |
2aef6d5c | 251 | return SCSI_DH_RES_TEMP_UNAVAIL; |
f6dd337e | 252 | |
f27b087b | 253 | blk_rq_set_block_pc(req); |
6000a368 MC |
254 | req->cmd_flags |= REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT | |
255 | REQ_FAILFAST_DRIVER; | |
f6dd337e | 256 | req->cmd_len = COMMAND_SIZE(START_STOP); |
f6dd337e MC |
257 | req->cmd[0] = START_STOP; |
258 | req->cmd[4] = 1; /* Start spin cycle */ | |
259 | req->timeout = HP_SW_TIMEOUT; | |
260 | req->sense = h->sense; | |
261 | memset(req->sense, 0, SCSI_SENSE_BUFFERSIZE); | |
262 | req->sense_len = 0; | |
4e2ef86c | 263 | req->end_io_data = h; |
2aef6d5c | 264 | |
4e2ef86c CS |
265 | blk_execute_rq_nowait(req->q, NULL, req, 1, start_stop_endio); |
266 | return SCSI_DH_OK; | |
2aef6d5c HR |
267 | } |
268 | ||
269 | static int hp_sw_prep_fn(struct scsi_device *sdev, struct request *req) | |
270 | { | |
271 | struct hp_sw_dh_data *h = get_hp_sw_data(sdev); | |
272 | int ret = BLKPREP_OK; | |
273 | ||
274 | if (h->path_state != HP_SW_PATH_ACTIVE) { | |
275 | ret = BLKPREP_KILL; | |
276 | req->cmd_flags |= REQ_QUIET; | |
277 | } | |
278 | return ret; | |
279 | ||
280 | } | |
281 | ||
282 | /* | |
283 | * hp_sw_activate - Activate a path | |
284 | * @sdev: sdev on the path to be activated | |
285 | * | |
286 | * The HP Active/Passive firmware is pretty simple; | |
287 | * the passive path reports NOT READY with sense codes | |
288 | * 0x04/0x02; a START STOP UNIT command will then | |
289 | * activate the passive path (and deactivate the | |
290 | * previously active one). | |
291 | */ | |
3ae31f6a CS |
292 | static int hp_sw_activate(struct scsi_device *sdev, |
293 | activate_complete fn, void *data) | |
2aef6d5c HR |
294 | { |
295 | int ret = SCSI_DH_OK; | |
296 | struct hp_sw_dh_data *h = get_hp_sw_data(sdev); | |
297 | ||
298 | ret = hp_sw_tur(sdev, h); | |
299 | ||
300 | if (ret == SCSI_DH_OK && h->path_state == HP_SW_PATH_PASSIVE) { | |
4e2ef86c CS |
301 | h->retry_cnt = h->retries; |
302 | h->callback_fn = fn; | |
303 | h->callback_data = data; | |
304 | ret = hp_sw_start_stop(h); | |
2aef6d5c | 305 | if (ret == SCSI_DH_OK) |
4e2ef86c CS |
306 | return 0; |
307 | h->callback_fn = h->callback_data = NULL; | |
2aef6d5c HR |
308 | } |
309 | ||
3ae31f6a CS |
310 | if (fn) |
311 | fn(data, ret); | |
312 | return 0; | |
f6dd337e MC |
313 | } |
314 | ||
f08c0761 | 315 | static const struct scsi_dh_devlist hp_sw_dh_data_list[] = { |
2aef6d5c HR |
316 | {"COMPAQ", "MSA1000 VOLUME"}, |
317 | {"COMPAQ", "HSV110"}, | |
318 | {"HP", "HSV100"}, | |
f6dd337e MC |
319 | {"DEC", "HSG80"}, |
320 | {NULL, NULL}, | |
321 | }; | |
322 | ||
a315969e MB |
323 | static bool hp_sw_match(struct scsi_device *sdev) |
324 | { | |
325 | int i; | |
326 | ||
327 | if (scsi_device_tpgs(sdev)) | |
328 | return false; | |
329 | ||
330 | for (i = 0; hp_sw_dh_data_list[i].vendor; i++) { | |
331 | if (!strncmp(sdev->vendor, hp_sw_dh_data_list[i].vendor, | |
332 | strlen(hp_sw_dh_data_list[i].vendor)) && | |
333 | !strncmp(sdev->model, hp_sw_dh_data_list[i].model, | |
334 | strlen(hp_sw_dh_data_list[i].model))) { | |
335 | return true; | |
336 | } | |
337 | } | |
338 | return false; | |
339 | } | |
340 | ||
765cbc6d HR |
341 | static int hp_sw_bus_attach(struct scsi_device *sdev); |
342 | static void hp_sw_bus_detach(struct scsi_device *sdev); | |
f6dd337e MC |
343 | |
344 | static struct scsi_device_handler hp_sw_dh = { | |
345 | .name = HP_SW_NAME, | |
346 | .module = THIS_MODULE, | |
765cbc6d HR |
347 | .devlist = hp_sw_dh_data_list, |
348 | .attach = hp_sw_bus_attach, | |
349 | .detach = hp_sw_bus_detach, | |
f6dd337e | 350 | .activate = hp_sw_activate, |
2aef6d5c | 351 | .prep_fn = hp_sw_prep_fn, |
a315969e | 352 | .match = hp_sw_match, |
f6dd337e MC |
353 | }; |
354 | ||
765cbc6d | 355 | static int hp_sw_bus_attach(struct scsi_device *sdev) |
f6dd337e | 356 | { |
f6dd337e | 357 | struct scsi_dh_data *scsi_dh_data; |
2aef6d5c | 358 | struct hp_sw_dh_data *h; |
f6dd337e | 359 | unsigned long flags; |
2aef6d5c | 360 | int ret; |
f6dd337e | 361 | |
9dfeb315 HD |
362 | scsi_dh_data = kzalloc(sizeof(*scsi_dh_data) |
363 | + sizeof(*h) , GFP_KERNEL); | |
765cbc6d | 364 | if (!scsi_dh_data) { |
2aef6d5c | 365 | sdev_printk(KERN_ERR, sdev, "%s: Attach Failed\n", |
765cbc6d | 366 | HP_SW_NAME); |
33af79d1 | 367 | return 0; |
765cbc6d | 368 | } |
33af79d1 | 369 | |
765cbc6d | 370 | scsi_dh_data->scsi_dh = &hp_sw_dh; |
2aef6d5c HR |
371 | h = (struct hp_sw_dh_data *) scsi_dh_data->buf; |
372 | h->path_state = HP_SW_PATH_UNINITIALIZED; | |
373 | h->retries = HP_SW_RETRIES; | |
4e2ef86c | 374 | h->sdev = sdev; |
2aef6d5c HR |
375 | |
376 | ret = hp_sw_tur(sdev, h); | |
377 | if (ret != SCSI_DH_OK || h->path_state == HP_SW_PATH_UNINITIALIZED) | |
378 | goto failed; | |
379 | ||
380 | if (!try_module_get(THIS_MODULE)) | |
381 | goto failed; | |
382 | ||
765cbc6d HR |
383 | spin_lock_irqsave(sdev->request_queue->queue_lock, flags); |
384 | sdev->scsi_dh_data = scsi_dh_data; | |
385 | spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags); | |
f6dd337e | 386 | |
2aef6d5c HR |
387 | sdev_printk(KERN_INFO, sdev, "%s: attached to %s path\n", |
388 | HP_SW_NAME, h->path_state == HP_SW_PATH_ACTIVE? | |
389 | "active":"passive"); | |
f6dd337e | 390 | |
765cbc6d | 391 | return 0; |
2aef6d5c HR |
392 | |
393 | failed: | |
394 | kfree(scsi_dh_data); | |
395 | sdev_printk(KERN_ERR, sdev, "%s: not attached\n", | |
396 | HP_SW_NAME); | |
397 | return -EINVAL; | |
765cbc6d | 398 | } |
f6dd337e | 399 | |
765cbc6d HR |
400 | static void hp_sw_bus_detach( struct scsi_device *sdev ) |
401 | { | |
402 | struct scsi_dh_data *scsi_dh_data; | |
403 | unsigned long flags; | |
f6dd337e | 404 | |
765cbc6d HR |
405 | spin_lock_irqsave(sdev->request_queue->queue_lock, flags); |
406 | scsi_dh_data = sdev->scsi_dh_data; | |
407 | sdev->scsi_dh_data = NULL; | |
408 | spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags); | |
409 | module_put(THIS_MODULE); | |
f6dd337e | 410 | |
2aef6d5c | 411 | sdev_printk(KERN_NOTICE, sdev, "%s: Detached\n", HP_SW_NAME); |
f6dd337e | 412 | |
765cbc6d | 413 | kfree(scsi_dh_data); |
f6dd337e MC |
414 | } |
415 | ||
416 | static int __init hp_sw_init(void) | |
417 | { | |
418 | return scsi_register_device_handler(&hp_sw_dh); | |
419 | } | |
420 | ||
421 | static void __exit hp_sw_exit(void) | |
422 | { | |
423 | scsi_unregister_device_handler(&hp_sw_dh); | |
424 | } | |
425 | ||
426 | module_init(hp_sw_init); | |
427 | module_exit(hp_sw_exit); | |
428 | ||
2aef6d5c | 429 | MODULE_DESCRIPTION("HP Active/Passive driver"); |
f6dd337e MC |
430 | MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu"); |
431 | MODULE_LICENSE("GPL"); |