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 NOT_READY: |
50 | if (sshdr->asc == 0x04 && sshdr->ascq == 2) { |
51 | /* |
52 | * LUN not ready - Initialization command required |
53 | * |
54 | * This is the passive path |
55 | */ |
56 | h->path_state = HP_SW_PATH_PASSIVE; |
57 | ret = SCSI_DH_OK; |
58 | break; |
59 | } |
60 | fallthrough; |
61 | default: |
62 | sdev_printk(KERN_WARNING, sdev, |
63 | "%s: sending tur failed, sense %x/%x/%x\n" , |
64 | HP_SW_NAME, sshdr->sense_key, sshdr->asc, |
65 | sshdr->ascq); |
66 | break; |
67 | } |
68 | return ret; |
69 | } |
70 | |
71 | /* |
72 | * hp_sw_tur - Send TEST UNIT READY |
73 | * @sdev: sdev command should be sent to |
74 | * |
75 | * Use the TEST UNIT READY command to determine |
76 | * the path state. |
77 | */ |
78 | static int hp_sw_tur(struct scsi_device *sdev, struct hp_sw_dh_data *h) |
79 | { |
80 | unsigned char cmd[6] = { TEST_UNIT_READY }; |
81 | struct scsi_sense_hdr sshdr; |
82 | int ret, res; |
83 | blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV | |
84 | REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; |
85 | struct scsi_failure failure_defs[] = { |
86 | { |
87 | .sense = UNIT_ATTENTION, |
88 | .asc = SCMD_FAILURE_ASC_ANY, |
89 | .ascq = SCMD_FAILURE_ASCQ_ANY, |
90 | .allowed = SCMD_FAILURE_NO_LIMIT, |
91 | .result = SAM_STAT_CHECK_CONDITION, |
92 | }, |
93 | {} |
94 | }; |
95 | struct scsi_failures failures = { |
96 | .failure_definitions = failure_defs, |
97 | }; |
98 | const struct scsi_exec_args exec_args = { |
99 | .sshdr = &sshdr, |
100 | .failures = &failures, |
101 | }; |
102 | |
103 | res = scsi_execute_cmd(sdev, cmd, opf, NULL, bufflen: 0, HP_SW_TIMEOUT, |
104 | HP_SW_RETRIES, args: &exec_args); |
105 | if (res > 0 && scsi_sense_valid(sshdr: &sshdr)) { |
106 | ret = tur_done(sdev, h, sshdr: &sshdr); |
107 | } else if (res == 0) { |
108 | h->path_state = HP_SW_PATH_ACTIVE; |
109 | ret = SCSI_DH_OK; |
110 | } else { |
111 | sdev_printk(KERN_WARNING, sdev, |
112 | "%s: sending tur failed with %x\n" , |
113 | HP_SW_NAME, res); |
114 | ret = SCSI_DH_IO; |
115 | } |
116 | |
117 | return ret; |
118 | } |
119 | |
120 | /* |
121 | * hp_sw_start_stop - Send START STOP UNIT command |
122 | * @sdev: sdev command should be sent to |
123 | * |
124 | * Sending START STOP UNIT activates the SP. |
125 | */ |
126 | static int hp_sw_start_stop(struct hp_sw_dh_data *h) |
127 | { |
128 | unsigned char cmd[6] = { START_STOP, 0, 0, 0, 1, 0 }; |
129 | struct scsi_sense_hdr sshdr; |
130 | struct scsi_device *sdev = h->sdev; |
131 | int res, rc; |
132 | blk_opf_t opf = REQ_OP_DRV_IN | REQ_FAILFAST_DEV | |
133 | REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; |
134 | struct scsi_failure failure_defs[] = { |
135 | { |
136 | /* |
137 | * LUN not ready - manual intervention required |
138 | * |
139 | * Switch-over in progress, retry. |
140 | */ |
141 | .sense = NOT_READY, |
142 | .asc = 0x04, |
143 | .ascq = 0x03, |
144 | .allowed = HP_SW_RETRIES, |
145 | .result = SAM_STAT_CHECK_CONDITION, |
146 | }, |
147 | {} |
148 | }; |
149 | struct scsi_failures failures = { |
150 | .failure_definitions = failure_defs, |
151 | }; |
152 | const struct scsi_exec_args exec_args = { |
153 | .sshdr = &sshdr, |
154 | .failures = &failures, |
155 | }; |
156 | |
157 | res = scsi_execute_cmd(sdev, cmd, opf, NULL, bufflen: 0, HP_SW_TIMEOUT, |
158 | HP_SW_RETRIES, args: &exec_args); |
159 | if (!res) { |
160 | return SCSI_DH_OK; |
161 | } else if (res < 0 || !scsi_sense_valid(sshdr: &sshdr)) { |
162 | sdev_printk(KERN_WARNING, sdev, |
163 | "%s: sending start_stop_unit failed, " |
164 | "no sense available\n" , HP_SW_NAME); |
165 | return SCSI_DH_IO; |
166 | } |
167 | |
168 | switch (sshdr.sense_key) { |
169 | case NOT_READY: |
170 | if (sshdr.asc == 0x04 && sshdr.ascq == 3) { |
171 | rc = SCSI_DH_RETRY; |
172 | break; |
173 | } |
174 | fallthrough; |
175 | default: |
176 | sdev_printk(KERN_WARNING, sdev, |
177 | "%s: sending start_stop_unit failed, " |
178 | "sense %x/%x/%x\n" , HP_SW_NAME, |
179 | sshdr.sense_key, sshdr.asc, sshdr.ascq); |
180 | rc = SCSI_DH_IO; |
181 | } |
182 | |
183 | return rc; |
184 | } |
185 | |
186 | static blk_status_t hp_sw_prep_fn(struct scsi_device *sdev, struct request *req) |
187 | { |
188 | struct hp_sw_dh_data *h = sdev->handler_data; |
189 | |
190 | if (h->path_state != HP_SW_PATH_ACTIVE) { |
191 | req->rq_flags |= RQF_QUIET; |
192 | return BLK_STS_IOERR; |
193 | } |
194 | |
195 | return BLK_STS_OK; |
196 | } |
197 | |
198 | /* |
199 | * hp_sw_activate - Activate a path |
200 | * @sdev: sdev on the path to be activated |
201 | * |
202 | * The HP Active/Passive firmware is pretty simple; |
203 | * the passive path reports NOT READY with sense codes |
204 | * 0x04/0x02; a START STOP UNIT command will then |
205 | * activate the passive path (and deactivate the |
206 | * previously active one). |
207 | */ |
208 | static int hp_sw_activate(struct scsi_device *sdev, |
209 | activate_complete fn, void *data) |
210 | { |
211 | int ret = SCSI_DH_OK; |
212 | struct hp_sw_dh_data *h = sdev->handler_data; |
213 | |
214 | ret = hp_sw_tur(sdev, h); |
215 | |
216 | if (ret == SCSI_DH_OK && h->path_state == HP_SW_PATH_PASSIVE) |
217 | ret = hp_sw_start_stop(h); |
218 | |
219 | if (fn) |
220 | fn(data, ret); |
221 | return 0; |
222 | } |
223 | |
224 | static int hp_sw_bus_attach(struct scsi_device *sdev) |
225 | { |
226 | struct hp_sw_dh_data *h; |
227 | int ret; |
228 | |
229 | h = kzalloc(size: sizeof(*h), GFP_KERNEL); |
230 | if (!h) |
231 | return SCSI_DH_NOMEM; |
232 | h->path_state = HP_SW_PATH_UNINITIALIZED; |
233 | h->retries = HP_SW_RETRIES; |
234 | h->sdev = sdev; |
235 | |
236 | ret = hp_sw_tur(sdev, h); |
237 | if (ret != SCSI_DH_OK) |
238 | goto failed; |
239 | if (h->path_state == HP_SW_PATH_UNINITIALIZED) { |
240 | ret = SCSI_DH_NOSYS; |
241 | goto failed; |
242 | } |
243 | |
244 | sdev_printk(KERN_INFO, sdev, "%s: attached to %s path\n" , |
245 | HP_SW_NAME, h->path_state == HP_SW_PATH_ACTIVE? |
246 | "active" :"passive" ); |
247 | |
248 | sdev->handler_data = h; |
249 | return SCSI_DH_OK; |
250 | failed: |
251 | kfree(objp: h); |
252 | return ret; |
253 | } |
254 | |
255 | static void hp_sw_bus_detach( struct scsi_device *sdev ) |
256 | { |
257 | kfree(objp: sdev->handler_data); |
258 | sdev->handler_data = NULL; |
259 | } |
260 | |
261 | static struct scsi_device_handler hp_sw_dh = { |
262 | .name = HP_SW_NAME, |
263 | .module = THIS_MODULE, |
264 | .attach = hp_sw_bus_attach, |
265 | .detach = hp_sw_bus_detach, |
266 | .activate = hp_sw_activate, |
267 | .prep_fn = hp_sw_prep_fn, |
268 | }; |
269 | |
270 | static int __init hp_sw_init(void) |
271 | { |
272 | return scsi_register_device_handler(scsi_dh: &hp_sw_dh); |
273 | } |
274 | |
275 | static void __exit hp_sw_exit(void) |
276 | { |
277 | scsi_unregister_device_handler(scsi_dh: &hp_sw_dh); |
278 | } |
279 | |
280 | module_init(hp_sw_init); |
281 | module_exit(hp_sw_exit); |
282 | |
283 | MODULE_DESCRIPTION("HP Active/Passive driver" ); |
284 | MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu" ); |
285 | MODULE_LICENSE("GPL" ); |
286 | |