Changeset View
Standalone View
sys/dev/ufshci/ufshci_sim.c
- This file was added.
| /*- | |||||||||
| * SPDX-License-Identifier: BSD-2-Clause | |||||||||
| * | |||||||||
| * Copyright (c) 2025, Samsung Electronics Co., Ltd. | |||||||||
| * All rights reserved. | |||||||||
| * | |||||||||
| * Redistribution and use in source and binary forms, with or without | |||||||||
| * modification, are permitted provided that the following conditions | |||||||||
| * are met: | |||||||||
| * 1. Redistributions of source code must retain the above copyright | |||||||||
| * notice, this list of conditions and the following disclaimer. | |||||||||
| * 2. Redistributions in binary form must reproduce the above copyright | |||||||||
| * notice, this list of conditions and the following disclaimer in the | |||||||||
| * documentation and/or other materials provided with the distribution. | |||||||||
| * | |||||||||
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||||||
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||||||
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||||||
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||||||
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||||||
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||||||
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||||||
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||||||
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||||||
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||||||
| * SUCH DAMAGE. | |||||||||
| */ | |||||||||
| #include <sys/param.h> | |||||||||
| #include <cam/cam.h> | |||||||||
| #include <cam/cam_ccb.h> | |||||||||
| #include <cam/cam_sim.h> | |||||||||
| #include <cam/cam_xpt_sim.h> | |||||||||
| #include <cam/cam_debug.h> | |||||||||
| #include <cam/scsi/scsi_all.h> | |||||||||
| #include "ufshci_private.h" | |||||||||
| #define sim2ctrlr(sim) ((struct ufshci_controller *)cam_sim_softc(sim)) | |||||||||
| static void | |||||||||
| ufshci_sim_scsiio_done(void *ccb_arg, const struct ufshci_completion *cpl, | |||||||||
| bool error) | |||||||||
| { | |||||||||
| const uint8_t *sense_data; | |||||||||
| uint16_t sense_data_max_size; | |||||||||
| uint16_t sense_data_len; | |||||||||
| union ccb *ccb = (union ccb *)ccb_arg; | |||||||||
| /* | |||||||||
| * Let the periph know the completion, and let it sort out what | |||||||||
| * it means. Report an error or success based on OCS and UPIU | |||||||||
| * response code. And We need to copy the sense data to be handled | |||||||||
| * by the CAM. | |||||||||
| */ | |||||||||
| sense_data = cpl->response_upiu.cmd_response_upiu.sense_data; | |||||||||
| sense_data_max_size = sizeof(cpl->response_upiu.cmd_response_upiu.sense_data); | |||||||||
| sense_data_len = be16toh(cpl->response_upiu.cmd_response_upiu.sense_data_len); | |||||||||
| memcpy(&ccb->csio.sense_data, sense_data, min(sense_data_len, sense_data_max_size)); | |||||||||
| ccb->ccb_h.status &= ~CAM_SIM_QUEUED; | |||||||||
| if (error) { | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP_ERR; | |||||||||
| printf("@@@@@ ufshci(4): ufshci_sim_scsiio_done: ERROR\n"); | |||||||||
| xpt_done(ccb); | |||||||||
imp: I assume this is going to be bad to keep in place... | |||||||||
Done Inline ActionsRemoved. jaeyoon: Removed. | |||||||||
| } else { | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP; | |||||||||
| xpt_done_direct(ccb); | |||||||||
| } | |||||||||
| // For debug | |||||||||
| // printf("@@@@@@@@@@@@@@@@@@@@@@@@@@ SCSI CMD DONE @@@@@@@@@@@@@@@@@@@@@@@@@@\n"); | |||||||||
| } | |||||||||
| /* ufshci_sim_illegal_request | |||||||||
| * Complete the command as an illegal command with invalid field | |||||||||
Done Inline Actions/* * ufshci_sim_illegal_request * * Complete the command as an illegal command with invalid field */ would match FreeBSD style better. imp: ```
/*
* ufshci_sim_illegal_request
*
* Complete the command as an illegal command with… | |||||||||
| */ | |||||||||
| static void | |||||||||
| ufshci_sim_illegal_request(union ccb *ccb) | |||||||||
| { | |||||||||
| scsi_set_sense_data(&ccb->csio.sense_data, | |||||||||
| /*sense_format*/ SSD_TYPE_NONE, | |||||||||
| /*current_error*/ 1, | |||||||||
| /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, | |||||||||
| /*asc*/ 0x24, /* 24h/00h INVALID FIELD IN CDB */ | |||||||||
| /*ascq*/ 0x00, | |||||||||
| /*extra args*/ SSD_ELEM_NONE); | |||||||||
| ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; | |||||||||
| ccb->ccb_h.status = | |||||||||
| CAM_SCSI_STATUS_ERROR | | |||||||||
| CAM_AUTOSNS_VALID | | |||||||||
| CAM_DEV_QFRZN; | |||||||||
| xpt_freeze_devq(ccb->ccb_h.path, 1); | |||||||||
| xpt_done(ccb); | |||||||||
| } | |||||||||
| static void | |||||||||
| ufshchi_sim_scsiio(struct cam_sim *sim, union ccb *ccb) | |||||||||
| { | |||||||||
| struct ccb_scsiio *csio = &ccb->csio; | |||||||||
| struct ufshci_request *req; | |||||||||
| void *payload; | |||||||||
| struct ufshci_cmd_command_upiu *upiu; | |||||||||
| uint8_t *cdb; | |||||||||
| uint32_t payload_len; | |||||||||
| bool is_write; | |||||||||
| struct ufshci_controller *ctrlr; | |||||||||
| uint8_t data_direction; | |||||||||
| int error; | |||||||||
| /* UFS device cannot process these commands */ | |||||||||
| if (csio->cdb_io.cdb_bytes[0] == MODE_SENSE_6 || | |||||||||
| csio->cdb_io.cdb_bytes[0] == MODE_SELECT_6 || | |||||||||
| csio->cdb_io.cdb_bytes[0] == READ_12 || | |||||||||
| csio->cdb_io.cdb_bytes[0] == WRITE_12) { | |||||||||
| ufshci_sim_illegal_request(ccb); | |||||||||
Done Inline ActionsSo it supports READ_6 and READ_10, but not READ_12? imp: So it supports READ_6 and READ_10, but not READ_12?
| |||||||||
Done Inline ActionsYes, if you look at section 5.6 of the UFS 2.1 specification, you can see that it supports READ 6, 10, and 16. jaeyoon: Yes, if you look at section 5.6 of the UFS 2.1 specification, you can see that it supports READ… | |||||||||
| return; | |||||||||
| } | |||||||||
| ctrlr = sim2ctrlr(sim); | |||||||||
| payload = csio->data_ptr; | |||||||||
| payload_len = csio->dxfer_len; | |||||||||
| is_write = csio->ccb_h.flags & CAM_DIR_OUT; | |||||||||
| // For debug | |||||||||
| printf("@@@@ cdb_io.cdb_bytes[0] = 0x%x, ccb_h.flags = 0x%x, payload_len = 0x%x\n", | |||||||||
| csio->cdb_io.cdb_bytes[0], csio->ccb_h.flags, payload_len); | |||||||||
| /* TODO: Check other data type */ | |||||||||
| if ((csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_BIO) | |||||||||
| req = ufshci_allocate_request_bio((struct bio *)payload, M_NOWAIT, | |||||||||
| ufshci_sim_scsiio_done, ccb); | |||||||||
| else | |||||||||
| req = ufshci_allocate_request_vaddr(payload, payload_len, M_NOWAIT, | |||||||||
| ufshci_sim_scsiio_done, ccb); | |||||||||
| req->request_size = sizeof(struct ufshci_cmd_command_upiu); | |||||||||
| req->response_size = sizeof(struct ufshci_cmd_response_upiu); | |||||||||
| switch (ccb->ccb_h.flags & CAM_DIR_MASK) { | |||||||||
| case CAM_DIR_IN: | |||||||||
| data_direction = UFSHCI_DATA_DIRECTION_FROM_TGT_TO_SYS; | |||||||||
| break; | |||||||||
| case CAM_DIR_OUT: | |||||||||
| data_direction = UFSHCI_DATA_DIRECTION_FROM_SYS_TO_TGT; | |||||||||
| break; | |||||||||
| default: | |||||||||
| data_direction = UFSHCI_DATA_DIRECTION_NO_DATA_TRANSFER; | |||||||||
| } | |||||||||
| req->data_direction = data_direction; | |||||||||
| upiu = (struct ufshci_cmd_command_upiu *)&req->request_upiu; | |||||||||
| memset(upiu, 0, req->request_size); | |||||||||
| upiu->header.trans_type = UFSHCI_UPIU_TRANSACTION_CODE_COMMAND; | |||||||||
| upiu->header.operational_flags = is_write ? UFSHCI_OPERATIONAL_FLAG_W | |||||||||
| : UFSHCI_OPERATIONAL_FLAG_R; | |||||||||
| upiu->header.lun = csio->ccb_h.target_lun; | |||||||||
| upiu->header.task_tag = csio->tag_id; | |||||||||
| upiu->header.cmd_set_type = UFSHCI_COMMAND_SET_TYPE_SCSI; | |||||||||
| upiu->expected_data_transfer_length = payload_len; | |||||||||
| ccb->ccb_h.status |= CAM_SIM_QUEUED; | |||||||||
| if (csio->ccb_h.flags & CAM_CDB_POINTER) | |||||||||
| cdb = csio->cdb_io.cdb_ptr; | |||||||||
| else | |||||||||
| cdb = csio->cdb_io.cdb_bytes; | |||||||||
| if (cdb == NULL || csio->cdb_len > sizeof(upiu->cdb)) { | |||||||||
| ccb->ccb_h.status = CAM_REQ_INVALID; | |||||||||
| xpt_done(ccb); | |||||||||
| return; | |||||||||
| } | |||||||||
| memcpy(upiu->cdb, cdb, csio->cdb_len); | |||||||||
| /* TODO: P1. Separate IO and Admin slot */ | |||||||||
| error = ufshci_ctrl_submit_io_request(ctrlr, req); | |||||||||
Done Inline ActionsWhat's the selector between I/O and Admin here? This is for SCSI CDBs, so how is that relevant to I/O vs Admin... In NVMe, it wouldn't be relevant, but I've not not read that part of the UFS standard yet... imp: What's the selector between I/O and Admin here? This is for SCSI CDBs, so how is that relevant… | |||||||||
Done Inline ActionsAdmin slot is a dedicated slot for QueryRequest cmd to manage the device, not SCSI cmd. In the Single Doorbell list, there are 32 slots in total, and the 32nd slot should be reserved for the admin slot. In early review, the 32 slots are shared by both I/O and Admin command. I will separate them later. I don't think this comment is in the right place, let's move it somewhere else. jaeyoon: Admin slot is a dedicated slot for QueryRequest cmd to manage the device, not SCSI cmd. In the… | |||||||||
| if (error == EBUSY) { | |||||||||
| ccb->ccb_h.status = CAM_SCSI_BUSY; | |||||||||
| // For debug | |||||||||
| // printf("@@@@@@@@@@@@@@@@@@@@@@@@@@ SCSI CMD BUSY @@@@@@@@@@@@@@@@@@@@@@@@@@\n"); | |||||||||
| xpt_done(ccb); | |||||||||
| return; | |||||||||
| } else if (error) { | |||||||||
| ccb->ccb_h.status = CAM_REQ_INVALID; | |||||||||
| xpt_done(ccb); | |||||||||
| return; | |||||||||
| } | |||||||||
| } | |||||||||
| static uint32_t | |||||||||
| ufshci_link_kBps(struct ufshci_controller *ctrlr) | |||||||||
| { | |||||||||
| uint32_t gear = ctrlr->hs_gear; | |||||||||
| uint32_t lanes = ctrlr->rx_lanes; | |||||||||
| /* | |||||||||
| * per-lane effective bandwidth (KB/s, SI 1 KB = 1000 B) | |||||||||
| * All HS-Gears use 8b/10b line coding, i.e. 80 % efficiency. | |||||||||
| * - KB/s per lane = raw-rate(Gbps) × 0.8 / 8 | |||||||||
| */ | |||||||||
| static const uint32_t kbps_per_lane[] = { | |||||||||
| 0, /* unused */ | |||||||||
| 145920, /* HS-Gear1 : 1459.2 Mbps *0.8 /8 */ | |||||||||
| 291840, /* HS-Gear2 : 2918.4 Mbps *0.8 /8 */ | |||||||||
| 583680, /* HS-Gear3 : 5836.8 Mbps *0.8 /8 */ | |||||||||
| 1414986, /* HS-Gear4 : 11673.6 Mbps *0.9697 /8 */ | |||||||||
| 2829972 /* HS-Gear5 : 23347.2 Mbps *0.9697 /8 */ | |||||||||
| }; | |||||||||
| /* Sanity checks */ | |||||||||
| if (gear >= nitems(kbps_per_lane)) | |||||||||
| gear = 0; /* out-of-range -> treat as invalid */ | |||||||||
| if (lanes == 0 || lanes > 2) | |||||||||
| lanes = 1; /* UFS spec allows 1–2 data lanes */ | |||||||||
| return kbps_per_lane[gear] * lanes; | |||||||||
| } | |||||||||
| static void | |||||||||
| ufshci_cam_action(struct cam_sim *sim, union ccb *ccb) | |||||||||
| { | |||||||||
| struct ufshci_controller *ctrlr = sim2ctrlr(sim); | |||||||||
| if (ctrlr == NULL) { | |||||||||
| ccb->ccb_h.status = CAM_SEL_TIMEOUT; | |||||||||
| xpt_done(ccb); | |||||||||
| return; | |||||||||
| } | |||||||||
| /* Perform the requested action */ | |||||||||
| switch (ccb->ccb_h.func_code) { | |||||||||
| case XPT_SCSI_IO: | |||||||||
| ufshchi_sim_scsiio(sim, ccb); | |||||||||
| return; | |||||||||
| case XPT_PATH_INQ: | |||||||||
| { | |||||||||
| struct ccb_pathinq *cpi = &ccb->cpi; | |||||||||
| cpi->version_num = 1; | |||||||||
| cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE; | |||||||||
| cpi->target_sprt = 0; | |||||||||
| cpi->hba_misc = PIM_UNMAPPED | PIM_NO_6_BYTE; | |||||||||
| cpi->hba_eng_cnt = 0; | |||||||||
| cpi->max_target = 0; | |||||||||
| cpi->max_lun = ctrlr->max_lun_count; | |||||||||
| cpi->async_flags = 0; | |||||||||
| cpi->maxio = ctrlr->max_xfer_size; | |||||||||
| cpi->initiator_id = 1; | |||||||||
| strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); | |||||||||
| strlcpy(cpi->hba_vid, "UFSHCI", HBA_IDLEN); | |||||||||
| strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); | |||||||||
| cpi->unit_number = cam_sim_unit(sim); | |||||||||
| cpi->base_transfer_speed = ufshci_link_kBps(ctrlr); | |||||||||
| cpi->transport = XPORT_UFSHCI; | |||||||||
| cpi->transport_version = 1; | |||||||||
| cpi->protocol = PROTO_SCSI; | |||||||||
| cpi->protocol_version = SCSI_REV_SPC5; | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP; | |||||||||
| break; | |||||||||
| } | |||||||||
| case XPT_RESET_BUS: | |||||||||
| if (ufshci_ctrl_reset(ctrlr)) | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP_ERR; | |||||||||
| else { | |||||||||
| /* TODO: revisit reset sequence */ | |||||||||
| // ufshci_ctrlr_start(ctrlr); | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP; | |||||||||
| } | |||||||||
| break; | |||||||||
| case XPT_RESET_DEV: | |||||||||
| if (ufshci_dev_reset(ctrlr)) | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP_ERR; | |||||||||
| else | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP; | |||||||||
| break; | |||||||||
| case XPT_ABORT: | |||||||||
| /* TODO: Implement Task Management CMD*/ | |||||||||
| ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; | |||||||||
| break; | |||||||||
| case XPT_SET_TRAN_SETTINGS: | |||||||||
| ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; | |||||||||
| break; | |||||||||
| case XPT_GET_TRAN_SETTINGS: | |||||||||
| { | |||||||||
| struct ccb_trans_settings *cts; | |||||||||
| struct ccb_trans_settings_ufshci *ufshcix; | |||||||||
| cts = &ccb->cts; | |||||||||
| ufshcix = &cts->xport_specific.ufshci; | |||||||||
| ufshcix->valid = CTS_UFSHCI_VALID_MODE; | |||||||||
| ufshcix->hs_gear = ctrlr->hs_gear; | |||||||||
| ufshcix->tx_lanes = ctrlr->tx_lanes; | |||||||||
| ufshcix->rx_lanes = ctrlr->rx_lanes; | |||||||||
| ufshcix->max_hs_gear = ctrlr->max_rx_hs_gear; | |||||||||
| ufshcix->max_tx_lanes = ctrlr->max_tx_lanes; | |||||||||
| ufshcix->max_rx_lanes = ctrlr->max_rx_lanes; | |||||||||
| cts->transport = XPORT_UFSHCI; | |||||||||
| cts->transport_version = 1; | |||||||||
| cts->protocol = PROTO_SCSI; | |||||||||
| cts->protocol_version = SCSI_REV_SPC5; | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP; | |||||||||
| break; | |||||||||
| } | |||||||||
| case XPT_CALC_GEOMETRY: | |||||||||
| cam_calc_geometry(&ccb->ccg, 1); | |||||||||
| break; | |||||||||
| case XPT_NOOP: | |||||||||
| ccb->ccb_h.status = CAM_REQ_CMP; | |||||||||
| break; | |||||||||
| default: | |||||||||
| printf("invalid ccb=%p func=%#x\n", ccb, ccb->ccb_h.func_code); | |||||||||
| break; | |||||||||
| } | |||||||||
| xpt_done(ccb); | |||||||||
| return; | |||||||||
| } | |||||||||
| static void | |||||||||
| ufshci_cam_poll(struct cam_sim *sim) | |||||||||
| { | |||||||||
| struct ufshci_controller *ctrlr = sim2ctrlr(sim); | |||||||||
| ufshci_ctrlr_poll(ctrlr); | |||||||||
| } | |||||||||
| int | |||||||||
| ufshci_sim_attach(struct ufshci_controller *ctrlr) | |||||||||
| { | |||||||||
| device_t dev; | |||||||||
| struct cam_devq *devq; | |||||||||
| int max_trans; | |||||||||
| dev = ctrlr->dev; | |||||||||
| max_trans = ctrlr->max_hw_pend_io; | |||||||||
| if ((devq = cam_simq_alloc(max_trans)) == NULL) { | |||||||||
| printf("Failed to allocate a simq\n"); | |||||||||
| return (ENOMEM); | |||||||||
| } | |||||||||
| ctrlr->ufshci_sim = cam_sim_alloc(ufshci_cam_action, ufshci_cam_poll, | |||||||||
| "ufshci", ctrlr, device_get_unit(dev), &ctrlr->sc_mtx, max_trans, | |||||||||
| max_trans, devq); | |||||||||
| if (ctrlr->ufshci_sim == NULL) { | |||||||||
| printf("Failed to allocate a sim\n"); | |||||||||
| cam_simq_free(devq); | |||||||||
| return (ENOMEM); | |||||||||
| } | |||||||||
| mtx_lock(&ctrlr->sc_mtx); | |||||||||
| if (xpt_bus_register(ctrlr->ufshci_sim, ctrlr->dev, 0) != CAM_SUCCESS){ | |||||||||
| cam_sim_free(ctrlr->ufshci_sim, /*free_devq*/TRUE); | |||||||||
Done Inline Actions
jrtc27: | |||||||||
| cam_simq_free(devq); | |||||||||
| mtx_unlock(&ctrlr->sc_mtx); | |||||||||
| printf("Failed to create a bus\n"); | |||||||||
| return (ENOMEM); | |||||||||
| } | |||||||||
| if (xpt_create_path(&ctrlr->ufshci_path, /*periph*/NULL, cam_sim_path(ctrlr->ufshci_sim), | |||||||||
| CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { | |||||||||
| xpt_bus_deregister(cam_sim_path(ctrlr->ufshci_sim)); | |||||||||
| cam_sim_free(ctrlr->ufshci_sim, /*free_devq*/TRUE); | |||||||||
| cam_simq_free(devq); | |||||||||
| mtx_unlock(&ctrlr->sc_mtx); | |||||||||
| printf("Failed to create a path\n"); | |||||||||
| return (ENOMEM); | |||||||||
| } | |||||||||
| mtx_unlock(&ctrlr->sc_mtx); | |||||||||
| return (0); | |||||||||
| } | |||||||||
| void | |||||||||
| ufshci_sim_detach(struct ufshci_controller *ctrlr) | |||||||||
| { | |||||||||
| int error; | |||||||||
| if (ctrlr->ufshci_path != NULL) { | |||||||||
| xpt_free_path(ctrlr->ufshci_path); | |||||||||
| ctrlr->ufshci_path = NULL; | |||||||||
| } | |||||||||
| if (ctrlr->ufshci_sim != NULL) { | |||||||||
| error = xpt_bus_deregister(cam_sim_path(ctrlr->ufshci_sim)); | |||||||||
| if (error == 0) { | |||||||||
| /* accessing the softc is not possible after this */ | |||||||||
| ctrlr->ufshci_sim->softc = NULL; | |||||||||
| ufshci_printf(ctrlr, "%s: %s:%d:%d caling " | |||||||||
| "cam_sim_free sim %p refc %u mtx %p\n", | |||||||||
| __func__, ctrlr->sc_name, cam_sim_path(ctrlr->ufshci_sim), | |||||||||
| ctrlr->sc_unit, ctrlr->ufshci_sim, | |||||||||
| ctrlr->ufshci_sim->refcount, ctrlr->ufshci_sim->mtx); | |||||||||
| } else { | |||||||||
| panic("%s: %s: CAM layer is busy: errno %d\n", | |||||||||
| __func__, ctrlr->sc_name, error); | |||||||||
| } | |||||||||
| cam_sim_free(ctrlr->ufshci_sim, /* free_devq */ TRUE); | |||||||||
| ctrlr->ufshci_sim = NULL; | |||||||||
| } | |||||||||
| } | |||||||||
I assume this is going to be bad to keep in place...