Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | // ========== Copyright Header Begin ========================================== |
2 | // | |
3 | // OpenSPARC T2 Processor File: sas_disk.cc | |
4 | // Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. | |
5 | // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES. | |
6 | // | |
7 | // The above named program is free software; you can redistribute it and/or | |
8 | // modify it under the terms of the GNU General Public | |
9 | // License version 2 as published by the Free Software Foundation. | |
10 | // | |
11 | // The above named program is distributed in the hope that it will be | |
12 | // useful, but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | // General Public License for more details. | |
15 | // | |
16 | // You should have received a copy of the GNU General Public | |
17 | // License along with this work; if not, write to the Free Software | |
18 | // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 | // | |
20 | // ========== Copyright Header End ============================================ | |
21 | /* | |
22 | * "sas_disk.cc" LSI SAS1064E PCIE to 4-Port Serial Attached | |
23 | * SCSI Controller Simulator | |
24 | * SCSI disk implementation | |
25 | * | |
26 | * Copyright (C) 2007 Sun Microsystems, Inc. | |
27 | * All rights reserved. | |
28 | * | |
29 | */ | |
30 | #include <sys/types.h> | |
31 | #include <stdio.h> | |
32 | #include <unistd.h> | |
33 | #include <errno.h> | |
34 | #include <sys/stat.h> | |
35 | #include <sys/dirent.h> | |
36 | #include <fcntl.h> | |
37 | ||
38 | #include "sas.h" | |
39 | #include "sas_disk.h" | |
40 | ||
41 | ||
42 | // scsi disk block size | |
43 | #define SCSI_DISK_SECTOR_SIZE 512 | |
44 | #define SCSI_DISK_LBLK_SIZE SCSI_DISK_SECTOR_SIZE | |
45 | ||
46 | ||
47 | // Compute the time when an io request will finish | |
48 | void SAS::compute_disk_delay(int entry, int target, bool wr) { | |
49 | int64_t current_time = mmi_get_time(); | |
50 | ||
51 | if (target >= 0 && target < IOC_NUM_PORTS && _disk[target]) { | |
52 | // target exists | |
53 | uint32_t disk_delay; | |
54 | if (wr) | |
55 | disk_delay = _disk[target]->get_write_delay(); | |
56 | else | |
57 | disk_delay = _disk[target]->get_read_delay(); | |
58 | ||
59 | if (_earliest_serving_time[target] > current_time) | |
60 | _earliest_serving_time[target] = _earliest_serving_time[target] + disk_delay; | |
61 | else | |
62 | _earliest_serving_time[target] = current_time + disk_delay; | |
63 | ||
64 | _etable.set_invoke_time(entry, _earliest_serving_time[target]); | |
65 | } | |
66 | else { | |
67 | // target doesn't exist at all | |
68 | if (wr) | |
69 | _etable.set_invoke_time(entry, current_time + WRITE_DELAY_DEFAULT); | |
70 | else | |
71 | _etable.set_invoke_time(entry, current_time + READ_DELAY_DEFAULT); | |
72 | } | |
73 | } | |
74 | ||
75 | ||
76 | // IO request is done, record the reply msg and time in the event table | |
77 | void SAS::scsi_io_done(int entry, uint32_t reply, uint8_t is_cntx, uint8_t cntx_type, int target, bool wr) { | |
78 | // update event table entry | |
79 | _etable.done(entry, reply, is_cntx, cntx_type); | |
80 | compute_disk_delay(entry, target, wr); | |
81 | } | |
82 | ||
83 | ||
84 | // Error msg is needed especially during scsi target probing, | |
85 | // where it is used to inform the host that a target disk doesn't exist. | |
86 | void SAS::scsi_io_error(int entry, msg_scsi_io_request_t *req, uint16_t ioc_status) { | |
87 | msg_scsi_io_reply_t *reply = (msg_scsi_io_reply_t *)calloc(1, sizeof(msg_scsi_io_reply_t)); | |
88 | ||
89 | reply->TargetID = req->TargetID; | |
90 | reply->Bus = req->Bus; | |
91 | reply->MsgLength = sizeof(msg_scsi_io_reply) >> 2; | |
92 | reply->Function = req->Function; | |
93 | reply->CDBLength = req->CDBLength; | |
94 | reply->SenseBufferLength = req->SenseBufferLength; | |
95 | reply->MsgFlags = req->MsgFlags; | |
96 | reply->MsgContext = SAM_LE_32(req->MsgContext); | |
97 | reply->SCSIStatus = MPI_SCSI_STATUS_SUCCESS; | |
98 | reply->SCSIState = MPI_SCSI_STATE_NO_SCSI_STATUS; | |
99 | reply->IOCStatus = SAM_LE_16(ioc_status & MPI_IOCSTATUS_MASK); | |
100 | reply->IOCLogInfo = 0; | |
101 | reply->TransferCount = 0; | |
102 | reply->SenseCount = 0; | |
103 | reply->ResponseInfo = 0; | |
104 | reply->TaskTag = 0; | |
105 | ||
106 | if (_reply_free_queue.empty()) { | |
107 | debug_err("%s: reply free queue is empty\n", getName()); | |
108 | exit(1); | |
109 | } | |
110 | ||
111 | uint32_t fma = _reply_free_queue.front(); | |
112 | uint32_t msg_addr = fma & FMA_ADDRESS_MASK; | |
113 | _reply_free_queue.pop(); | |
114 | ||
115 | memory_access(mfa_addr(msg_addr), mfa_addr_mode(), (void *)reply, sizeof(msg_scsi_io_reply_t), true); | |
116 | scsi_io_done(entry, msg_addr >> 1, 0, 0, req->TargetID, false); | |
117 | debug_info("%s: reply frame address= 0x%x, msgcontext=0x%x\n", getName(), msg_addr, reply->MsgContext); | |
118 | ||
119 | free(reply); | |
120 | } | |
121 | ||
122 | ||
123 | // Process a SCSI request | |
124 | // step 1: memory_transport(), move the required data from/to disk to/from memory | |
125 | // step 2: send_mpi_msg(), send the reply to the host | |
126 | // Some requests may not need the first step. | |
127 | void SAS::process_scsi_cmd(int entry, msg_scsi_io_request_t *req, uint64_t sge_addr) { | |
128 | if (!(req->Bus == 0)) { | |
129 | debug_info("%s: only bus 0 is supported\n", getName()); | |
130 | scsi_io_error(entry, req, MPI_IOCSTATUS_SCSI_INVALID_BUS); | |
131 | return; | |
132 | } | |
133 | ||
134 | if (!(req->TargetID >= 0 && req->TargetID < IOC_NUM_PORTS && _disk[req->TargetID])) { | |
135 | debug_info("%s: target %d doesn't exists, check %s\n", getName(), req->TargetID, fname); | |
136 | scsi_io_error(entry, req, MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE); | |
137 | return; | |
138 | } | |
139 | ||
140 | if (!(req->LUN[0] == 0 && req->LUN[1] == 0)) { | |
141 | debug_err("%s: only LUN 0 is supported\n", getName()); | |
142 | exit(1); | |
143 | } | |
144 | ||
145 | debug_info("%s: process scsi cmd for Bus %d, Target %d...\n", getName(), req->Bus, req->TargetID); | |
146 | ||
147 | switch (req->CDB[0]) { | |
148 | case SCSI_COMMAND_REPORT_LUNS: { | |
149 | scsi_command_report_luns_t *lunp; | |
150 | ||
151 | lunp = (scsi_command_report_luns_t *)req->CDB; | |
152 | ||
153 | int32_t alloc_length = (lunp->allocation_length[0] << 24) | | |
154 | (lunp->allocation_length[1] << 16) | | |
155 | (lunp->allocation_length[2] << 8) | | |
156 | (lunp->allocation_length[3] << 0); | |
157 | ||
158 | if (alloc_length < 16) { | |
159 | debug_err("%s: allocation length in report lun is less than 16 bytes\n", getName()); | |
160 | exit(1); | |
161 | } | |
162 | ||
163 | int datalen = sizeof(struct scsi_report_luns); | |
164 | uchar_t *datap = (uchar_t *)calloc(datalen, sizeof(uchar_t)); | |
165 | ||
166 | struct scsi_report_luns *lundp = (struct scsi_report_luns *)datap; | |
167 | lundp->lun_list_len = SAM_BE_32(8); | |
168 | ||
169 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true); | |
170 | ||
171 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
172 | ||
173 | free(datap); | |
174 | ||
175 | break; | |
176 | } | |
177 | ||
178 | case SCSI_COMMAND_INQUIRY: { | |
179 | scsi_command_inquiry_t *inquiryp; | |
180 | ||
181 | inquiryp = (scsi_command_inquiry_t *)req->CDB; | |
182 | ||
183 | int datalen = 0; | |
184 | uchar_t *datap; | |
185 | ||
186 | if ((req->CDB[1] & 1) == 0) { /* Non-EVPD Inquiry */ | |
187 | ||
188 | datalen = sizeof(SCSI_Inquiry_Data); | |
189 | datap = (uchar_t *)calloc (datalen, sizeof(uchar_t)); | |
190 | ||
191 | bcopy(_disk[req->TargetID]->get_SCSI_Inquiry_Data(), datap, datalen); | |
192 | } | |
193 | else { /* EVPD Inquiry */ | |
194 | switch (req->CDB[2]) { | |
195 | case 0x00: { | |
196 | datalen = sizeof(Supported_VPD); | |
197 | datap = (uchar_t*)calloc(datalen, sizeof(uchar_t)); | |
198 | ||
199 | bcopy(_disk[req->TargetID]->get_Supported_VPD(), datap, datalen); | |
200 | ||
201 | break; | |
202 | } | |
203 | ||
204 | case 0x80: { // Unit Serial Number | |
205 | datalen = sizeof(Unit_Serial_Number); | |
206 | datap = (uchar_t*)calloc (datalen, sizeof(uchar_t)); | |
207 | ||
208 | bcopy(_disk[req->TargetID]->get_Unit_Serial_Number(), datap, datalen); | |
209 | ||
210 | break; | |
211 | } | |
212 | ||
213 | case 0x81: { // Implemented Operating Definition | |
214 | datalen = sizeof(Implemented_Operating_Definition); | |
215 | datap = (uchar_t*)calloc(datalen, sizeof(uchar_t)); | |
216 | ||
217 | bcopy(_disk[req->TargetID]->get_Implemented_Operating_Definition(), datap, datalen); | |
218 | ||
219 | break; | |
220 | } | |
221 | ||
222 | ||
223 | case 0x83: { // Device Identification | |
224 | datalen = sizeof(Device_Identification); | |
225 | datap = (uchar_t*)calloc(datalen, sizeof(uchar_t)); | |
226 | ||
227 | bcopy(_disk[req->TargetID]->get_Device_Identification(), datap, datalen); | |
228 | ||
229 | break; | |
230 | } | |
231 | ||
232 | default: | |
233 | debug_err("%s: scsi_extended_inquiry page 0x%x NOT IMPL\n", getName(), req->CDB[2]); | |
234 | exit(1); | |
235 | } | |
236 | } | |
237 | ||
238 | assert(datalen > 0); | |
239 | ||
240 | if (datalen > inquiryp->allocation_length) { | |
241 | debug_info("%s: inquiry allocated data length is less than datalen\n", getName()); | |
242 | datalen = inquiryp->allocation_length; | |
243 | } | |
244 | ||
245 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true); | |
246 | ||
247 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
248 | ||
249 | free(datap); | |
250 | ||
251 | break; | |
252 | } | |
253 | ||
254 | case SCSI_COMMAND_TEST_UNIT_READY: { | |
255 | scsi_command_test_unit_ready_t *turp; | |
256 | ||
257 | turp = (scsi_command_test_unit_ready_t *)req->CDB; | |
258 | ||
259 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
260 | ||
261 | break; | |
262 | } | |
263 | ||
264 | case SCSI_COMMAND_START_STOP_UNIT: { | |
265 | scsi_command_start_stop_unit_t *ssup; | |
266 | ssup = (scsi_command_start_stop_unit_t *)req->CDB; | |
267 | ||
268 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
269 | ||
270 | break; | |
271 | } | |
272 | ||
273 | case SCSI_COMMAND_READ_CAPACITY: { | |
274 | scsi_command_read_capacity_t *rcp; | |
275 | rcp = (scsi_command_read_capacity_t *)req->CDB; | |
276 | ||
277 | int datalen = sizeof(scsi_command_read_capacity_data_t); | |
278 | uchar_t *datap = (uchar_t *)calloc(datalen, sizeof(uchar_t)); | |
279 | ||
280 | scsi_command_read_capacity_data_t *rcdp = (scsi_command_read_capacity_data_t *)datap; | |
281 | rcdp->lblk = SAM_BE_32(_disk[req->TargetID]->get_capacity()); | |
282 | rcdp->lblk_size = SAM_BE_32(SCSI_DISK_LBLK_SIZE); | |
283 | ||
284 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true); | |
285 | ||
286 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
287 | ||
288 | free(datap); | |
289 | ||
290 | break; | |
291 | } | |
292 | ||
293 | case SCSI_COMMAND_READ: { | |
294 | scsi_command_read_t *rdp; | |
295 | daddr_t lblkno; | |
296 | size_t blks, size; | |
297 | uchar_t *tmp_buf; | |
298 | ||
299 | rdp = (scsi_command_read_t *)req->CDB; | |
300 | lblkno = rdp->lblk_msb << 16; | |
301 | lblkno |= SAM_BE_16(rdp->lblk); | |
302 | blks = rdp->transfer_length; | |
303 | ||
304 | size = blks * SCSI_DISK_LBLK_SIZE; | |
305 | tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t)); | |
306 | ||
307 | debug_info("%s: read----> blkno: %d, blks: %d\n", getName(), lblkno, blks); | |
308 | _disk[req->TargetID]->disk_read_lblk(lblkno, tmp_buf, blks); | |
309 | ||
310 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, true); | |
311 | ||
312 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
313 | ||
314 | free(tmp_buf); | |
315 | ||
316 | break; | |
317 | } | |
318 | ||
319 | case SCSI_COMMAND_READ_LONG: { | |
320 | scsi_command_read_long_t *rdlp; | |
321 | daddr_t lblkno; | |
322 | size_t blks, size; | |
323 | uchar_t *tmp_buf; | |
324 | ||
325 | rdlp = (scsi_command_read_long_t *)req->CDB; | |
326 | lblkno = (rdlp->lblk[0] << 24) | | |
327 | (rdlp->lblk[1] << 16) | | |
328 | (rdlp->lblk[2] << 8) | | |
329 | (rdlp->lblk[3] << 0); | |
330 | blks = (rdlp->transfer_length[0] << 8) | | |
331 | (rdlp->transfer_length[1] << 0); | |
332 | ||
333 | size = blks * SCSI_DISK_LBLK_SIZE; | |
334 | tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t)); | |
335 | ||
336 | debug_info("%s: read long----> blkno: %d, blks: %d\n", getName(), lblkno, blks); | |
337 | _disk[req->TargetID]->disk_read_lblk(lblkno, tmp_buf, blks); | |
338 | ||
339 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, true); | |
340 | ||
341 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
342 | ||
343 | free(tmp_buf); | |
344 | ||
345 | break; | |
346 | } | |
347 | ||
348 | case SCSI_COMMAND_WRITE: { | |
349 | scsi_command_write_t *wrp; | |
350 | daddr_t lblkno; | |
351 | size_t blks, size; | |
352 | uchar_t *tmp_buf; | |
353 | ||
354 | wrp = (scsi_command_write_t *)req->CDB; | |
355 | lblkno = (wrp->lblk_msb << 16) | SAM_BE_16(wrp->lblk); | |
356 | blks = wrp->transfer_length; | |
357 | ||
358 | size = blks * SCSI_DISK_LBLK_SIZE; | |
359 | tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t)); | |
360 | ||
361 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, false); | |
362 | ||
363 | debug_info("%s: write---> blkno: %d, blks: %d\n", getName(), lblkno, blks); | |
364 | _disk[req->TargetID]->disk_write_lblk(lblkno, tmp_buf, blks); | |
365 | ||
366 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, true); | |
367 | ||
368 | free(tmp_buf); | |
369 | ||
370 | break; | |
371 | } | |
372 | ||
373 | case SCSI_COMMAND_WRITE_LONG: { | |
374 | scsi_command_write_long_t *wrlp; | |
375 | daddr_t lblkno; | |
376 | size_t blks, size; | |
377 | uchar_t *tmp_buf; | |
378 | ||
379 | wrlp = (scsi_command_write_long_t *)req->CDB; | |
380 | lblkno = (wrlp->lblk[0] << 24) | | |
381 | (wrlp->lblk[1] << 16) | | |
382 | (wrlp->lblk[2] << 8) | | |
383 | (wrlp->lblk[3] << 0); | |
384 | blks = (wrlp->transfer_length[0] << 8) | | |
385 | (wrlp->transfer_length[1] << 0); | |
386 | ||
387 | size = blks * SCSI_DISK_LBLK_SIZE; | |
388 | tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t)); | |
389 | ||
390 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, false); | |
391 | ||
392 | debug_info("%s: write long----> blkno: %d, blks: %d\n", getName(), lblkno, blks); | |
393 | _disk[req->TargetID]->disk_write_lblk(lblkno, tmp_buf, blks); | |
394 | ||
395 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, true); | |
396 | ||
397 | free(tmp_buf); | |
398 | ||
399 | break; | |
400 | } | |
401 | ||
402 | case SCSI_COMMAND_REQUEST_SENSE: { | |
403 | scsi_command_request_sense_t *rqsp; | |
404 | ||
405 | rqsp = (scsi_command_request_sense_t *)req->CDB; | |
406 | ||
407 | if (rqsp->allocation_length < sizeof(scsi_disk_request_sense_data_t)) { | |
408 | debug_err("%s: SCSI_COMMAND_REQUEST_SENSE allocation length is too small\n", getName()); | |
409 | exit(1); | |
410 | } | |
411 | ||
412 | int datalen = sizeof(scsi_disk_request_sense_data_t); | |
413 | uchar_t *datap = (uchar_t *)calloc(datalen, sizeof(uchar_t)); | |
414 | ||
415 | scsi_disk_request_sense_data_t *rqsdp = (scsi_disk_request_sense_data_t *)datap; | |
416 | rqsdp->valid = 1; | |
417 | rqsdp->error_code = 0x70; | |
418 | rqsdp->segment_number = 0; | |
419 | rqsdp->sense_key = 0x5; /* Illegal request */ | |
420 | rqsdp->additional_sense_length = 10; | |
421 | rqsdp->additional_sense_code = 0x20; /* Invalid command opcode */ | |
422 | rqsdp->additional_sense_code_qualifier = 0x00; | |
423 | ||
424 | memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true); | |
425 | ||
426 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
427 | ||
428 | free(datap); | |
429 | ||
430 | break; | |
431 | } | |
432 | ||
433 | case SCSI_COMMAND_LOG_SENSE: { | |
434 | scsi_command_log_sense_t *lsp; | |
435 | ||
436 | lsp = (scsi_command_log_sense_t *)req->CDB; | |
437 | ||
438 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
439 | ||
440 | break; | |
441 | } | |
442 | ||
443 | case SCSI_COMMAND_MODE_SENSE: { | |
444 | scsi_command_mode_sense_t *msp; | |
445 | ||
446 | msp = (scsi_command_mode_sense_t *)req->CDB; | |
447 | ||
448 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
449 | ||
450 | break; | |
451 | } | |
452 | ||
453 | case SCSI_COMMAND_PERSISTENT_RESERVE_IN: { | |
454 | scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false); | |
455 | ||
456 | break; | |
457 | } | |
458 | ||
459 | default: | |
460 | debug_err("%s: scsi command: 0x%x not supported\n", getName(), req->CDB[0]); | |
461 | exit(1); | |
462 | } | |
463 | } | |
464 | ||
465 | ||
466 | // Callback function for event handling | |
467 | void SAS::io_callback(int entry) { | |
468 | if (entry < 0 || entry >= EVENT_TABLE_SIZE) { | |
469 | debug_err("%s: invalid entry id %d for the event table\n", getName(), entry); | |
470 | exit(1); | |
471 | } | |
472 | ||
473 | if (_etable.events[entry].done == 0) { | |
474 | debug_err("%s: request in entry %d has not been served\n", getName(), entry); | |
475 | exit(1); | |
476 | } | |
477 | ||
478 | int64_t invoke_time = _etable.get_invoke_time(entry); | |
479 | int64_t current_time = mmi_get_time(); | |
480 | ||
481 | if (invoke_time != current_time) { | |
482 | debug_err("%s: invoke time %lld is different from current time %lld\n", getName(), invoke_time, current_time); | |
483 | exit(1); | |
484 | } | |
485 | ||
486 | debug_info("%s: io_callback is called, entry = %d, current time = %lld\n", getName(), entry, current_time); | |
487 | send_mpi_msg(_etable.events[entry].reply, _etable.events[entry].is_cntx, _etable.events[entry].cntx_type); | |
488 | ||
489 | // free the entry | |
490 | _etable.free(entry); | |
491 | } | |
492 | ||
493 | ||
494 | void sas_io_callback(void *arg1, void *arg2) { | |
495 | SAS *sasp = (SAS *)arg1; | |
496 | ||
497 | sasp->io_callback((int)(long long int)arg2); | |
498 | } |