Pawel Wojcik's Weblog Pawel Wojcik's Weblog

Wednesday Jan 17, 2007

SATA framework has to interface with Solaris scsi layer and sd target driver on one end (see the chart in the first part of the overview) and with SATA HBA driver on the other end. Let's first consider interface with SATA HBA driver. The interface between sata module and sata hba drivers is defined in usr/src/uts/common/sys/sata/sata_hba.h header file. SATA framework concerns itself with the generic operations, common for all SATA HBA. But it have to consider HBA capabilities, when it generate I/O and control requests. Also, it has to export some of these capabilities to the system, particularly DMA capabilities. SATA HBA driver exports its capabilities and entry points via sata_hba_tran structure, analogous to scsi_hba_tran structure. This structure contains:

  • Pointer to the SATA controller hardware instance node
  • Controller DMA attributes used for I/O data transfer (see man page for ddi_dma_attr)
  • Features supported by the controller, such as command queuing, native command queuing, ATAPI, Port Multipliers, etc. One-bit flags are used to indicate that specific feature is supported.
  • Number of sata device ports present/usable on the controller (SATA devices may be connected to those ports).
  • Queue depth supported by the controller
  • Pointers to the HBA driver functions used to:
    • Probe ports, to find the state of the port and presence/absence of a device.
    • Transport sata packet to the HBA driver, specifying some SATA device operation.
    • Abort sata packet (or packets) execution.
    • Reset sata device, device port or entire controller.
    • Initiate self-test.
    • Activate/deactivate sata device port.
Here is the complete structure definition (refer to usr/src/uts/common/sys/sata/sata_hba.h header file for details):
struct sata_hba_tran {
        int             sata_tran_hba_rev;      /* version */
        dev_info_t      *sata_tran_hba_dip;     /* Controler dev info */
        ddi_dma_attr_t  *sata_tran_hba_dma_attr; /* DMA attributes */
        int             sata_tran_hba_num_cports; /* Num of HBA device ports */
        uint16_t        sata_tran_hba_features_support; /* HBA features */
        uint16_t        sata_tran_hba_qdepth;   /* HBA-supported queue depth */

        int             (*sata_tran_probe_port)(dev_info_t *, sata_device_t *);
        int             (*sata_tran_start)(dev_info_t *, sata_pkt_t *);
        int             (*sata_tran_abort)(dev_info_t *, sata_pkt_t *, int);
        int             (*sata_tran_reset_dport)(dev_info_t *,
                                        sata_device_t *);
        int             (*sata_tran_selftest)(dev_info_t *, sata_device_t *);

                                                /* Hotplug vector */
        struct sata_tran_hotplug_ops *sata_tran_hotplug_ops;

                                                /* Power mgt vector */
        struct sata_tran_pwrmgt_ops *sata_tran_pwrmgt_ops;

        int             (*sata_tran_ioctl)(dev_info_t *, int, intptr_t);
};

When SATA framework calls one of the interface function supplied by the SATA HBA driver, it passes as one of the arguments the pointer to the device info that is specific for a given instance of the controller hardware. SATA HBA driver is using it to identify on which controller the operation is to be executed. It is important to note that by refering to the controller instance we refer to an individual hardware node in a device tree, created when the system is configured during the boot process. Such node does not necessarily corresponds to a physical controller chip, which may contain a number of independent controllers.
Specifying a controller instance is obvioulsy not enough - an additional information is needed to identify to which device or a device port apply an operation. This info is passed either directly as a pointer to sata_device addressing structure, or indirectly though the sata packet (sata_pkt) structure which embedds sata_device structure. Specifying sata port is simple - sata ports are numbered from 0 to max number of available data ports on the controller. But we also need to consider that a port multiplier may be connected to the controller's port, so in addition to the controller's port, the device port beyond the port multiplier has to be specified. To indicate when this secondary information is valid, the type of addressing (adress qualifier) is also included in the sata_address structure embedded in sata_device.

struct sata_address {
        uint8_t         cport;          /* Controller's SATA port number */
        uint8_t         pmport;         /* Port Multiplier SATA port number */
        uint8_t         qual;           /* Address Qualifier flags */
        uint8_t         pad;            /* Reserved */
};

The sata_device structure (see sata_hba.h file for definition) is used not only to address specific sata device or sata port, but also to inform the SATA framework about the state of the port (including content of standard port registers) or device and about an attached device type. The state of the port has to be always returned to SATA module by SATA HBA driver, regardless of the type of the operation initiated by SATA module.

The forth imnportant (may be the most important) interface structure is sata packet structure (sata_pkt). Sata packet carries description of the operation that SATA framework is requesting from SATA HBA driver. When the operation is completed, the satus of the operation is returned to SATA module in the same structure.

struct sata_pkt {
        int             satapkt_rev;            /* version */
        struct sata_device satapkt_device;      /* Device address/type */

                                                /* HBA driver private data */
        void            *satapkt_hba_driver_private;

                                                /* SATA framework priv data */
        void            *satapkt_framework_private;

