root/usr/src/cmd/bhyve/common/pci_virtio_scsi.h
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2016 Jakub Klama <jceel@FreeBSD.org>.
 * Copyright (c) 2018 Marcelo Araujo <araujo@FreeBSD.org>.
 * Copyright (c) 2026 Hans Rosenfeld
 * 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
 *    in this position and unchanged.
 * 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.
 */

#ifndef _PCI_VIRTIO_SCSI_H_
#define _PCI_VIRTIO_SCSI_H_

#include "iov.h"

extern int pci_vtscsi_debug;

#define WPRINTF(msg, params...) PRINTLN("virtio-scsi: " msg, ##params)
#define DPRINTF(msg, params...) if (pci_vtscsi_debug) WPRINTF(msg, ##params)

/* Absolute limits given by the VirtIO SCSI spec */
#define VIRTIO_SCSI_MAX_CHANNEL 0
#define VIRTIO_SCSI_MAX_TARGET  255
#define VIRTIO_SCSI_MAX_LUN     16383
#define VIRTIO_SCSI_HDR_SEG     2
#define VIRTIO_SCSI_ADDL_Q      2

/* Features specific to VirtIO SCSI, none of which we currently support */
#define VIRTIO_SCSI_F_INOUT     (1 << 0)
#define VIRTIO_SCSI_F_HOTPLUG   (1 << 1)
#define VIRTIO_SCSI_F_CHANGE    (1 << 2)

/* Default limits which we set. All of these are configurable. */
#define VTSCSI_DEF_RINGSZ       64
#define VTSCSI_MIN_RINGSZ       4
#define VTSCSI_MAX_RINGSZ       4096

#define VTSCSI_DEF_THR_PER_Q    16
#define VTSCSI_MIN_THR_PER_Q    1
#define VTSCSI_MAX_THR_PER_Q    256

#define VTSCSI_DEF_MAXSEG       64
#define VTSCSI_MIN_MAXSEG       (VIRTIO_SCSI_HDR_SEG + 1)
#define VTSCSI_MAX_MAXSEG       \
    (4096 - VIRTIO_SCSI_HDR_SEG - SPLIT_IOV_ADDL_IOV)

#define VTSCSI_DEF_REQUESTQ     1
#define VTSCSI_MIN_REQUESTQ     1
#define VTSCSI_MAX_REQUESTQ     (32 - VIRTIO_SCSI_ADDL_Q)

/*
 * Device-specific config space registers
 *
 * The guest driver may try to modify cdb_size and sense_size by writing the
 * respective config space registers. Since we currently ignore all writes to
 * config space, these macros are essentially constant.
 */
#define VTSCSI_IN_HEADER_LEN(_sc)       \
        (sizeof(struct pci_vtscsi_req_cmd_rd) + _sc->vss_config.cdb_size)

#define VTSCSI_OUT_HEADER_LEN(_sc)      \
        (sizeof(struct pci_vtscsi_req_cmd_wr) + _sc->vss_config.sense_size)

struct pci_vtscsi_config {
        uint32_t                                num_queues;
        uint32_t                                seg_max;
        uint32_t                                max_sectors;
        uint32_t                                cmd_per_lun;
        uint32_t                                event_info_size;
        uint32_t                                sense_size;
        uint32_t                                cdb_size;
        uint16_t                                max_channel;
        uint16_t                                max_target;
        uint32_t                                max_lun;
} __attribute__((packed));


