Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / sam / devices / sas / sas_disk.cc
// ========== Copyright Header Begin ==========================================
//
// OpenSPARC T2 Processor File: sas_disk.cc
// Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
//
// The above named program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License version 2 as published by the Free Software Foundation.
//
// The above named program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this work; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
// ========== Copyright Header End ============================================
/*
* "sas_disk.cc" LSI SAS1064E PCIE to 4-Port Serial Attached
* SCSI Controller Simulator
* SCSI disk implementation
*
* Copyright (C) 2007 Sun Microsystems, Inc.
* All rights reserved.
*
*/
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/dirent.h>
#include <fcntl.h>
#include "sas.h"
#include "sas_disk.h"
// scsi disk block size
#define SCSI_DISK_SECTOR_SIZE 512
#define SCSI_DISK_LBLK_SIZE SCSI_DISK_SECTOR_SIZE
// Compute the time when an io request will finish
void SAS::compute_disk_delay(int entry, int target, bool wr) {
int64_t current_time = mmi_get_time();
if (target >= 0 && target < IOC_NUM_PORTS && _disk[target]) {
// target exists
uint32_t disk_delay;
if (wr)
disk_delay = _disk[target]->get_write_delay();
else
disk_delay = _disk[target]->get_read_delay();
if (_earliest_serving_time[target] > current_time)
_earliest_serving_time[target] = _earliest_serving_time[target] + disk_delay;
else
_earliest_serving_time[target] = current_time + disk_delay;
_etable.set_invoke_time(entry, _earliest_serving_time[target]);
}
else {
// target doesn't exist at all
if (wr)
_etable.set_invoke_time(entry, current_time + WRITE_DELAY_DEFAULT);
else
_etable.set_invoke_time(entry, current_time + READ_DELAY_DEFAULT);
}
}
// IO request is done, record the reply msg and time in the event table
void SAS::scsi_io_done(int entry, uint32_t reply, uint8_t is_cntx, uint8_t cntx_type, int target, bool wr) {
// update event table entry
_etable.done(entry, reply, is_cntx, cntx_type);
compute_disk_delay(entry, target, wr);
}
// Error msg is needed especially during scsi target probing,
// where it is used to inform the host that a target disk doesn't exist.
void SAS::scsi_io_error(int entry, msg_scsi_io_request_t *req, uint16_t ioc_status) {
msg_scsi_io_reply_t *reply = (msg_scsi_io_reply_t *)calloc(1, sizeof(msg_scsi_io_reply_t));
reply->TargetID = req->TargetID;
reply->Bus = req->Bus;
reply->MsgLength = sizeof(msg_scsi_io_reply) >> 2;
reply->Function = req->Function;
reply->CDBLength = req->CDBLength;
reply->SenseBufferLength = req->SenseBufferLength;
reply->MsgFlags = req->MsgFlags;
reply->MsgContext = SAM_LE_32(req->MsgContext);
reply->SCSIStatus = MPI_SCSI_STATUS_SUCCESS;
reply->SCSIState = MPI_SCSI_STATE_NO_SCSI_STATUS;
reply->IOCStatus = SAM_LE_16(ioc_status & MPI_IOCSTATUS_MASK);
reply->IOCLogInfo = 0;
reply->TransferCount = 0;
reply->SenseCount = 0;
reply->ResponseInfo = 0;
reply->TaskTag = 0;
if (_reply_free_queue.empty()) {
debug_err("%s: reply free queue is empty\n", getName());
exit(1);
}
uint32_t fma = _reply_free_queue.front();
uint32_t msg_addr = fma & FMA_ADDRESS_MASK;
_reply_free_queue.pop();
memory_access(mfa_addr(msg_addr), mfa_addr_mode(), (void *)reply, sizeof(msg_scsi_io_reply_t), true);
scsi_io_done(entry, msg_addr >> 1, 0, 0, req->TargetID, false);
debug_info("%s: reply frame address= 0x%x, msgcontext=0x%x\n", getName(), msg_addr, reply->MsgContext);
free(reply);
}
// Process a SCSI request
// step 1: memory_transport(), move the required data from/to disk to/from memory
// step 2: send_mpi_msg(), send the reply to the host
// Some requests may not need the first step.
void SAS::process_scsi_cmd(int entry, msg_scsi_io_request_t *req, uint64_t sge_addr) {
if (!(req->Bus == 0)) {
debug_info("%s: only bus 0 is supported\n", getName());
scsi_io_error(entry, req, MPI_IOCSTATUS_SCSI_INVALID_BUS);
return;
}
if (!(req->TargetID >= 0 && req->TargetID < IOC_NUM_PORTS && _disk[req->TargetID])) {
debug_info("%s: target %d doesn't exists, check %s\n", getName(), req->TargetID, fname);
scsi_io_error(entry, req, MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE);
return;
}
if (!(req->LUN[0] == 0 && req->LUN[1] == 0)) {
debug_err("%s: only LUN 0 is supported\n", getName());
exit(1);
}
debug_info("%s: process scsi cmd for Bus %d, Target %d...\n", getName(), req->Bus, req->TargetID);
switch (req->CDB[0]) {
case SCSI_COMMAND_REPORT_LUNS: {
scsi_command_report_luns_t *lunp;
lunp = (scsi_command_report_luns_t *)req->CDB;
int32_t alloc_length = (lunp->allocation_length[0] << 24) |
(lunp->allocation_length[1] << 16) |
(lunp->allocation_length[2] << 8) |
(lunp->allocation_length[3] << 0);
if (alloc_length < 16) {
debug_err("%s: allocation length in report lun is less than 16 bytes\n", getName());
exit(1);
}
int datalen = sizeof(struct scsi_report_luns);
uchar_t *datap = (uchar_t *)calloc(datalen, sizeof(uchar_t));
struct scsi_report_luns *lundp = (struct scsi_report_luns *)datap;
lundp->lun_list_len = SAM_BE_32(8);
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
free(datap);
break;
}
case SCSI_COMMAND_INQUIRY: {
scsi_command_inquiry_t *inquiryp;
inquiryp = (scsi_command_inquiry_t *)req->CDB;
int datalen = 0;
uchar_t *datap;
if ((req->CDB[1] & 1) == 0) { /* Non-EVPD Inquiry */
datalen = sizeof(SCSI_Inquiry_Data);
datap = (uchar_t *)calloc (datalen, sizeof(uchar_t));
bcopy(_disk[req->TargetID]->get_SCSI_Inquiry_Data(), datap, datalen);
}
else { /* EVPD Inquiry */
switch (req->CDB[2]) {
case 0x00: {
datalen = sizeof(Supported_VPD);
datap = (uchar_t*)calloc(datalen, sizeof(uchar_t));
bcopy(_disk[req->TargetID]->get_Supported_VPD(), datap, datalen);
break;
}
case 0x80: { // Unit Serial Number
datalen = sizeof(Unit_Serial_Number);
datap = (uchar_t*)calloc (datalen, sizeof(uchar_t));
bcopy(_disk[req->TargetID]->get_Unit_Serial_Number(), datap, datalen);
break;
}
case 0x81: { // Implemented Operating Definition
datalen = sizeof(Implemented_Operating_Definition);
datap = (uchar_t*)calloc(datalen, sizeof(uchar_t));
bcopy(_disk[req->TargetID]->get_Implemented_Operating_Definition(), datap, datalen);
break;
}
case 0x83: { // Device Identification
datalen = sizeof(Device_Identification);
datap = (uchar_t*)calloc(datalen, sizeof(uchar_t));
bcopy(_disk[req->TargetID]->get_Device_Identification(), datap, datalen);
break;
}
default:
debug_err("%s: scsi_extended_inquiry page 0x%x NOT IMPL\n", getName(), req->CDB[2]);
exit(1);
}
}
assert(datalen > 0);
if (datalen > inquiryp->allocation_length) {
debug_info("%s: inquiry allocated data length is less than datalen\n", getName());
datalen = inquiryp->allocation_length;
}
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
free(datap);
break;
}
case SCSI_COMMAND_TEST_UNIT_READY: {
scsi_command_test_unit_ready_t *turp;
turp = (scsi_command_test_unit_ready_t *)req->CDB;
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
break;
}
case SCSI_COMMAND_START_STOP_UNIT: {
scsi_command_start_stop_unit_t *ssup;
ssup = (scsi_command_start_stop_unit_t *)req->CDB;
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
break;
}
case SCSI_COMMAND_READ_CAPACITY: {
scsi_command_read_capacity_t *rcp;
rcp = (scsi_command_read_capacity_t *)req->CDB;
int datalen = sizeof(scsi_command_read_capacity_data_t);
uchar_t *datap = (uchar_t *)calloc(datalen, sizeof(uchar_t));
scsi_command_read_capacity_data_t *rcdp = (scsi_command_read_capacity_data_t *)datap;
rcdp->lblk = SAM_BE_32(_disk[req->TargetID]->get_capacity());
rcdp->lblk_size = SAM_BE_32(SCSI_DISK_LBLK_SIZE);
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
free(datap);
break;
}
case SCSI_COMMAND_READ: {
scsi_command_read_t *rdp;
daddr_t lblkno;
size_t blks, size;
uchar_t *tmp_buf;
rdp = (scsi_command_read_t *)req->CDB;
lblkno = rdp->lblk_msb << 16;
lblkno |= SAM_BE_16(rdp->lblk);
blks = rdp->transfer_length;
size = blks * SCSI_DISK_LBLK_SIZE;
tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t));
debug_info("%s: read----> blkno: %d, blks: %d\n", getName(), lblkno, blks);
_disk[req->TargetID]->disk_read_lblk(lblkno, tmp_buf, blks);
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, true);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
free(tmp_buf);
break;
}
case SCSI_COMMAND_READ_LONG: {
scsi_command_read_long_t *rdlp;
daddr_t lblkno;
size_t blks, size;
uchar_t *tmp_buf;
rdlp = (scsi_command_read_long_t *)req->CDB;
lblkno = (rdlp->lblk[0] << 24) |
(rdlp->lblk[1] << 16) |
(rdlp->lblk[2] << 8) |
(rdlp->lblk[3] << 0);
blks = (rdlp->transfer_length[0] << 8) |
(rdlp->transfer_length[1] << 0);
size = blks * SCSI_DISK_LBLK_SIZE;
tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t));
debug_info("%s: read long----> blkno: %d, blks: %d\n", getName(), lblkno, blks);
_disk[req->TargetID]->disk_read_lblk(lblkno, tmp_buf, blks);
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, true);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
free(tmp_buf);
break;
}
case SCSI_COMMAND_WRITE: {
scsi_command_write_t *wrp;
daddr_t lblkno;
size_t blks, size;
uchar_t *tmp_buf;
wrp = (scsi_command_write_t *)req->CDB;
lblkno = (wrp->lblk_msb << 16) | SAM_BE_16(wrp->lblk);
blks = wrp->transfer_length;
size = blks * SCSI_DISK_LBLK_SIZE;
tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t));
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, false);
debug_info("%s: write---> blkno: %d, blks: %d\n", getName(), lblkno, blks);
_disk[req->TargetID]->disk_write_lblk(lblkno, tmp_buf, blks);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, true);
free(tmp_buf);
break;
}
case SCSI_COMMAND_WRITE_LONG: {
scsi_command_write_long_t *wrlp;
daddr_t lblkno;
size_t blks, size;
uchar_t *tmp_buf;
wrlp = (scsi_command_write_long_t *)req->CDB;
lblkno = (wrlp->lblk[0] << 24) |
(wrlp->lblk[1] << 16) |
(wrlp->lblk[2] << 8) |
(wrlp->lblk[3] << 0);
blks = (wrlp->transfer_length[0] << 8) |
(wrlp->transfer_length[1] << 0);
size = blks * SCSI_DISK_LBLK_SIZE;
tmp_buf = (uchar_t *)calloc(size, sizeof(uchar_t));
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), tmp_buf, size, false);
debug_info("%s: write long----> blkno: %d, blks: %d\n", getName(), lblkno, blks);
_disk[req->TargetID]->disk_write_lblk(lblkno, tmp_buf, blks);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, true);
free(tmp_buf);
break;
}
case SCSI_COMMAND_REQUEST_SENSE: {
scsi_command_request_sense_t *rqsp;
rqsp = (scsi_command_request_sense_t *)req->CDB;
if (rqsp->allocation_length < sizeof(scsi_disk_request_sense_data_t)) {
debug_err("%s: SCSI_COMMAND_REQUEST_SENSE allocation length is too small\n", getName());
exit(1);
}
int datalen = sizeof(scsi_disk_request_sense_data_t);
uchar_t *datap = (uchar_t *)calloc(datalen, sizeof(uchar_t));
scsi_disk_request_sense_data_t *rqsdp = (scsi_disk_request_sense_data_t *)datap;
rqsdp->valid = 1;
rqsdp->error_code = 0x70;
rqsdp->segment_number = 0;
rqsdp->sense_key = 0x5; /* Illegal request */
rqsdp->additional_sense_length = 10;
rqsdp->additional_sense_code = 0x20; /* Invalid command opcode */
rqsdp->additional_sense_code_qualifier = 0x00;
memory_transport(mfa_addr(sge_addr), mfa_addr_mode(), datap, datalen, true);
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
free(datap);
break;
}
case SCSI_COMMAND_LOG_SENSE: {
scsi_command_log_sense_t *lsp;
lsp = (scsi_command_log_sense_t *)req->CDB;
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
break;
}
case SCSI_COMMAND_MODE_SENSE: {
scsi_command_mode_sense_t *msp;
msp = (scsi_command_mode_sense_t *)req->CDB;
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
break;
}
case SCSI_COMMAND_PERSISTENT_RESERVE_IN: {
scsi_io_done(entry, req->MsgContext, true, 0, req->TargetID, false);
break;
}
default:
debug_err("%s: scsi command: 0x%x not supported\n", getName(), req->CDB[0]);
exit(1);
}
}
// Callback function for event handling
void SAS::io_callback(int entry) {
if (entry < 0 || entry >= EVENT_TABLE_SIZE) {
debug_err("%s: invalid entry id %d for the event table\n", getName(), entry);
exit(1);
}
if (_etable.events[entry].done == 0) {
debug_err("%s: request in entry %d has not been served\n", getName(), entry);
exit(1);
}
int64_t invoke_time = _etable.get_invoke_time(entry);
int64_t current_time = mmi_get_time();
if (invoke_time != current_time) {
debug_err("%s: invoke time %lld is different from current time %lld\n", getName(), invoke_time, current_time);
exit(1);
}
debug_info("%s: io_callback is called, entry = %d, current time = %lld\n", getName(), entry, current_time);
send_mpi_msg(_etable.events[entry].reply, _etable.events[entry].is_cntx, _etable.events[entry].cntx_type);
// free the entry
_etable.free(entry);
}
void sas_io_callback(void *arg1, void *arg2) {
SAS *sasp = (SAS *)arg1;
sasp->io_callback((int)(long long int)arg2);
}