Action Routines

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.

Note

For the complete list of constants/operations, see the xpt_opcode enumeration defined in the <cam/cam_ccb.h> header.

XPT_PATH_INQ

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.

XPT_RESET_BUS

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.

XPT_GET_TRAN_SETTINGS

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.

XPT_SET_TRAN_SETTINGS

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.

XPT_SCSI_IO

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.

Note

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.

Note

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.

XPT_RESET_DEV

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):

Note

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.5.217