/*
 * I/O request state
 *
 * Each pci_vtscsi_queue has configurable number of pci_vtscsi_request
 * structures pre-allocated on vsq_free_requests. For each I/O request
 * coming in on the I/O virtqueue, the request queue handler takes
 * pci_vtscsi_request off vsq_free_requests, fills in the data from the
 * I/O virtqueue, puts it on vsq_requests, and signals vsq_cv.
 *
 * Each pci_vtscsi_queue will have a configurable number of worker threads,
 * which wait on vsq_cv. When signalled, they repeatedly take a single
 * pci_vtscsi_request off vsq_requests and hand it to the backend, which
 * processes it synchronously. After completion, the pci_vtscsi_request
 * is re-initialized and put back onto vsq_free_requests.
 *
 * The worker threads exit when vsq_cv is signalled after vsw_exiting was set.
 *
 * The I/O vectors for each request are kept in the preallocated iovec array
 * vsr_iov, and pointers to the respective header/data in/out portions are set
 * up to point into the array when the request is queued for processing.
 *
 * The number of iovecs preallocated for vsr_iov is derived from the configured
 * 'seg_max' parameter defined by the virtio spec:
 *   - 'seg_max' parameter specifies the maximum number of I/O data vectors
 *     we support in any request
 *   - we need 2 additional iovecs for the I/O headers (VIRTIO_SCSI_HDR_SEG)
 *   - we need another 2 additional iovecs for split_iov() (SPLIT_IOV_ADDL_IOV)
 *
 * The only time we explicitly need the full size of vsr_iov after preallocation
 * is during re-initialization after completing a request, and implicitly in the
 * calls to split_iov() the set up the pointers. In all other cases, we use only
 * 'seg_max' + VIRTIO_SCSI_HDR_SEG, and we advertise only 'seg_max' to the guest
 * in accordance to the virtio spec.
 */
STAILQ_HEAD(pci_vtscsi_req_queue, pci_vtscsi_request);

struct pci_vtscsi_queue {
        struct pci_vtscsi_softc                 *vsq_sc;
        struct vqueue_info                      *vsq_vq;
        pthread_mutex_t                         vsq_rmtx;
        pthread_mutex_t                         vsq_fmtx;
        pthread_mutex_t                         vsq_qmtx;
        pthread_cond_t                          vsq_cv;
        struct pci_vtscsi_req_queue             vsq_requests;
        struct pci_vtscsi_req_queue             vsq_free_requests;
        LIST_HEAD(, pci_vtscsi_worker)          vsq_workers;
};

struct pci_vtscsi_worker {
        struct pci_vtscsi_queue                 *vsw_queue;
        pthread_t                               vsw_thread;
        bool                                    vsw_exiting;
        LIST_ENTRY(pci_vtscsi_worker)           vsw_link;
};

struct pci_vtscsi_request {
        struct pci_vtscsi_queue                 *vsr_queue;
        struct iovec                            *vsr_iov;
        struct iovec                            *vsr_iov_in;
        struct iovec                            *vsr_iov_out;
        struct iovec                            *vsr_data_iov_in;
        struct iovec                            *vsr_data_iov_out;
        struct pci_vtscsi_req_cmd_rd            *vsr_cmd_rd;
        struct pci_vtscsi_req_cmd_wr            *vsr_cmd_wr;
        void                                    *vsr_backend;
        size_t                                  vsr_niov_in;
        size_t                                  vsr_niov_out;
        size_t                                  vsr_data_niov_in;
        size_t                                  vsr_data_niov_out;
        uint32_t                                vsr_idx;
        STAILQ_ENTRY(pci_vtscsi_request)        vsr_link;
};

/*
 * Per-target state.
 */
struct pci_vtscsi_target {
        uint8_t                                 vst_target;
        int                                     vst_fd;
        int                                     vst_max_sectors;
};

/*
 * Per-device softc
 */
struct pci_vtscsi_softc {
        struct virtio_softc                     vss_vs;
        struct virtio_consts                    vss_vi_consts;
        struct vqueue_info                      *vss_vq;
        struct pci_vtscsi_queue                 *vss_queues;
        pthread_mutex_t                         vss_mtx;
        uint32_t                                vss_features;
        size_t                                  vss_num_target;
        uint32_t                                vss_ctl_ringsz;
        uint32_t                                vss_evt_ringsz;
        uint32_t                                vss_req_ringsz;
        uint32_t                                vss_thr_per_q;
        struct pci_vtscsi_config                vss_default_config;
        struct pci_vtscsi_config                vss_config;
        struct pci_vtscsi_target                *vss_targets;
        struct pci_vtscsi_backend               *vss_backend;
};

/*
 * VirtIO-SCSI Task Management Function control requests
 */
