1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Basic HP/COMPAQ MSA 1000 support. This is only needed if your HW cannot be |
4 | * upgraded. |
5 | * |
6 | * Copyright (C) 2006 Red Hat, Inc. All rights reserved. |
7 | * Copyright (C) 2006 Mike Christie |
8 | * Copyright (C) 2008 Hannes Reinecke <hare@suse.de> |
9 | */ |
10 | |
11 | #include <linux/slab.h> |
12 | #include <linux/module.h> |
13 | #include <scsi/scsi.h> |
14 | #include <scsi/scsi_dbg.h> |
15 | #include <scsi/scsi_eh.h> |
16 | #include <scsi/scsi_dh.h> |
17 | |
18 | #define HP_SW_NAME "hp_sw" |
19 | |
20 | #define HP_SW_TIMEOUT (60 * HZ) |
21 | #define HP_SW_RETRIES 3 |
22 | |
23 | #define HP_SW_PATH_UNINITIALIZED -1 |
24 | #define HP_SW_PATH_ACTIVE 0 |
25 | #define HP_SW_PATH_PASSIVE 1 |
26 | |
27 | struct hp_sw_dh_data { |
28 | int path_state; |
29 | int retries; |
30 | int retry_cnt; |
31 | struct scsi_device *sdev; |
32 | }; |
33 | |
34 | static int hp_sw_start_stop(struct hp_sw_dh_data *); |
35 | |
36 | /* |
37 | * tur_done - Handle TEST UNIT READY return status |
38 | * @sdev: sdev the command has been sent to |
39 | * @errors: blk error code |
40 | * |
41 | * Returns SCSI_DH_DEV_OFFLINED if the sdev is on the passive path |
42 | */ |
43 | static int tur_done(struct scsi_device *sdev, struct hp_sw_dh_data *h, |
44 | struct scsi_sense_hdr *sshdr) |
45 | { |
46 | int ret = SCSI_DH_IO; |
47 | |
48 | switch (sshdr->sense_key) { |
49 | case UNIT_ATTENTION: |
50 | ret = SCSI_DH_IMM_RETRY; |
51 | break; |
52 | case NOT_READY: |
53 | if (sshdr->asc == 0x04 && sshdr->ascq == 2) { |
54 | /* |
55 | * LUN not ready - Initialization command required |
56 | * |
57 | * This is the passive path |
58 | */ |
59 | h->path_state = HP_SW_PATH_PASSIVE; |
60 | ret = SCSI_DH_OK; |
61 | break; |
62 | } |
63 | fallthrough; |
64 | default: |
65 | sdev_printk(KERN_WARNING, sdev, |
66 | "%s: sending tur failed, sense %x/%x/%x\n" , |
67 | HP_SW_NAME, sshdr->sense_key, sshdr->asc, |
68 | sshdr->ascq); |
69 | break; |
70 | } |
71 | return ret; |
72 | } |
73 | |
74 | /* |
75 | * hp_sw_tur - Send TEST UNIT READY |
76 | * @sdev: sdev command should be sent to |
77 | * |
78 | * Use the TEST UNIT READY command to determine |
79 | * the path state. |
80 | */ |
81 | static int hp_sw_tur(struct scsi_device *sdev, struct hp_sw_dh_data *h) |
82 | { |
83 | unsigned char cmd[6] = { TEST_UNIT_READY }; |
84 | struct scsi_sense_hdr sshdr; |
85 | int ret, res; |
86 | blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV | |
87 | REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; |
88 | const struct scsi_exec_args exec_args = { |
89 | .sshdr = &sshdr, |
90 | }; |
91 | |
92 | retry: |
93 | res = scsi_execute_cmd(sdev, cmd, opf, NULL, bufflen: 0, HP_SW_TIMEOUT, |
94 | HP_SW_RETRIES, args: &exec_args); |
95 | if (res > 0 && scsi_sense_valid(sshdr: &sshdr)) { |
96 | ret = tur_done(sdev, h, sshdr: &sshdr); |
97 | } else if (res == 0) { |
98 | h->path_state = HP_SW_PATH_ACTIVE; |
99 | ret = SCSI_DH_OK; |
100 | } else { |
101 | sdev_printk(KERN_WARNING, sdev, |
102 | "%s: sending tur failed with %x\n" , |
103 | HP_SW_NAME, res); |
104 | ret = SCSI_DH_IO; |
105 | } |
106 | |
107 | if (ret == SCSI_DH_IMM_RETRY) |
108 | goto retry; |
109 | |
110 | return ret; |
111 | } |
112 | |
113 | /* |
114 | * hp_sw_start_stop - Send START STOP UNIT command |
115 | * @sdev: sdev command should be sent to |
116 | * |
117 | * Sending START STOP UNIT activates the SP. |
118 | */ |
119 | static int hp_sw_start_stop(struct hp_sw_dh_data *h) |
120 | { |
121 | unsigned char cmd[6] = { START_STOP, 0, 0, 0, 1, 0 }; |
122 | struct scsi_sense_hdr sshdr; |
123 | struct scsi_device *sdev = h->sdev; |
124 | int res, rc; |
125 | int retry_cnt = HP_SW_RETRIES; |
126 | blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV | |
127 | REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; |
128 | const struct scsi_exec_args exec_args = { |
129 | .sshdr = &sshdr, |
130 | }; |
131 | |
132 | retry: |
133 | res = scsi_execute_cmd(sdev, cmd, opf, NULL, bufflen: 0, HP_SW_TIMEOUT, |
134 | HP_SW_RETRIES, args: &exec_args); |
135 | if (!res) { |
136 | return SCSI_DH_OK; |
137 | } else if (res < 0 || !scsi_sense_valid(sshdr: &sshdr)) { |
138 | sdev_printk(KERN_WARNING, sdev, |
139 | "%s: sending start_stop_unit failed, " |
140 | "no sense available\n" , HP_SW_NAME); |
141 | return SCSI_DH_IO; |
142 | } |
143 | |
144 | switch (sshdr.sense_key) { |
145 | case NOT_READY: |
146 | if (sshdr.asc == 0x04 && sshdr.ascq == 3) { |
147 | /* |
148 | * LUN not ready - manual intervention required |
149 | * |
150 | * Switch-over in progress, retry. |
151 | */ |
152 | if (--retry_cnt) |
153 | goto retry; |
154 | rc = SCSI_DH_RETRY; |
155 | break; |
156 | } |
157 | fallthrough; |
158 | default: |
159 | sdev_printk(KERN_WARNING, sdev, |
160 | "%s: sending start_stop_unit failed, " |
161 | "sense %x/%x/%x\n" , HP_SW_NAME, |
162 | sshdr.sense_key, sshdr.asc, sshdr.ascq); |
163 | rc = SCSI_DH_IO; |
164 | } |
165 | |
166 | return rc; |
167 | } |
168 | |
169 | static blk_status_t hp_sw_prep_fn(struct scsi_device *sdev, struct request *req) |
170 | { |
171 | struct hp_sw_dh_data *h = sdev->handler_data; |
172 | |
173 | if (h->path_state != HP_SW_PATH_ACTIVE) { |
174 | req->rq_flags |= RQF_QUIET; |
175 | return BLK_STS_IOERR; |
176 | } |
177 | |
178 | return BLK_STS_OK; |
179 | } |
180 | |
181 | /* |
182 | * hp_sw_activate - Activate a path |
183 | * @sdev: sdev on the path to be activated |
184 | * |
185 | * The HP Active/Passive firmware is pretty simple; |
186 | * the passive path reports NOT READY with sense codes |
187 | * 0x04/0x02; a START STOP UNIT command will then |
188 | * activate the passive path (and deactivate the |
189 | * previously active one). |
190 | */ |
191 | static int hp_sw_activate(struct scsi_device *sdev, |
192 | activate_complete fn, void *data) |
193 | { |
194 | int ret = SCSI_DH_OK; |
195 | struct hp_sw_dh_data *h = sdev->handler_data; |
196 | |
197 | ret = hp_sw_tur(sdev, h); |
198 | |
199 | if (ret == SCSI_DH_OK && h->path_state == HP_SW_PATH_PASSIVE) |
200 | ret = hp_sw_start_stop(h); |
201 | |
202 | if (fn) |
203 | fn(data, ret); |
204 | return 0; |
205 | } |
206 | |
207 | static int hp_sw_bus_attach(struct scsi_device *sdev) |
208 | { |
209 | struct hp_sw_dh_data *h; |
210 | int ret; |
211 | |
212 | h = kzalloc(size: sizeof(*h), GFP_KERNEL); |
213 | if (!h) |
214 | return SCSI_DH_NOMEM; |
215 | h->path_state = HP_SW_PATH_UNINITIALIZED; |
216 | h->retries = HP_SW_RETRIES; |
217 | h->sdev = sdev; |
218 | |
219 | ret = hp_sw_tur(sdev, h); |
220 | if (ret != SCSI_DH_OK) |
221 | goto failed; |
222 | if (h->path_state == HP_SW_PATH_UNINITIALIZED) { |
223 | ret = SCSI_DH_NOSYS; |
224 | goto failed; |
225 | } |
226 | |
227 | sdev_printk(KERN_INFO, sdev, "%s: attached to %s path\n" , |
228 | HP_SW_NAME, h->path_state == HP_SW_PATH_ACTIVE? |
229 | "active" :"passive" ); |
230 | |
231 | sdev->handler_data = h; |
232 | return SCSI_DH_OK; |
233 | failed: |
234 | kfree(objp: h); |
235 | return ret; |
236 | } |
237 | |
238 | static void hp_sw_bus_detach( struct scsi_device *sdev ) |
239 | { |
240 | kfree(objp: sdev->handler_data); |
241 | sdev->handler_data = NULL; |
242 | } |
243 | |
244 | static struct scsi_device_handler hp_sw_dh = { |
245 | .name = HP_SW_NAME, |
246 | .module = THIS_MODULE, |
247 | .attach = hp_sw_bus_attach, |
248 | .detach = hp_sw_bus_detach, |
249 | .activate = hp_sw_activate, |
250 | .prep_fn = hp_sw_prep_fn, |
251 | }; |
252 | |
253 | static int __init hp_sw_init(void) |
254 | { |
255 | return scsi_register_device_handler(scsi_dh: &hp_sw_dh); |
256 | } |
257 | |
258 | static void __exit hp_sw_exit(void) |
259 | { |
260 | scsi_unregister_device_handler(scsi_dh: &hp_sw_dh); |
261 | } |
262 | |
263 | module_init(hp_sw_init); |
264 | module_exit(hp_sw_exit); |
265 | |
266 | MODULE_DESCRIPTION("HP Active/Passive driver" ); |
267 | MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu" ); |
268 | MODULE_LICENSE("GPL" ); |
269 | |