1 | /* |
2 | * linux/drivers/scsi/esas2r/esas2r_vda.c |
3 | * esas2r driver VDA firmware interface functions |
4 | * |
5 | * Copyright (c) 2001-2013 ATTO Technology, Inc. |
6 | * (mailto:linuxdrivers@attotech.com) |
7 | */ |
8 | /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ |
9 | /* |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License as published by |
12 | * the Free Software Foundation; version 2 of the License. |
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 | * NO WARRANTY |
20 | * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR |
21 | * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT |
22 | * LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, |
23 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is |
24 | * solely responsible for determining the appropriateness of using and |
25 | * distributing the Program and assumes all risks associated with its |
26 | * exercise of rights under this Agreement, including but not limited to |
27 | * the risks and costs of program errors, damage to or loss of data, |
28 | * programs or equipment, and unavailability or interruption of operations. |
29 | * |
30 | * DISCLAIMER OF LIABILITY |
31 | * NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY |
32 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
33 | * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND |
34 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
35 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
36 | * USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED |
37 | * HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES |
38 | * |
39 | * You should have received a copy of the GNU General Public License |
40 | * along with this program; if not, write to the Free Software |
41 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
42 | */ |
43 | /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ |
44 | |
45 | #include "esas2r.h" |
46 | |
47 | static u8 esas2r_vdaioctl_versions[] = { |
48 | ATTO_VDA_VER_UNSUPPORTED, |
49 | ATTO_VDA_FLASH_VER, |
50 | ATTO_VDA_VER_UNSUPPORTED, |
51 | ATTO_VDA_VER_UNSUPPORTED, |
52 | ATTO_VDA_CLI_VER, |
53 | ATTO_VDA_VER_UNSUPPORTED, |
54 | ATTO_VDA_CFG_VER, |
55 | ATTO_VDA_MGT_VER, |
56 | ATTO_VDA_GSV_VER |
57 | }; |
58 | |
59 | static void clear_vda_request(struct esas2r_request *rq); |
60 | |
61 | static void esas2r_complete_vda_ioctl(struct esas2r_adapter *a, |
62 | struct esas2r_request *rq); |
63 | |
64 | /* Prepare a VDA IOCTL request to be sent to the firmware. */ |
65 | bool esas2r_process_vda_ioctl(struct esas2r_adapter *a, |
66 | struct atto_ioctl_vda *vi, |
67 | struct esas2r_request *rq, |
68 | struct esas2r_sg_context *sgc) |
69 | { |
70 | u32 datalen = 0; |
71 | struct atto_vda_sge *firstsg = NULL; |
72 | u8 vercnt = (u8)ARRAY_SIZE(esas2r_vdaioctl_versions); |
73 | |
74 | vi->status = ATTO_STS_SUCCESS; |
75 | vi->vda_status = RS_PENDING; |
76 | |
77 | if (vi->function >= vercnt) { |
78 | vi->status = ATTO_STS_INV_FUNC; |
79 | return false; |
80 | } |
81 | |
82 | if (vi->version > esas2r_vdaioctl_versions[vi->function]) { |
83 | vi->status = ATTO_STS_INV_VERSION; |
84 | return false; |
85 | } |
86 | |
87 | if (test_bit(AF_DEGRADED_MODE, &a->flags)) { |
88 | vi->status = ATTO_STS_DEGRADED; |
89 | return false; |
90 | } |
91 | |
92 | if (vi->function != VDA_FUNC_SCSI) |
93 | clear_vda_request(rq); |
94 | |
95 | rq->vrq->scsi.function = vi->function; |
96 | rq->interrupt_cb = esas2r_complete_vda_ioctl; |
97 | rq->interrupt_cx = vi; |
98 | |
99 | switch (vi->function) { |
100 | case VDA_FUNC_FLASH: |
101 | |
102 | if (vi->cmd.flash.sub_func != VDA_FLASH_FREAD |
103 | && vi->cmd.flash.sub_func != VDA_FLASH_FWRITE |
104 | && vi->cmd.flash.sub_func != VDA_FLASH_FINFO) { |
105 | vi->status = ATTO_STS_INV_FUNC; |
106 | return false; |
107 | } |
108 | |
109 | if (vi->cmd.flash.sub_func != VDA_FLASH_FINFO) |
110 | datalen = vi->data_length; |
111 | |
112 | rq->vrq->flash.length = cpu_to_le32(datalen); |
113 | rq->vrq->flash.sub_func = vi->cmd.flash.sub_func; |
114 | |
115 | memcpy(rq->vrq->flash.data.file.file_name, |
116 | vi->cmd.flash.data.file.file_name, |
117 | sizeof(vi->cmd.flash.data.file.file_name)); |
118 | |
119 | firstsg = rq->vrq->flash.data.file.sge; |
120 | break; |
121 | |
122 | case VDA_FUNC_CLI: |
123 | |
124 | datalen = vi->data_length; |
125 | |
126 | rq->vrq->cli.cmd_rsp_len = |
127 | cpu_to_le32(vi->cmd.cli.cmd_rsp_len); |
128 | rq->vrq->cli.length = cpu_to_le32(datalen); |
129 | |
130 | firstsg = rq->vrq->cli.sge; |
131 | break; |
132 | |
133 | case VDA_FUNC_MGT: |
134 | { |
135 | u8 *cmdcurr_offset = sgc->cur_offset |
136 | - offsetof(struct atto_ioctl_vda, data) |
137 | + offsetof(struct atto_ioctl_vda, cmd) |
138 | + offsetof(struct atto_ioctl_vda_mgt_cmd, |
139 | data); |
140 | /* |
141 | * build the data payload SGL here first since |
142 | * esas2r_sgc_init() will modify the S/G list offset for the |
143 | * management SGL (which is built below where the data SGL is |
144 | * usually built). |
145 | */ |
146 | |
147 | if (vi->data_length) { |
148 | u32 payldlen = 0; |
149 | |
150 | if (vi->cmd.mgt.mgt_func == VDAMGT_DEV_HEALTH_REQ |
151 | || vi->cmd.mgt.mgt_func == VDAMGT_DEV_METRICS) { |
152 | rq->vrq->mgt.payld_sglst_offset = |
153 | (u8)offsetof(struct atto_vda_mgmt_req, |
154 | payld_sge); |
155 | |
156 | payldlen = vi->data_length; |
157 | datalen = vi->cmd.mgt.data_length; |
158 | } else if (vi->cmd.mgt.mgt_func == VDAMGT_DEV_INFO2 |
159 | || vi->cmd.mgt.mgt_func == |
160 | VDAMGT_DEV_INFO2_BYADDR) { |
161 | datalen = vi->data_length; |
162 | cmdcurr_offset = sgc->cur_offset; |
163 | } else { |
164 | vi->status = ATTO_STS_INV_PARAM; |
165 | return false; |
166 | } |
167 | |
168 | /* Setup the length so building the payload SGL works */ |
169 | rq->vrq->mgt.length = cpu_to_le32(datalen); |
170 | |
171 | if (payldlen) { |
172 | rq->vrq->mgt.payld_length = |
173 | cpu_to_le32(payldlen); |
174 | |
175 | esas2r_sgc_init(sgc, a, rq, |
176 | first: rq->vrq->mgt.payld_sge); |
177 | sgc->length = payldlen; |
178 | |
179 | if (!esas2r_build_sg_list(a, rq, sgc)) { |
180 | vi->status = ATTO_STS_OUT_OF_RSRC; |
181 | return false; |
182 | } |
183 | } |
184 | } else { |
185 | datalen = vi->cmd.mgt.data_length; |
186 | |
187 | rq->vrq->mgt.length = cpu_to_le32(datalen); |
188 | } |
189 | |
190 | /* |
191 | * Now that the payload SGL is built, if any, setup to build |
192 | * the management SGL. |
193 | */ |
194 | firstsg = rq->vrq->mgt.sge; |
195 | sgc->cur_offset = cmdcurr_offset; |
196 | |
197 | /* Finish initializing the management request. */ |
198 | rq->vrq->mgt.mgt_func = vi->cmd.mgt.mgt_func; |
199 | rq->vrq->mgt.scan_generation = vi->cmd.mgt.scan_generation; |
200 | rq->vrq->mgt.dev_index = |
201 | cpu_to_le32(vi->cmd.mgt.dev_index); |
202 | |
203 | esas2r_nuxi_mgt_data(function: rq->vrq->mgt.mgt_func, data: &vi->cmd.mgt.data); |
204 | break; |
205 | } |
206 | |
207 | case VDA_FUNC_CFG: |
208 | |
209 | if (vi->data_length |
210 | || vi->cmd.cfg.data_length == 0) { |
211 | vi->status = ATTO_STS_INV_PARAM; |
212 | return false; |
213 | } |
214 | |
215 | if (vi->cmd.cfg.cfg_func == VDA_CFG_INIT) { |
216 | vi->status = ATTO_STS_INV_FUNC; |
217 | return false; |
218 | } |
219 | |
220 | rq->vrq->cfg.sub_func = vi->cmd.cfg.cfg_func; |
221 | rq->vrq->cfg.length = cpu_to_le32(vi->cmd.cfg.data_length); |
222 | |
223 | if (vi->cmd.cfg.cfg_func == VDA_CFG_GET_INIT) { |
224 | memcpy(&rq->vrq->cfg.data, |
225 | &vi->cmd.cfg.data, |
226 | vi->cmd.cfg.data_length); |
227 | |
228 | esas2r_nuxi_cfg_data(function: rq->vrq->cfg.sub_func, |
229 | data: &rq->vrq->cfg.data); |
230 | } else { |
231 | vi->status = ATTO_STS_INV_FUNC; |
232 | |
233 | return false; |
234 | } |
235 | |
236 | break; |
237 | |
238 | case VDA_FUNC_GSV: |
239 | |
240 | vi->cmd.gsv.rsp_len = vercnt; |
241 | |
242 | memcpy(vi->cmd.gsv.version_info, esas2r_vdaioctl_versions, |
243 | vercnt); |
244 | |
245 | vi->vda_status = RS_SUCCESS; |
246 | break; |
247 | |
248 | default: |
249 | |
250 | vi->status = ATTO_STS_INV_FUNC; |
251 | return false; |
252 | } |
253 | |
254 | if (datalen) { |
255 | esas2r_sgc_init(sgc, a, rq, first: firstsg); |
256 | sgc->length = datalen; |
257 | |
258 | if (!esas2r_build_sg_list(a, rq, sgc)) { |
259 | vi->status = ATTO_STS_OUT_OF_RSRC; |
260 | return false; |
261 | } |
262 | } |
263 | |
264 | esas2r_start_request(a, rq); |
265 | |
266 | return true; |
267 | } |
268 | |
269 | static void esas2r_complete_vda_ioctl(struct esas2r_adapter *a, |
270 | struct esas2r_request *rq) |
271 | { |
272 | struct atto_ioctl_vda *vi = (struct atto_ioctl_vda *)rq->interrupt_cx; |
273 | |
274 | vi->vda_status = rq->req_stat; |
275 | |
276 | switch (vi->function) { |
277 | case VDA_FUNC_FLASH: |
278 | |
279 | if (vi->cmd.flash.sub_func == VDA_FLASH_FINFO |
280 | || vi->cmd.flash.sub_func == VDA_FLASH_FREAD) |
281 | vi->cmd.flash.data.file.file_size = |
282 | le32_to_cpu(rq->func_rsp.flash_rsp.file_size); |
283 | |
284 | break; |
285 | |
286 | case VDA_FUNC_MGT: |
287 | |
288 | vi->cmd.mgt.scan_generation = |
289 | rq->func_rsp.mgt_rsp.scan_generation; |
290 | vi->cmd.mgt.dev_index = le16_to_cpu( |
291 | rq->func_rsp.mgt_rsp.dev_index); |
292 | |
293 | if (vi->data_length == 0) |
294 | vi->cmd.mgt.data_length = |
295 | le32_to_cpu(rq->func_rsp.mgt_rsp.length); |
296 | |
297 | esas2r_nuxi_mgt_data(function: rq->vrq->mgt.mgt_func, data: &vi->cmd.mgt.data); |
298 | break; |
299 | |
300 | case VDA_FUNC_CFG: |
301 | |
302 | if (vi->cmd.cfg.cfg_func == VDA_CFG_GET_INIT) { |
303 | struct atto_ioctl_vda_cfg_cmd *cfg = &vi->cmd.cfg; |
304 | struct atto_vda_cfg_rsp *rsp = &rq->func_rsp.cfg_rsp; |
305 | char buf[sizeof(cfg->data.init.fw_release) + 1]; |
306 | |
307 | cfg->data_length = |
308 | cpu_to_le32(sizeof(struct atto_vda_cfg_init)); |
309 | cfg->data.init.vda_version = |
310 | le32_to_cpu(rsp->vda_version); |
311 | cfg->data.init.fw_build = rsp->fw_build; |
312 | |
313 | snprintf(buf, size: sizeof(buf), fmt: "%1.1u.%2.2u" , |
314 | (int)LOBYTE(le16_to_cpu(rsp->fw_release)), |
315 | (int)HIBYTE(le16_to_cpu(rsp->fw_release))); |
316 | |
317 | memcpy(&cfg->data.init.fw_release, buf, |
318 | sizeof(cfg->data.init.fw_release)); |
319 | |
320 | if (LOWORD(LOBYTE(cfg->data.init.fw_build)) == 'A') |
321 | cfg->data.init.fw_version = |
322 | cfg->data.init.fw_build; |
323 | else |
324 | cfg->data.init.fw_version = |
325 | cfg->data.init.fw_release; |
326 | } else { |
327 | esas2r_nuxi_cfg_data(function: rq->vrq->cfg.sub_func, |
328 | data: &vi->cmd.cfg.data); |
329 | } |
330 | |
331 | break; |
332 | |
333 | case VDA_FUNC_CLI: |
334 | |
335 | vi->cmd.cli.cmd_rsp_len = |
336 | le32_to_cpu(rq->func_rsp.cli_rsp.cmd_rsp_len); |
337 | break; |
338 | |
339 | default: |
340 | |
341 | break; |
342 | } |
343 | } |
344 | |
345 | /* Build a flash VDA request. */ |
346 | void esas2r_build_flash_req(struct esas2r_adapter *a, |
347 | struct esas2r_request *rq, |
348 | u8 sub_func, |
349 | u8 cksum, |
350 | u32 addr, |
351 | u32 length) |
352 | { |
353 | struct atto_vda_flash_req *vrq = &rq->vrq->flash; |
354 | |
355 | clear_vda_request(rq); |
356 | |
357 | rq->vrq->scsi.function = VDA_FUNC_FLASH; |
358 | |
359 | if (sub_func == VDA_FLASH_BEGINW |
360 | || sub_func == VDA_FLASH_WRITE |
361 | || sub_func == VDA_FLASH_READ) |
362 | vrq->sg_list_offset = (u8)offsetof(struct atto_vda_flash_req, |
363 | data.sge); |
364 | |
365 | vrq->length = cpu_to_le32(length); |
366 | vrq->flash_addr = cpu_to_le32(addr); |
367 | vrq->checksum = cksum; |
368 | vrq->sub_func = sub_func; |
369 | } |
370 | |
371 | /* Build a VDA management request. */ |
372 | void esas2r_build_mgt_req(struct esas2r_adapter *a, |
373 | struct esas2r_request *rq, |
374 | u8 sub_func, |
375 | u8 scan_gen, |
376 | u16 dev_index, |
377 | u32 length, |
378 | void *data) |
379 | { |
380 | struct atto_vda_mgmt_req *vrq = &rq->vrq->mgt; |
381 | |
382 | clear_vda_request(rq); |
383 | |
384 | rq->vrq->scsi.function = VDA_FUNC_MGT; |
385 | |
386 | vrq->mgt_func = sub_func; |
387 | vrq->scan_generation = scan_gen; |
388 | vrq->dev_index = cpu_to_le16(dev_index); |
389 | vrq->length = cpu_to_le32(length); |
390 | |
391 | if (vrq->length) { |
392 | if (test_bit(AF_LEGACY_SGE_MODE, &a->flags)) { |
393 | vrq->sg_list_offset = (u8)offsetof( |
394 | struct atto_vda_mgmt_req, sge); |
395 | |
396 | vrq->sge[0].length = cpu_to_le32(SGE_LAST | length); |
397 | vrq->sge[0].address = cpu_to_le64( |
398 | rq->vrq_md->phys_addr + |
399 | sizeof(union atto_vda_req)); |
400 | } else { |
401 | vrq->sg_list_offset = (u8)offsetof( |
402 | struct atto_vda_mgmt_req, prde); |
403 | |
404 | vrq->prde[0].ctl_len = cpu_to_le32(length); |
405 | vrq->prde[0].address = cpu_to_le64( |
406 | rq->vrq_md->phys_addr + |
407 | sizeof(union atto_vda_req)); |
408 | } |
409 | } |
410 | |
411 | if (data) { |
412 | esas2r_nuxi_mgt_data(function: sub_func, data); |
413 | |
414 | memcpy(&rq->vda_rsp_data->mgt_data.data.bytes[0], data, |
415 | length); |
416 | } |
417 | } |
418 | |
419 | /* Build a VDA asyncronous event (AE) request. */ |
420 | void esas2r_build_ae_req(struct esas2r_adapter *a, struct esas2r_request *rq) |
421 | { |
422 | struct atto_vda_ae_req *vrq = &rq->vrq->ae; |
423 | |
424 | clear_vda_request(rq); |
425 | |
426 | rq->vrq->scsi.function = VDA_FUNC_AE; |
427 | |
428 | vrq->length = cpu_to_le32(sizeof(struct atto_vda_ae_data)); |
429 | |
430 | if (test_bit(AF_LEGACY_SGE_MODE, &a->flags)) { |
431 | vrq->sg_list_offset = |
432 | (u8)offsetof(struct atto_vda_ae_req, sge); |
433 | vrq->sge[0].length = cpu_to_le32(SGE_LAST | vrq->length); |
434 | vrq->sge[0].address = cpu_to_le64( |
435 | rq->vrq_md->phys_addr + |
436 | sizeof(union atto_vda_req)); |
437 | } else { |
438 | vrq->sg_list_offset = (u8)offsetof(struct atto_vda_ae_req, |
439 | prde); |
440 | vrq->prde[0].ctl_len = cpu_to_le32(vrq->length); |
441 | vrq->prde[0].address = cpu_to_le64( |
442 | rq->vrq_md->phys_addr + |
443 | sizeof(union atto_vda_req)); |
444 | } |
445 | } |
446 | |
447 | /* Build a VDA CLI request. */ |
448 | void esas2r_build_cli_req(struct esas2r_adapter *a, |
449 | struct esas2r_request *rq, |
450 | u32 length, |
451 | u32 cmd_rsp_len) |
452 | { |
453 | struct atto_vda_cli_req *vrq = &rq->vrq->cli; |
454 | |
455 | clear_vda_request(rq); |
456 | |
457 | rq->vrq->scsi.function = VDA_FUNC_CLI; |
458 | |
459 | vrq->length = cpu_to_le32(length); |
460 | vrq->cmd_rsp_len = cpu_to_le32(cmd_rsp_len); |
461 | vrq->sg_list_offset = (u8)offsetof(struct atto_vda_cli_req, sge); |
462 | } |
463 | |
464 | /* Build a VDA IOCTL request. */ |
465 | void esas2r_build_ioctl_req(struct esas2r_adapter *a, |
466 | struct esas2r_request *rq, |
467 | u32 length, |
468 | u8 sub_func) |
469 | { |
470 | struct atto_vda_ioctl_req *vrq = &rq->vrq->ioctl; |
471 | |
472 | clear_vda_request(rq); |
473 | |
474 | rq->vrq->scsi.function = VDA_FUNC_IOCTL; |
475 | |
476 | vrq->length = cpu_to_le32(length); |
477 | vrq->sub_func = sub_func; |
478 | vrq->sg_list_offset = (u8)offsetof(struct atto_vda_ioctl_req, sge); |
479 | } |
480 | |
481 | /* Build a VDA configuration request. */ |
482 | void esas2r_build_cfg_req(struct esas2r_adapter *a, |
483 | struct esas2r_request *rq, |
484 | u8 sub_func, |
485 | u32 length, |
486 | void *data) |
487 | { |
488 | struct atto_vda_cfg_req *vrq = &rq->vrq->cfg; |
489 | |
490 | clear_vda_request(rq); |
491 | |
492 | rq->vrq->scsi.function = VDA_FUNC_CFG; |
493 | |
494 | vrq->sub_func = sub_func; |
495 | vrq->length = cpu_to_le32(length); |
496 | |
497 | if (data) { |
498 | esas2r_nuxi_cfg_data(function: sub_func, data); |
499 | |
500 | memcpy(&vrq->data, data, length); |
501 | } |
502 | } |
503 | |
504 | static void clear_vda_request(struct esas2r_request *rq) |
505 | { |
506 | u32 handle = rq->vrq->scsi.handle; |
507 | |
508 | memset(rq->vrq, 0, sizeof(*rq->vrq)); |
509 | |
510 | rq->vrq->scsi.handle = handle; |
511 | |
512 | rq->req_stat = RS_PENDING; |
513 | |
514 | /* since the data buffer is separate clear that too */ |
515 | |
516 | memset(rq->data_buf, 0, ESAS2R_DATA_BUF_LEN); |
517 | |
518 | /* |
519 | * Setup next and prev pointer in case the request is not going through |
520 | * esas2r_start_request(). |
521 | */ |
522 | |
523 | INIT_LIST_HEAD(list: &rq->req_list); |
524 | } |
525 | |