#define VIRTIO_SCSI_T_TMF                       0
#define VIRTIO_SCSI_T_TMF_ABORT_TASK            0
#define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET        1
#define VIRTIO_SCSI_T_TMF_CLEAR_ACA             2
#define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET        3
#define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET       4
#define VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET    5
#define VIRTIO_SCSI_T_TMF_QUERY_TASK            6
#define VIRTIO_SCSI_T_TMF_QUERY_TASK_SET        7

#define VIRTIO_SCSI_T_TMF_MAX_FUNC              VIRTIO_SCSI_T_TMF_QUERY_TASK_SET

/* command-specific response values */
#define VIRTIO_SCSI_S_FUNCTION_COMPLETE         0
#define VIRTIO_SCSI_S_FUNCTION_SUCCEEDED        10
#define VIRTIO_SCSI_S_FUNCTION_REJECTED         11

struct pci_vtscsi_ctrl_tmf {
        const uint32_t  type;
        const uint32_t  subtype;
        const uint8_t   lun[8];
        const uint64_t  id;
        uint8_t         response;
} __attribute__((packed));


/*
 * VirtIO-SCSI Asynchronous Notification control requests
 */
#define VIRTIO_SCSI_T_AN_QUERY                  1
#define VIRTIO_SCSI_EVT_ASYNC_OPERATIONAL_CHANGE 2
#define VIRTIO_SCSI_EVT_ASYNC_POWER_MGMT        4
#define VIRTIO_SCSI_EVT_ASYNC_EXTERNAL_REQUEST  8
#define VIRTIO_SCSI_EVT_ASYNC_MEDIA_CHANGE      16
#define VIRTIO_SCSI_EVT_ASYNC_MULTI_HOST        32
#define VIRTIO_SCSI_EVT_ASYNC_DEVICE_BUSY       64

struct pci_vtscsi_ctrl_an {
        const uint32_t  type;
        const uint8_t   lun[8];
        const uint32_t  event_requested;
        uint32_t        event_actual;
        uint8_t         response;
} __attribute__((packed));

/* command-specific response values */
#define VIRTIO_SCSI_S_OK                        0
#define VIRTIO_SCSI_S_OVERRUN                   1
#define VIRTIO_SCSI_S_ABORTED                   2
#define VIRTIO_SCSI_S_BAD_TARGET                3
#define VIRTIO_SCSI_S_RESET                     4
#define VIRTIO_SCSI_S_BUSY                      5
#define VIRTIO_SCSI_S_TRANSPORT_FAILURE         6
#define VIRTIO_SCSI_S_TARGET_FAILURE            7
#define VIRTIO_SCSI_S_NEXUS_FAILURE             8
#define VIRTIO_SCSI_S_FAILURE                   9
#define VIRTIO_SCSI_S_INCORRECT_LUN             12

struct pci_vtscsi_event {
        uint32_t        event;
        uint8_t         lun[8];
        uint32_t        reason;
} __attribute__((packed));

/*
 * VirtIO-SCSI I/O requests
 */
struct pci_vtscsi_req_cmd_rd {
        const uint8_t   lun[8];
        const uint64_t  id;
        const uint8_t   task_attr;
        const uint8_t   prio;
        const uint8_t   crn;
        const uint8_t   cdb[];
} __attribute__((packed));

/* task_attr */
#define VIRTIO_SCSI_S_SIMPLE                    0
#define VIRTIO_SCSI_S_ORDERED                   1
#define VIRTIO_SCSI_S_HEAD                      2
#define VIRTIO_SCSI_S_ACA                       3

struct pci_vtscsi_req_cmd_wr {
        uint32_t        sense_len;
        uint32_t        residual;
        uint16_t        status_qualifier;
        uint8_t         status;
        uint8_t         response;
        uint8_t         sense[];
} __attribute__((packed));

/*
 * Backend interface
 */
struct pci_vtscsi_backend {
        const char      *vsb_name;
        int             (*vsb_init)(struct pci_vtscsi_softc *,
                            struct pci_vtscsi_backend *, nvlist_t *);
        int             (*vsb_open)(struct pci_vtscsi_softc *, const char *,
                            long);
        void            (*vsb_reset)(struct pci_vtscsi_softc *);

        void*           (*vsb_req_alloc)(struct pci_vtscsi_softc *);
        void            (*vsb_req_clear)(void *);
        void            (*vsb_req_free)(void *);