                                                /* Rqsted mode of operation */
        uint32_t        satapkt_op_mode;

        struct sata_cmd satapkt_cmd;            /* composite sata command */
        int             satapkt_time;           /* time allotted to command */
        void            (*satapkt_comp)(struct sata_pkt *); /* callback */
        int             satapkt_reason;         /* completion reason */
};
The sata packet contains embedded sata_device addressing structure, mode of operation, SATA command to be passed to a sata device, a time allowed for the execution, and a status of the execution.
Two pointers are embedded in sata packet - one (satapkt_framework_private) is used to link the packet with SATA module internal structures and original scsi packet (if any). This pointer should not be ever modified by the SATA HBA driver.
The other embedded pointer could be used by the SATA HBA driver to link the packet with its own internal data structures related to the addressed device and the packet execution.
The pointer to a callback function allows queuing the sata packet in SATA HBA driver for delayed execution and an immediate return to the SATA module. When the operation specified by the sata packet completes, the callback function is called, with the sata packet passed as the argument.
The status of the operation is returned two ways. The completion reason specifies general reason for the completion, i.e. success, rejection due to a full packet queue, unsupported operation or unspecified driver busy condition, or a failure due to some serial link problems or error returned by the device (see sata_hba.h for details). Some additional information about a failure reason are returned either in embedded sata_device structure (port or serial link problem) or in sata command structure (satapkt_cmd) when an error was reported by the actual sata device.

The last, but not the least important structure is sata_cmd structure, embedded in the sata packet. Sata_cmd is set-up by the SATA module and specifies the content of the task file registers. Task file registers compose a set of registers used to transfer information to a sata device. In old parallel ATA word these registers were directly accessible by the IDE controller. In SATA, their content is transfered to/from the sata device via packets send through a serial link. SATA specification retained the concept of the task file registers for a software compatibility reason. It uses the ATA/ATAPI command formats, and those commands are based on task file registers interface
So, when SATA module wants to execute specific SATA command, it sets up the content of every relevant register (for a given command) in the task file register. The SATA HBA driver does not have to interpret anything, just takes prepared register content and passes it, in a controller-specific fashion, to a sata device (sometimes stuffing it into "shadow" task file registers, sometimes building complete Frame Information Structure - a packet being transfered via serial link).

struct sata_cmd {
        int             satacmd_rev;            /* version */
        struct buf      *satacmd_bp;            /* ptr to buffer structure */
        struct sata_cmd_flags  satacmd_flags;
        uint8_t         satacmd_addr_type;      /* addr type: LBA28, LBA48 */
        uint8_t         satacmd_features_reg_ext; /* features reg extended */
        uint8_t         satacmd_sec_count_msb;  /* sector count MSB (LBA48) */
        uint8_t         satacmd_lba_low_msb;    /* LBA Low MSB (LBA48) */
        uint8_t         satacmd_lba_mid_msb;    /* LBA Mid MSB (LBA48) */
        uint8_t         satacmd_lba_high_msb;   /* LBA High MSB (LBA48) */
        uint8_t         satacmd_sec_count_lsb;  /* sector count LSB */
        uint8_t         satacmd_lba_low_lsb;    /* LBA Low LSB */
        uint8_t         satacmd_lba_mid_lsb;    /* LBA Mid LSB */
        uint8_t         satacmd_lba_high_lsb;   /* LBA High LSB */
        uint8_t         satacmd_device_reg;     /* ATA dev reg & LBA28 MSB */
        uint8_t         satacmd_cmd_reg;        /* ata command code */
        uint8_t         satacmd_features_reg;   /* ATA features register */
        uint8_t         satacmd_status_reg;     /* ATA status register */
        uint8_t         satacmd_error_reg;      /* ATA error register  */
        uint8_t         satacmd_acdb_len;       /* ATAPI cdb length */
              ..........
        int             satacmd_num_dma_cookies; /* number of dma cookies */
                                                /* ptr to dma cookie list */
        ddi_dma_cookie_t *satacmd_dma_cookie_list;
};
Other interesting part of the sata_cmd structure is dma cookie list. This list contains adresses and lengths of the regions in the system physical memory, and it is used to set-up DMA controller's scatter-gather list. SATA module makes sure that each specified "chunk" of memory would match the capability of the HBA's DMA controller with respect to an address range and a transfer size (Remember DMA attributes that SATA HBA passed in sata_hab_tran structure? They are used for this transfer "limiting" purpose). Once a sata device executes the command, it returns the status of the operation in the status "register" of task file registes set . If the operation was successfull, SATA HBA driver just have to return a general 'completion reason'. If the operation failed, SATA device returns error information (command specific) in various registers. SATA HBA driver has to collect this information from the device (from "shadow" registers or from register FIS) and store it back in the sata_cmd structure. When the sata_pkt is returned to SATA module, the 'completion reason' in conjunction with additional information in sata_cmd let SATA module to determine the cause of the failure and the information that may need to be passed to other system software.

TO BE CONTINUED