As mentioned previously, action routines are executed every time a SIM receives a CCB. You can think of action routines like the “main function” for a SIM.
Here is the function prototype for an action routine (taken from the <cam/cam_sim.h>
header):
typedef void (*sim_action_func)(struct cam_sim *sim, union ccb *ccb);
Recall that action routines switch
according to the ccb->ccb_h.func_code
variable, which contains a constant that symbolizes the I/O operation to perform. For the rest of this chapter, I’ll detail the most common constants/operations.
For the complete list of constants/operations, see the xpt_opcode
enumeration defined in the <cam/cam_ccb.h>
header.
The XPT_PATH_INQ
constant specifies a path inquiry operation, which returns the SIM and HBA properties. Action routines that are passed XPT_PATH_INQ
simply fill in a ccb_pathinq
structure and then return.
struct ccb_pathinq
is defined in the <cam/cam_ccb.h>
header as follows:
struct ccb_pathinq { struct ccb_hdr ccb_h; /* Header information fields. */ u_int8_t version_num; /* Version number. */ u_int8_t hba_inquiry; /* Imitate INQ byte 7. */ u_int8_t target_sprt; /* Target mode support flags. */ u_int8_t hba_misc; /* Miscellaneous HBA features. */ u_int16_t hba_eng_cnt; /* HBA engine count. */ u_int8_t vuhba_flags[VUHBALEN]; /* Vendor unique capabilities. */ u_int32_t max_target; /* Maximum supported targets. */ u_int32_t max_lun; /* Maximum supported LUN. */ u_int32_t async_flags; /* Asynchronous handler flags. */ path_id_t hpath_id; /* Highest path ID in the subsystem. */ target_id_t initiator_id; /* HBA ID on the bus. */ char sim_vid[SIM_IDLEN]; /* SIM vendor ID. */ char hba_vid[HBA_IDLEN]; /* HBA vendor ID. */ char dev_name[DEV_IDLEN]; /* SIM device name. */ u_int32_t unit_number; /* SIM unit number. */ u_int32_t bus_id; /* SIM bus ID. */ u_int32_t base_transfer_speed; /* Base bus speed in KB/sec. */ cam_proto protocol; /* CAM protocol. */ u_int protocol_version; /* CAM protocol version. */ cam_xport transport; /* Transport (e.g., FC, USB). */ u_int transport_version; /* Transport version. */ union { struct ccb_pathinq_settings_spi spi; struct ccb_pathinq_settings_fc fc; struct ccb_pathinq_settings_sas sas; char ccb_pathinq_settings_opaque[PATHINQ_SETTINGS_SIZE]; } xport_specific; u_int maxio; /* Maximum supported I/O size (in bytes). */ };
Here is an example XPT_PATH_INQ
operation (taken from Example 14-1):
static void mfip_action(struct cam_sim *sim, union ccb *ccb) { struct mfip *sc; struct mfi_softc *mfi; sc = cam_sim_softc(sim); mfi = sc->mfi; mtx_assert(&mfi->mfi_io_lock, MA_OWNED); switch (ccb->ccb_h.func_code) { case XPT_PATH_INQ: { struct ccb_pathinq *cpi; cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE | PI_WIDE_16; cpi->target_sprt = 0; cpi->hba_misc = PIM_NOBUSRESET | PIM_SEQSCAN; cpi->hba_eng_cnt = 0; cpi->max_target = MFI_SCSI_MAX_TARGETS; cpi->max_lun = MFI_SCSI_MAX_LUNS; cpi->initiator_id = MFI_SCSI_INITIATOR_ID; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "LSI", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 150000; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->transport = XPORT_SAS; cpi->transport_version = 0; cpi->ccb_h.status = CAM_REQ_CMP; break; } ... default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; }
Notice that the ccb_pathinq
structure is provided by the CCB. Moreover, notice that the success or failure of any operation is returned in ccb_h.status
.
The XPT_RESET_BUS
constant specifies a bus reset operation. As you’d expect, XPT_RESET_BUS
is horrifically hardware specific. Here is a minimalist implementation (taken from Example 14-1):
static void mfip_action(struct cam_sim *sim, union ccb *ccb) { struct mfip *sc; struct mfi_softc *mfi; sc = cam_sim_softc(sim); mfi = sc->mfi; mtx_assert(&mfi->mfi_io_lock, MA_OWNED); switch (ccb->ccb_h.func_code) { ... case XPT_RESET_BUS: ccb->ccb_h.status = CAM_REQ_CMP; break; ... default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; }
Here, sim
is the bus to reset. Unsurprisingly, minimalist implementations forgo any “real” work and simply return success.
Many SIMs use a minimalist implementation. A “proper” implementation is out of the scope of this book.
The XPT_GET_TRAN_SETTINGS
constant denotes an I/O operation that returns the current transfer settings or the user-defined upper limits. Action routines that are passed XPT_GET_TRAN_SETTINGS
simply fill in a ccb_trans_settings
structure and then return.
struct ccb_trans_settings
is defined in <cam/cam_ccb.h>
like so:
typedef enum { CTS_TYPE_CURRENT_SETTINGS, /* Current transfer settings. */ CTS_TYPE_USER_SETTINGS /* User-defined upper limits. */ } cts_type; struct ccb_trans_settings { struct ccb_hdr ccb_h; /* Header information fields. */ cts_type type; /* Current or user settings? */ cam_proto protocol; /* CAM protocol. */ u_int protocol_version; /* CAM protocol version. */ cam_xport transport; /* Transport (e.g., FC, USB). */ u_int transport_version; /* Transport version. */ union { u_int valid; /* Which field(s) to honor. */ struct ccb_trans_settings_scsi scsi; } proto_specific; union { u_int valid; /* Which field(s) to honor. */ struct ccb_trans_settings_spi spi; struct ccb_trans_settings_fc fc; struct ccb_trans_settings_sas sas; struct ccb_trans_settings_ata ata; struct ccb_trans_settings_sata sata; } xport_specific; };
As you can see, ccb_trans_settings
marshals a protocol structure and five transport-specific structures. These structures are defined in <cam/cam_ccb.h>
like so:
struct ccb_trans_settings_scsi { u_int valid; /* Which field(s) to honor. */ #define CTS_SCSI_VALID_TQ 0x01 u_int flags; #define CTS_SCSI_FLAGS_TAG_ENB 0x01 }; struct ccb_trans_settings_spi { u_int valid; /* Which field(s) to honor. */ #define CTS_SPI_VALID_SYNC_RATE 0x01 #define CTS_SPI_VALID_SYNC_OFFSET 0x02 #define CTS_SPI_VALID_BUS_WIDTH 0x04 #define CTS_SPI_VALID_DISC 0x08 #define CTS_SPI_VALID_PPR_OPTIONS 0x10 u_int flags; #define CTS_SPI_FLAGS_DISC_ENB 0x01 u_int sync_period; /* Sync period. */ u_int sync_offset; /* Sync offset. */ u_int bus_width; /* Bus width. */ u_int ppr_options; /* Parallel protocol request. */ }; struct ccb_trans_settings_fc { u_int valid; /* Which field(s) to honor. */ #define CTS_FC_VALID_WWNN 0x8000 #define CTS_FC_VALID_WWPN 0x4000 #define CTS_FC_VALID_PORT 0x2000 #define CTS_FC_VALID_SPEED 0x1000 u_int64_t wwnn; /* World wide node name. */ u_int64_t wwpn; /* World wide port name. */ u_int32_t port; /* 24-bit port ID (if known). */ u_int32_t bitrate; /* Mbps. */ }; struct ccb_trans_settings_sas { u_int valid; /* Which field(s) to honor. */ #define CTS_SAS_VALID_SPEED 0x1000 u_int32_t bitrate; /* Mbps. */ }; struct ccb_trans_settings_ata { u_int valid; /* Which field(s) to honor. */ #define CTS_ATA_VALID_MODE 0x01 #define CTS_ATA_VALID_BYTECOUNT 0x02 #define CTS_ATA_VALID_ATAPI 0x20 int mode; /* Mode. */ u_int bytecount; /* PIO transaction length. */ u_int atapi; /* ATAPI CDB length. */ }; struct ccb_trans_settings_sata { u_int valid; /* Which field(s) to honor. */ #define CTS_SATA_VALID_MODE 0x01 #define CTS_SATA_VALID_BYTECOUNT 0x02 #define CTS_SATA_VALID_REVISION 0x04 #define CTS_SATA_VALID_PM 0x08 #define CTS_SATA_VALID_TAGS 0x10 #define CTS_SATA_VALID_ATAPI 0x20 #define CTS_SATA_VALID_CAPS 0x40 int mode; /* Legacy PATA mode. */ u_int bytecount; /* PIO transaction length. */ int revision; /* SATA revision. */ u_int pm_present; /* PM is present (XPT->SIM). */ u_int tags; /* Number of allowed tags. */ u_int atapi; /* ATAPI CDB length. */ u_int caps; /* Host and device SATA caps. */ #define CTS_SATA_CAPS_H 0x0000ffff #define CTS_SATA_CAPS_H_PMREQ 0x00000001 #define CTS_SATA_CAPS_H_APST 0x00000002 #define CTS_SATA_CAPS_H_DMAAA 0x00000010 #define CTS_SATA_CAPS_D 0xffff0000 #define CTS_SATA_CAPS_D_PMREQ 0x00010000 #define CTS_SATA_CAPS_D_APST 0x00020000 };
Here is an example XPT_GET_TRAN_SETTINGS
operation (taken from Example 14-1):
static void mfip_action(struct cam_sim *sim, union ccb *ccb) { struct mfip *sc; struct mfi_softc *mfi; sc = cam_sim_softc(sim); mfi = sc->mfi; mtx_assert(&mfi->mfi_io_lock, MA_OWNED); switch (ccb->ccb_h.func_code) { ... case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings_sas *sas; ccb->cts.protocol = PROTO_SCSI; ccb->cts.protocol_version = SCSI_REV_2; ccb->cts.transport = XPORT_SAS; ccb->cts.transport_version = 0; sas = &ccb->cts.xport_specific.sas; sas->valid &= ˜CTS_SAS_VALID_SPEED; sas->bitrate = 150000; ccb->ccb_h.status = CAM_REQ_CMP; break; } ... default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; }
Notice that the ccb_trans_settings
structure is provided by the CCB. Naturally, only the fields applicable to the HBA are filled in.
As you’d expect, XPT_SET_TRAN_SETTINGS
is the opposite of XPT_GET_TRAN_SETTINGS
. That is, XPT_SET_TRAN_SETTINGS
changes the current transfer settings based on a ccb_trans_settings
structure. Unsurprisingly, not all SIMs support this operation. For example:
static void mfip_action(struct cam_sim *sim, union ccb *ccb) { struct mfip *sc; struct mfi_softc *mfi; sc = cam_sim_softc(sim); mfi = sc->mfi; mtx_assert(&mfi->mfi_io_lock, MA_OWNED); switch (ccb->ccb_h.func_code) { ... case XPT_SET_TRAN_SETTINGS: ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; break; ... default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; }
This function states that XPT_SET_TRAN_SETTINGS
is not available. Note that a “proper” implementation is hardware specific and not covered in this book.
The XPT_SCSI_IO
constant denotes an I/O operation that issues a SCSI command to a device. The particulars of this SCSI command are stored in two structures: ccb_scsiio
and ccb_hdr
.
struct ccb_scsiio
is defined in <cam/cam_ccb.h>
like so:
struct ccb_scsiio { struct ccb_hdr ccb_h; /* Header information fields. */ union ccb *next_ccb; /* Next CCB to process. */ u_int8_t *req_map; /* Mapping information. */ u_int8_t *data_ptr; /* Data buffer or S/G list. */ u_int32_t dxfer_len; /* Length of data to transfer. */ /* Sense information (used if the command returns an error). */ struct scsi_sense_data sense_data; u_int8_t sense_len; /* Sense information length. */ u_int8_t cdb_len; /* SCSI command length. */ u_int16_t sglist_cnt; /* Number of S/G segments. */ u_int8_t scsi_status; /* SCSI status (returned by device). */ u_int8_t sense_resid; /* Residual sense information length. */ u_int32_t resid; /* Residual data length. */ cdb_t cdb_io; /* SCSI command. */ u_int8_t *msg_ptr; /* Message. */ u_int16_t msg_len; /* Message length. */ u_int8_t tag_action; /* Tag action? */ /* * tag_action should be the constant below to send a non-tagged * transaction or one of the constants in scsi_message.h. */ #define CAM_TAG_ACTION_NONE 0x00 u_int tag_id; /* Tag ID (from initiator). */ u_int init_id; /* Initiator ID. */ };
struct ccb_hdr
is also defined in <cam/cam_ccb.h>
, like so:
struct ccb_hdr { cam_pinfo pinfo; /* Priority scheduling. */ camq_entry xpt_links; /* Transport layer links. */ camq_entry sim_links; /* SIM layer links. */ camq_entry periph_links; /* Peripheral layer links. */ u_int32_t retry_count; /* Retry count. */ /* Pointer to peripheral module done routine. */ void (*cbfcnp)(struct cam_periph *, union ccb *); xpt_opcode func_code; /* I/O operation to perform. */ u_int32_t status; /* Completion status. */ struct cam_path *path; /* Path for this CCB. */ path_id_t path_id; /* Path ID for the request. */ target_id_t target_id; /* Target device ID. */ lun_id_t target_lun; /* Target logical unit number. */ u_int32_t flags; /* CCB flags. */ ccb_ppriv_area periph_priv; /* Private use by peripheral. */ ccb_spriv_area sim_priv; /* Private use by SIM. */ u_int32_t timeout; /* Timeout value. */ /* Deprecated. Don't use! */ struct callout_handle timeout_ch; };
struct ccb_hdr
should seem familiar—it’s used to return the completion status in every I/O operation.
The following is an example XPT_SCSI_IO
operation (taken from Example 14-1):
#define ccb_mfip_ptr sim_priv.entries[0].ptr ... static void mfip_action(struct cam_sim *sim, union ccb *ccb) { struct mfip *sc; struct mfi_softc *mfi; sc = cam_sim_softc(sim); mfi = sc->mfi; mtx_assert(&mfi->mfi_io_lock, MA_OWNED); switch (ccb->ccb_h.func_code) { ... case XPT_SCSI_IO: { struct ccb_hdr *ccb_h = &ccb->ccb_h; struct ccb_scsiio *csio = &ccb->csio; ccb_h->status = CAM_REQ_INPROG; if (csio->cdb_len > MFI_SCSI_MAX_CDB_LEN) { ccb_h->status = CAM_REQ_INVALID; break; } if ((ccb_h->flags & CAM_DIR_MASK) != CAM_DIR_NONE) { if (ccb_h->flags & CAM_DATA_PHYS) { ccb_h->status = CAM_REQ_INVALID; break; } if (ccb_h->flags & CAM_SCATTER_VALID) { ccb_h->status = CAM_REQ_INVALID; break; } } ccb_h->ccb_mfip_ptr = sc; TAILQ_INSERT_TAIL(&mfi->mfi_cam_ccbq, ccb_h, sim_links.tqe); mfi_startio(mfi); return; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; }
This operation begins by checking that the SCSI command length is acceptable. Then it determines whether the SCSI command uses physical addresses or scatter/gather segments to transfer data. If either is used, this operation exits (as it’s received invalid arguments). Then ccb_h->ccb_mfip_ptr
is set to the software context and mfi_startio
is called.
The mfi_startio
function is what actually issues the SCSI command.
Recall from mfip_start Function in mfip_poll Function that mfi_startio
calls mfip_start
to transform the SCSI command into a hardware-specific command.
static struct mfi_command * mfip_start(void *data) { union ccb *ccb = data; struct ccb_hdr *ccb_h = &ccb->ccb_h; struct ccb_scsiio *csio = &ccb->csio; struct mfip *sc; struct mfi_command *cm; struct mfi_pass_frame *pt; sc = ccb_h->ccb_mfip_ptr; if ((cm = mfi_dequeue_free(sc->mfi)) == NULL) return (NULL); pt = &cm->cm_frame->pass; pt->header.cmd = MFI_CMD_PD_SCSI_IO; pt->header.cmd_status = 0; pt->header.scsi_status = 0; pt->header.target_id = ccb_h->target_id; pt->header.lun_id = ccb_h->target_lun; pt->header.flags = 0; pt->header.timeout = 0; pt->header.data_len = csio->dxfer_len; pt->header.sense_len = MFI_SENSE_LEN; pt->header.cdb_len = csio->cdb_len; pt->sense_addr_lo = cm->cm_sense_busaddr; pt->sense_addr_hi = 0; if (ccb_h->flags & CAM_CDB_POINTER) bcopy(csio->cdb_io.cdb_ptr, &pt->cdb[0], csio->cdb_len); else bcopy(csio->cdb_io.cdb_bytes, &pt->cdb[0], csio->cdb_len); cm->cm_complete = mfip_done; cm->cm_private = ccb; cm->cm_sg = &pt->sgl; cm->cm_total_frame_size = MFI_PASS_FRAME_SIZE; cm->cm_data = csio->data_ptr; cm->cm_len = csio->dxfer_len; switch (ccb_h->flags & CAM_DIR_MASK) { case CAM_DIR_IN: cm->cm_flags = MFI_CMD_DATAIN; break; case CAM_DIR_OUT: cm->cm_flags = MFI_CMD_DATAOUT; break; case CAM_DIR_NONE: default: cm->cm_data = NULL; cm->cm_len = 0; cm->cm_flags = 0; break; } TAILQ_REMOVE(&sc->mfi->mfi_cam_ccbq, ccb_h, sim_links.tqe); return (cm); }
Notice that struct ccb_hdr
lists the target’s device ID and logical unit number. It also lists whether the SCSI command transfers data in, out, or nothing. Note that XPT_SCSI_IO
operations are seen from the SIM’s point of view. Therefore, “in” means from the device, and “out” means to the device.
The ccb_scsiio
structure maintains the data to transfer and its length. It also maintains the SCSI command (through a pointer or a buffer) and the command’s length.
Once more, the hardware-specific command constructed above is issued to the target device via mfi_startio
.
Recall that as soon as a device completes a hardware-specific command, it sends an interrupt, which causes the done routine (mfip_done
in this case) to execute.
static void mfip_done(struct mfi_command *cm) { union ccb *ccb = cm->cm_private; struct ccb_hdr *ccb_h = &ccb->ccb_h; struct ccb_scsiio *csio = &ccb->csio; struct mfip *sc; struct mfi_pass_frame *pt; sc = ccb_h->ccb_mfip_ptr; pt = &cm->cm_frame->pass; switch (pt->header.cmd_status) { case MFI_STAT_OK: { uint8_t command, device; ccb_h->status = CAM_REQ_CMP; csio->scsi_status = pt->header.scsi_status; if (ccb_h->flags & CAM_CDB_POINTER) command = ccb->csio.cdb_io.cdb_ptr[0]; else command = ccb->csio.cdb_io.cdb_bytes[0]; if (command == INQUIRY) { device = ccb->csio.data_ptr[0] & 0x1f; if ((device == T_DIRECT) || (device == T_PROCESSOR)) csio->data_ptr[0] = (device & 0xe0) | T_NODEVICE; } break; } case MFI_STAT_SCSI_DONE_WITH_ERROR: { int sense_len; ccb_h->status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; csio->scsi_status = pt->header.scsi_status; sense_len = min(pt->header.sense_len, sizeof(struct scsi_sense_data)); bzero(&csio->sense_data, sizeof(struct scsi_sense_data)); bcopy(&cm->cm_sense->data[0], &csio->sense_data, sense_len); break; } case MFI_STAT_DEVICE_NOT_FOUND: ccb_h->status = CAM_SEL_TIMEOUT; break; case MFI_STAT_SCSI_IO_FAILED: ccb_h->status = CAM_REQ_CMP_ERR; csio->scsi_status = pt->header.scsi_status; break; default: ccb_h->status = CAM_REQ_CMP_ERR; csio->scsi_status = pt->header.scsi_status; break; } mfi_release_command(cm); xpt_done(ccb); }
Notice that if the hardware-specific command returns an error, the error information (or sense data) is copied to the ccb_scsiio
structure’s sense_data
field.
At this point in the game, the unexplained parts of this function should be obvious.
The XPT_RESET_DEV
constant specifies a device reset operation. Unsurprisingly, XPT_RESET_DEV
is fairly hardware specific. Here is a simple XPT_RESET_DEV
operation (taken from bt.c):
The bt.c source file is part of the bt(4)
code base.
static void btaction(struct cam_sim *sim, union ccb *ccb) { struct bt_softc *bt; bt = (struct bt_softc *)cam_sim_softc(sim); switch (ccb->ccb_h.func_code) { case XPT_RESET_DEV: /* FALLTHROUGH */ case XPT_SCSI_IO: { ...
Given that a hardware-specific command must be issued to reset this device, XPT_RESET_DEV
simply cascades into XPT_SCSI_IO
.
While not shown here, it should be stressed that all operations conclude by appending their completion status to their CCB and then calling xpt_done(ccb)
.
3.143.5.217