        void            (*vsb_tmf_hdl)(struct pci_vtscsi_softc *, int,
                            struct pci_vtscsi_ctrl_tmf *);
        void            (*vsb_an_hdl)(struct pci_vtscsi_softc *, int,
                            struct pci_vtscsi_ctrl_an *);
        int             (*vsb_req_hdl)(struct pci_vtscsi_softc *, int,
                            struct pci_vtscsi_request *);
};
#define PCI_VTSCSI_BACKEND_SET(x)       DATA_SET(pci_vtscsi_backend_set, x)

/*
 * LUN address parsing
 *
 * The LUN address consists of 8 bytes. While the spec describes this as 0x01,
 * followed by the target byte, followed by a "single-level LUN structure",
 * this is actually the same as a hierarchical LUN address as defined by SAM-5,
 * consisting of four levels of addressing, where in each level the two MSB of
 * byte 0 select the address mode used in the remaining bits and bytes.
 *
 *
 * Only the first two levels are acutally used by virtio-scsi:
 *
 * Level 1: 0x01, 0xTT: Peripheral Device Addressing: Bus 1, Target 0-255
 * Level 2: 0xLL, 0xLL: Peripheral Device Addressing: Bus MBZ, LUN 0-255
 *                  or: Flat Space Addressing: LUN (0-16383)
 * Level 3 and 4: not used, MBZ
 *
 *
 * Alternatively, the first level may contain an extended LUN address to select
 * the REPORT_LUNS well-known logical unit:
 *
 * Level 1: 0xC1, 0x01: Extended LUN Adressing, Well-Known LUN 1 (REPORT_LUNS)
 * Level 2, 3, and 4: not used, MBZ
 *
 * The virtio spec says that we SHOULD implement the REPORT_LUNS well-known
 * logical unit  but we currently don't.
 *
 * According to the virtio spec, these are the only LUNS address formats to be
 * used with virtio-scsi.
 */

/*
 * Check that the given LUN address conforms to the virtio spec, does not
 * address an unknown target, and especially does not address the REPORT_LUNS
 * well-known logical unit.
 */
static inline bool
pci_vtscsi_check_lun(struct pci_vtscsi_softc *sc, const uint8_t *lun)
{
        if (lun[0] == 0xC1)
                return (false);

        if (lun[0] != 0x01)
                return (false);

        if (lun[1] >= sc->vss_num_target)
                return (false);

        if (lun[1] != sc->vss_targets[lun[1]].vst_target)
                return (false);

        if (sc->vss_targets[lun[1]].vst_fd < 0)
                return (false);

        if (lun[2] != 0x00 && (lun[2] & 0xc0) != 0x40)
                return (false);

        if (lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0)
                return (false);

        return (true);
}

/*
 * Get the target id from a LUN address.
 *
 * Every code path using this function must have called pci_vtscsi_check_lun()
 * before to make sure the LUN address is valid.
 */
static inline uint8_t
pci_vtscsi_get_target(struct pci_vtscsi_softc *sc, const uint8_t *lun)
{
        assert(lun[0] == 0x01);
        assert(lun[1] < sc->vss_num_target);
        assert(lun[1] == sc->vss_targets[lun[1]].vst_target);
        assert(sc->vss_targets[lun[1]].vst_fd >= 0);
        assert(lun[2] == 0x00 || (lun[2] & 0xc0) == 0x40);

        return (lun[1]);
}

/*
 * Get the LUN id from a LUN address.
 *
 * Every code path using this function must have called pci_vtscsi_check_lun()
 * before to make sure the LUN address is valid.
 */
static inline uint16_t
pci_vtscsi_get_lun(struct pci_vtscsi_softc *sc, const uint8_t *lun)
{
        assert(lun[0] == 0x01);
        assert(lun[1] < sc->vss_num_target);
        assert(lun[1] == sc->vss_targets[lun[1]].vst_target);
        assert(sc->vss_targets[lun[1]].vst_fd >= 0);
        assert(lun[2] == 0x00 || (lun[2] & 0xc0) == 0x40);

        return (((lun[2] << 8) | lun[3]) & 0x3fff);
}

#endif  /* _PCI_VIRTIO_SCSI_H_ */