Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_virtio_scsi.c
| Show First 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | |||||
| #define VTSCSI_IN_HEADER_LEN(_sc) \ | #define VTSCSI_IN_HEADER_LEN(_sc) \ | ||||
| (sizeof(struct pci_vtscsi_req_cmd_rd) + _sc->vss_config.cdb_size) | (sizeof(struct pci_vtscsi_req_cmd_rd) + _sc->vss_config.cdb_size) | ||||
| #define VTSCSI_OUT_HEADER_LEN(_sc) \ | #define VTSCSI_OUT_HEADER_LEN(_sc) \ | ||||
| (sizeof(struct pci_vtscsi_req_cmd_wr) + _sc->vss_config.sense_size) | (sizeof(struct pci_vtscsi_req_cmd_wr) + _sc->vss_config.sense_size) | ||||
| #define VIRTIO_SCSI_MAX_CHANNEL 0 | #define VIRTIO_SCSI_MAX_CHANNEL 0 | ||||
| #define VIRTIO_SCSI_MAX_TARGET 0 | #define VIRTIO_SCSI_MAX_TARGET 255 | ||||
| #define VIRTIO_SCSI_MAX_LUN 16383 | #define VIRTIO_SCSI_MAX_LUN 16383 | ||||
| #define VIRTIO_SCSI_F_INOUT (1 << 0) | #define VIRTIO_SCSI_F_INOUT (1 << 0) | ||||
| #define VIRTIO_SCSI_F_HOTPLUG (1 << 1) | #define VIRTIO_SCSI_F_HOTPLUG (1 << 1) | ||||
| #define VIRTIO_SCSI_F_CHANGE (1 << 2) | #define VIRTIO_SCSI_F_CHANGE (1 << 2) | ||||
| static int pci_vtscsi_debug = 0; | static int pci_vtscsi_debug = 0; | ||||
| #define WPRINTF(msg, params...) PRINTLN("virtio-scsi: " msg, ##params) | #define WPRINTF(msg, params...) PRINTLN("virtio-scsi: " msg, ##params) | ||||
| ▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | struct pci_vtscsi_request { | ||||
| int vsr_niov_in; | int vsr_niov_in; | ||||
| int vsr_niov_out; | int vsr_niov_out; | ||||
| int vsr_data_niov_in; | int vsr_data_niov_in; | ||||
| int vsr_data_niov_out; | int vsr_data_niov_out; | ||||
| uint32_t vsr_idx; | uint32_t vsr_idx; | ||||
| STAILQ_ENTRY(pci_vtscsi_request) vsr_link; | STAILQ_ENTRY(pci_vtscsi_request) vsr_link; | ||||
| }; | }; | ||||
| struct pci_vtscsi_target { | |||||
| int vst_target; | |||||
| int vst_fd; | |||||
| int vst_max_sectors; | |||||
| }; | |||||
markj: The spacing here looks off. | |||||
| /* | /* | ||||
| * Per-device softc | * Per-device softc | ||||
| */ | */ | ||||
| struct pci_vtscsi_softc { | struct pci_vtscsi_softc { | ||||
| struct virtio_softc vss_vs; | struct virtio_softc vss_vs; | ||||
| struct vqueue_info vss_vq[VTSCSI_MAXQ]; | struct vqueue_info vss_vq[VTSCSI_MAXQ]; | ||||
| struct pci_vtscsi_queue vss_queues[VTSCSI_REQUESTQ]; | struct pci_vtscsi_queue vss_queues[VTSCSI_REQUESTQ]; | ||||
| pthread_mutex_t vss_mtx; | pthread_mutex_t vss_mtx; | ||||
| int vss_iid; | int vss_iid; | ||||
| int vss_ctl_fd; | int vss_ctl_fd; | ||||
| uint32_t vss_features; | uint32_t vss_features; | ||||
| int vss_num_target; | |||||
| struct pci_vtscsi_config vss_config; | struct pci_vtscsi_config vss_config; | ||||
| struct pci_vtscsi_target *vss_targets; | |||||
| }; | }; | ||||
| #define VIRTIO_SCSI_T_TMF 0 | #define VIRTIO_SCSI_T_TMF 0 | ||||
| #define VIRTIO_SCSI_T_TMF_ABORT_TASK 0 | #define VIRTIO_SCSI_T_TMF_ABORT_TASK 0 | ||||
| #define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET 1 | #define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET 1 | ||||
| #define VIRTIO_SCSI_T_TMF_CLEAR_ACA 2 | #define VIRTIO_SCSI_T_TMF_CLEAR_ACA 2 | ||||
| #define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET 3 | #define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET 3 | ||||
| #define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET 4 | #define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET 4 | ||||
| ▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | |||||
| } __attribute__((packed)); | } __attribute__((packed)); | ||||
| static void *pci_vtscsi_proc(void *); | static void *pci_vtscsi_proc(void *); | ||||
| static void pci_vtscsi_reset(void *); | static void pci_vtscsi_reset(void *); | ||||
| static void pci_vtscsi_neg_features(void *, uint64_t); | static void pci_vtscsi_neg_features(void *, uint64_t); | ||||
| static int pci_vtscsi_cfgread(void *, int, int, uint32_t *); | static int pci_vtscsi_cfgread(void *, int, int, uint32_t *); | ||||
| static int pci_vtscsi_cfgwrite(void *, int, int, uint32_t); | static int pci_vtscsi_cfgwrite(void *, int, int, uint32_t); | ||||
| static inline bool pci_vtscsi_check_lun(const uint8_t *); | static inline bool pci_vtscsi_check_lun(struct pci_vtscsi_softc *, | ||||
| static inline int pci_vtscsi_get_lun(const uint8_t *); | const uint8_t *); | ||||
| static inline int pci_vtscsi_get_target(struct pci_vtscsi_softc *, | |||||
| const uint8_t *); | |||||
| static inline int pci_vtscsi_get_lun(struct pci_vtscsi_softc *, | |||||
| const uint8_t *); | |||||
| static void pci_vtscsi_control_handle(struct pci_vtscsi_softc *, void *, size_t); | static void pci_vtscsi_control_handle(struct pci_vtscsi_softc *, void *, size_t); | ||||
| static void pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *, | static void pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *, | ||||
| struct pci_vtscsi_ctrl_tmf *); | struct pci_vtscsi_ctrl_tmf *); | ||||
| static void pci_vtscsi_an_handle(struct pci_vtscsi_softc *, | static void pci_vtscsi_an_handle(struct pci_vtscsi_softc *, | ||||
| struct pci_vtscsi_ctrl_an *); | struct pci_vtscsi_ctrl_an *); | ||||
| static struct pci_vtscsi_request *pci_vtscsi_alloc_request( | static struct pci_vtscsi_request *pci_vtscsi_alloc_request( | ||||
| struct pci_vtscsi_softc *); | struct pci_vtscsi_softc *); | ||||
| static void pci_vtscsi_free_request(struct pci_vtscsi_request *); | static void pci_vtscsi_free_request(struct pci_vtscsi_request *); | ||||
| static struct pci_vtscsi_request *pci_vtscsi_get_request( | static struct pci_vtscsi_request *pci_vtscsi_get_request( | ||||
| struct pci_vtscsi_req_queue *); | struct pci_vtscsi_req_queue *); | ||||
| static void pci_vtscsi_put_request(struct pci_vtscsi_req_queue *, | static void pci_vtscsi_put_request(struct pci_vtscsi_req_queue *, | ||||
| struct pci_vtscsi_request *); | struct pci_vtscsi_request *); | ||||
| static void pci_vtscsi_queue_request(struct pci_vtscsi_softc *, | static void pci_vtscsi_queue_request(struct pci_vtscsi_softc *, | ||||
| struct vqueue_info *); | struct vqueue_info *); | ||||
| static void pci_vtscsi_return_request(struct pci_vtscsi_queue *, | static void pci_vtscsi_return_request(struct pci_vtscsi_queue *, | ||||
| struct pci_vtscsi_request *, int); | struct pci_vtscsi_request *, int); | ||||
| static int pci_vtscsi_request_handle(struct pci_vtscsi_softc *, | static int pci_vtscsi_request_handle(struct pci_vtscsi_softc *, int, | ||||
| struct pci_vtscsi_request *); | struct pci_vtscsi_request *); | ||||
| static void pci_vtscsi_controlq_notify(void *, struct vqueue_info *); | static void pci_vtscsi_controlq_notify(void *, struct vqueue_info *); | ||||
| static void pci_vtscsi_eventq_notify(void *, struct vqueue_info *); | static void pci_vtscsi_eventq_notify(void *, struct vqueue_info *); | ||||
| static void pci_vtscsi_requestq_notify(void *, struct vqueue_info *); | static void pci_vtscsi_requestq_notify(void *, struct vqueue_info *); | ||||
| static int pci_vtscsi_add_target_config(nvlist_t *, const char *, int); | |||||
| static int pci_vtscsi_init_queue(struct pci_vtscsi_softc *, | static int pci_vtscsi_init_queue(struct pci_vtscsi_softc *, | ||||
| struct pci_vtscsi_queue *, int); | struct pci_vtscsi_queue *, int); | ||||
| static int pci_vtscsi_init(struct pci_devinst *, nvlist_t *); | static int pci_vtscsi_init(struct pci_devinst *, nvlist_t *); | ||||
| static struct virtio_consts vtscsi_vi_consts = { | static struct virtio_consts vtscsi_vi_consts = { | ||||
| .vc_name = "vtscsi", | .vc_name = "vtscsi", | ||||
| .vc_nvq = VTSCSI_MAXQ, | .vc_nvq = VTSCSI_MAXQ, | ||||
| .vc_cfgsize = sizeof(struct pci_vtscsi_config), | .vc_cfgsize = sizeof(struct pci_vtscsi_config), | ||||
| .vc_reset = pci_vtscsi_reset, | .vc_reset = pci_vtscsi_reset, | ||||
| .vc_cfgread = pci_vtscsi_cfgread, | .vc_cfgread = pci_vtscsi_cfgread, | ||||
| .vc_cfgwrite = pci_vtscsi_cfgwrite, | .vc_cfgwrite = pci_vtscsi_cfgwrite, | ||||
| .vc_apply_features = pci_vtscsi_neg_features, | .vc_apply_features = pci_vtscsi_neg_features, | ||||
| .vc_hv_caps = VIRTIO_RING_F_INDIRECT_DESC, | .vc_hv_caps = VIRTIO_RING_F_INDIRECT_DESC, | ||||
| }; | }; | ||||
| static void * | static void * | ||||
| pci_vtscsi_proc(void *arg) | pci_vtscsi_proc(void *arg) | ||||
| { | { | ||||
| struct pci_vtscsi_worker *worker = (struct pci_vtscsi_worker *)arg; | struct pci_vtscsi_worker *worker = (struct pci_vtscsi_worker *)arg; | ||||
| struct pci_vtscsi_queue *q = worker->vsw_queue; | struct pci_vtscsi_queue *q = worker->vsw_queue; | ||||
| struct pci_vtscsi_softc *sc = q->vsq_sc; | struct pci_vtscsi_softc *sc = q->vsq_sc; | ||||
| int iolen; | |||||
| for (;;) { | for (;;) { | ||||
| struct pci_vtscsi_request *req; | struct pci_vtscsi_request *req; | ||||
| int target; | |||||
| int iolen; | |||||
| int fd; | |||||
| pthread_mutex_lock(&q->vsq_rmtx); | pthread_mutex_lock(&q->vsq_rmtx); | ||||
| while (STAILQ_EMPTY(&q->vsq_requests) && !worker->vsw_exiting) | while (STAILQ_EMPTY(&q->vsq_requests) && !worker->vsw_exiting) | ||||
| pthread_cond_wait(&q->vsq_cv, &q->vsq_rmtx); | pthread_cond_wait(&q->vsq_cv, &q->vsq_rmtx); | ||||
| if (worker->vsw_exiting) { | if (worker->vsw_exiting) { | ||||
| pthread_mutex_unlock(&q->vsq_rmtx); | pthread_mutex_unlock(&q->vsq_rmtx); | ||||
| return (NULL); | return (NULL); | ||||
| } | } | ||||
| req = pci_vtscsi_get_request(&q->vsq_requests); | req = pci_vtscsi_get_request(&q->vsq_requests); | ||||
| pthread_mutex_unlock(&q->vsq_rmtx); | pthread_mutex_unlock(&q->vsq_rmtx); | ||||
| DPRINTF("I/O request lun %d, data_niov_in %d, data_niov_out %d", | target = pci_vtscsi_get_target(sc, req->vsr_cmd_rd->lun); | ||||
| pci_vtscsi_get_lun(req->vsr_cmd_rd->lun), | fd = sc->vss_targets[target].vst_fd; | ||||
| DPRINTF("I/O request tgt %d, lun %d, data_niov_in %d, " | |||||
| "data_niov_out %d", target, | |||||
| pci_vtscsi_get_lun(sc, req->vsr_cmd_rd->lun), | |||||
| req->vsr_data_niov_in, req->vsr_data_niov_out); | req->vsr_data_niov_in, req->vsr_data_niov_out); | ||||
| iolen = pci_vtscsi_request_handle(sc, req); | iolen = pci_vtscsi_request_handle(sc, fd, req); | ||||
| pci_vtscsi_return_request(q, req, iolen); | pci_vtscsi_return_request(q, req, iolen); | ||||
| } | } | ||||
| } | } | ||||
| static void | static void | ||||
| pci_vtscsi_reset(void *vsc) | pci_vtscsi_reset(void *vsc) | ||||
| { | { | ||||
| Show All 11 Lines | sc->vss_config = (struct pci_vtscsi_config){ | ||||
| .seg_max = VTSCSI_MAXSEG - 2, | .seg_max = VTSCSI_MAXSEG - 2, | ||||
| /* CTL apparently doesn't have a limit here */ | /* CTL apparently doesn't have a limit here */ | ||||
| .max_sectors = INT32_MAX, | .max_sectors = INT32_MAX, | ||||
| .cmd_per_lun = 1, | .cmd_per_lun = 1, | ||||
| .event_info_size = sizeof(struct pci_vtscsi_event), | .event_info_size = sizeof(struct pci_vtscsi_event), | ||||
| .sense_size = 96, | .sense_size = 96, | ||||
| .cdb_size = 32, | .cdb_size = 32, | ||||
| .max_channel = VIRTIO_SCSI_MAX_CHANNEL, | .max_channel = VIRTIO_SCSI_MAX_CHANNEL, | ||||
| .max_target = VIRTIO_SCSI_MAX_TARGET, | .max_target = MAX(1, sc->vss_num_target) - 1, | ||||
| .max_lun = VIRTIO_SCSI_MAX_LUN | .max_lun = VIRTIO_SCSI_MAX_LUN | ||||
| }; | }; | ||||
| } | } | ||||
| static void | static void | ||||
| pci_vtscsi_neg_features(void *vsc, uint64_t negotiated_features) | pci_vtscsi_neg_features(void *vsc, uint64_t negotiated_features) | ||||
| { | { | ||||
| struct pci_vtscsi_softc *sc = vsc; | struct pci_vtscsi_softc *sc = vsc; | ||||
| Show All 31 Lines | |||||
| * | * | ||||
| * Only the first two levels are acutally used by virtio-scsi: | * Only the first two levels are acutally used by virtio-scsi: | ||||
| * | * | ||||
| * Level 1: 0x01, 0xTT: Peripheral Device Addressing: Bus 1, Target 0-255 | * Level 1: 0x01, 0xTT: Peripheral Device Addressing: Bus 1, Target 0-255 | ||||
| * Level 2: 0xLL, 0xLL: Peripheral Device Addressing: Bus MBZ, LUN 0-255 | * Level 2: 0xLL, 0xLL: Peripheral Device Addressing: Bus MBZ, LUN 0-255 | ||||
| * or: Flat Space Addressing: LUN (0-16383) | * or: Flat Space Addressing: LUN (0-16383) | ||||
| * Level 3 and 4: not used, MBZ | * Level 3 and 4: not used, MBZ | ||||
| * | * | ||||
| * Currently, we only support Target 0. | |||||
| * | * | ||||
| * Alternatively, the first level may contain an extended LUN address to select | * Alternatively, the first level may contain an extended LUN address to select | ||||
| * the REPORT_LUNS well-known logical unit: | * the REPORT_LUNS well-known logical unit: | ||||
| * | * | ||||
| * Level 1: 0xC1, 0x01: Extended LUN Adressing, Well-Known LUN 1 (REPORT_LUNS) | * Level 1: 0xC1, 0x01: Extended LUN Adressing, Well-Known LUN 1 (REPORT_LUNS) | ||||
| * Level 2, 3, and 4: not used, MBZ | * Level 2, 3, and 4: not used, MBZ | ||||
| * | * | ||||
| * The virtio spec says that we SHOULD implement the REPORT_LUNS well-known | * The virtio spec says that we SHOULD implement the REPORT_LUNS well-known | ||||
| * logical unit but we currently don't. | * logical unit but we currently don't. | ||||
| * | * | ||||
| * According to the virtio spec, these are the only LUNS address formats to be | * According to the virtio spec, these are the only LUNS address formats to be | ||||
| * used with virtio-scsi. | * used with virtio-scsi. | ||||
| */ | */ | ||||
| /* | /* | ||||
| * Check that the given LUN address conforms to the virtio spec, does not | * 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 | * address an unknown target, and especially does not address the REPORT_LUNS | ||||
| * well-known logical unit. | * well-known logical unit. | ||||
| */ | */ | ||||
| static inline bool | static inline bool | ||||
| pci_vtscsi_check_lun(const uint8_t *lun) | pci_vtscsi_check_lun(struct pci_vtscsi_softc *sc, const uint8_t *lun) | ||||
| { | { | ||||
| if (lun[0] == 0xC1) | if (lun[0] == 0xC1) | ||||
| return (false); | return (false); | ||||
| if (lun[0] != 0x01) | if (lun[0] != 0x01) | ||||
| return (false); | return (false); | ||||
| if (lun[1] != 0x00) | if (lun[1] >= sc->vss_num_target) | ||||
| return (false); | 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) | if (lun[2] != 0x00 && (lun[2] & 0xc0) != 0x40) | ||||
| return (false); | return (false); | ||||
| if (lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) | if (lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) | ||||
| return (false); | return (false); | ||||
| return (true); | return (true); | ||||
| } | } | ||||
| static inline int | static inline int | ||||
| pci_vtscsi_get_lun(const uint8_t *lun) | pci_vtscsi_get_target(struct pci_vtscsi_softc *sc, const uint8_t *lun) | ||||
| { | { | ||||
| assert(lun[0] == 0x01); | assert(lun[0] == 0x01); | ||||
| assert(lun[1] == 0x00); | 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); | assert(lun[2] == 0x00 || (lun[2] & 0xc0) == 0x40); | ||||
| return (lun[1]); | |||||
| } | |||||
| static inline int | |||||
| 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); | return (((lun[2] << 8) | lun[3]) & 0x3fff); | ||||
| } | } | ||||
| static void | static void | ||||
| pci_vtscsi_control_handle(struct pci_vtscsi_softc *sc, void *buf, | pci_vtscsi_control_handle(struct pci_vtscsi_softc *sc, void *buf, | ||||
| size_t bufsize) | size_t bufsize) | ||||
| { | { | ||||
| struct pci_vtscsi_ctrl_tmf *tmf; | struct pci_vtscsi_ctrl_tmf *tmf; | ||||
| Show All 24 Lines | pci_vtscsi_control_handle(struct pci_vtscsi_softc *sc, void *buf, | ||||
| } | } | ||||
| } | } | ||||
| static void | static void | ||||
| pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *sc, | pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *sc, | ||||
| struct pci_vtscsi_ctrl_tmf *tmf) | struct pci_vtscsi_ctrl_tmf *tmf) | ||||
| { | { | ||||
| union ctl_io *io; | union ctl_io *io; | ||||
| int target; | |||||
| int err; | int err; | ||||
| int fd; | |||||
| if (pci_vtscsi_check_lun(tmf->lun) == false) { | if (pci_vtscsi_check_lun(sc, tmf->lun) == false) { | ||||
| DPRINTF("TMF request to invalid LUN %.2hhx%.2hhx-%.2hhx%.2hhx-" | DPRINTF("TMF request to invalid LUN %.2hhx%.2hhx-%.2hhx%.2hhx-" | ||||
| "%.2hhx%.2hhx-%.2hhx%.2hhx", tmf->lun[0], tmf->lun[1], | "%.2hhx%.2hhx-%.2hhx%.2hhx", tmf->lun[0], tmf->lun[1], | ||||
| tmf->lun[2], tmf->lun[3], tmf->lun[4], tmf->lun[5], | tmf->lun[2], tmf->lun[3], tmf->lun[4], tmf->lun[5], | ||||
| tmf->lun[6], tmf->lun[7]); | tmf->lun[6], tmf->lun[7]); | ||||
| tmf->response = VIRTIO_SCSI_S_BAD_TARGET; | tmf->response = VIRTIO_SCSI_S_BAD_TARGET; | ||||
| return; | return; | ||||
| } | } | ||||
| target = pci_vtscsi_get_target(sc, tmf->lun); | |||||
| fd = sc->vss_targets[target].vst_fd; | |||||
| io = ctl_scsi_alloc_io(sc->vss_iid); | io = ctl_scsi_alloc_io(sc->vss_iid); | ||||
| if (io == NULL) { | if (io == NULL) { | ||||
| WPRINTF("failed to allocate ctl_io: err=%d (%s)", | WPRINTF("failed to allocate ctl_io: err=%d (%s)", | ||||
| errno, strerror(errno)); | errno, strerror(errno)); | ||||
| tmf->response = VIRTIO_SCSI_S_FAILURE; | tmf->response = VIRTIO_SCSI_S_FAILURE; | ||||
| return; | return; | ||||
| } | } | ||||
| ctl_scsi_zero_io(io); | ctl_scsi_zero_io(io); | ||||
| io->io_hdr.io_type = CTL_IO_TASK; | io->io_hdr.io_type = CTL_IO_TASK; | ||||
| io->io_hdr.nexus.initid = sc->vss_iid; | io->io_hdr.nexus.initid = sc->vss_iid; | ||||
| io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(tmf->lun); | io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(sc, tmf->lun); | ||||
| io->taskio.tag_type = CTL_TAG_SIMPLE; | io->taskio.tag_type = CTL_TAG_SIMPLE; | ||||
| io->taskio.tag_num = tmf->id; | io->taskio.tag_num = tmf->id; | ||||
| io->io_hdr.flags |= CTL_FLAG_USER_TAG; | io->io_hdr.flags |= CTL_FLAG_USER_TAG; | ||||
| switch (tmf->subtype) { | switch (tmf->subtype) { | ||||
| case VIRTIO_SCSI_T_TMF_ABORT_TASK: | case VIRTIO_SCSI_T_TMF_ABORT_TASK: | ||||
| io->taskio.task_action = CTL_TASK_ABORT_TASK; | io->taskio.task_action = CTL_TASK_ABORT_TASK; | ||||
| break; | break; | ||||
| Show All 30 Lines | pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *sc, | ||||
| if (pci_vtscsi_debug) { | if (pci_vtscsi_debug) { | ||||
| struct sbuf *sb = sbuf_new_auto(); | struct sbuf *sb = sbuf_new_auto(); | ||||
| ctl_io_sbuf(io, sb); | ctl_io_sbuf(io, sb); | ||||
| sbuf_finish(sb); | sbuf_finish(sb); | ||||
| DPRINTF("%s", sbuf_data(sb)); | DPRINTF("%s", sbuf_data(sb)); | ||||
| sbuf_delete(sb); | sbuf_delete(sb); | ||||
| } | } | ||||
| err = ioctl(sc->vss_ctl_fd, CTL_IO, io); | err = ioctl(fd, CTL_IO, io); | ||||
| if (err != 0) | if (err != 0) | ||||
| WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno)); | WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno)); | ||||
| tmf->response = io->taskio.task_status; | tmf->response = io->taskio.task_status; | ||||
| ctl_scsi_free_io(io); | ctl_scsi_free_io(io); | ||||
| } | } | ||||
| static void | static void | ||||
| ▲ Show 20 Lines • Show All 150 Lines • ▼ Show 20 Lines | pci_vtscsi_queue_request(struct pci_vtscsi_softc *sc, struct vqueue_info *vq) | ||||
| * | * | ||||
| * This will have to change if we begin allowing config space writes | * This will have to change if we begin allowing config space writes | ||||
| * to change sense size. | * to change sense size. | ||||
| */ | */ | ||||
| assert(iov_to_buf(req->vsr_iov_in, req->vsr_niov_in, | assert(iov_to_buf(req->vsr_iov_in, req->vsr_niov_in, | ||||
| (void **)&req->vsr_cmd_rd) == VTSCSI_IN_HEADER_LEN(q->vsq_sc)); | (void **)&req->vsr_cmd_rd) == VTSCSI_IN_HEADER_LEN(q->vsq_sc)); | ||||
| /* Make sure this request addresses a valid LUN. */ | /* Make sure this request addresses a valid LUN. */ | ||||
| if (pci_vtscsi_check_lun(req->vsr_cmd_rd->lun) == false) { | if (pci_vtscsi_check_lun(sc, req->vsr_cmd_rd->lun) == false) { | ||||
| DPRINTF("I/O request to invalid LUN " | DPRINTF("I/O request to invalid LUN " | ||||
| "%.2hhx%.2hhx-%.2hhx%.2hhx-%.2hhx%.2hhx-%.2hhx%.2hhx", | "%.2hhx%.2hhx-%.2hhx%.2hhx-%.2hhx%.2hhx-%.2hhx%.2hhx", | ||||
| req->vsr_cmd_rd->lun[0], req->vsr_cmd_rd->lun[1], | req->vsr_cmd_rd->lun[0], req->vsr_cmd_rd->lun[1], | ||||
| req->vsr_cmd_rd->lun[2], req->vsr_cmd_rd->lun[3], | req->vsr_cmd_rd->lun[2], req->vsr_cmd_rd->lun[3], | ||||
| req->vsr_cmd_rd->lun[4], req->vsr_cmd_rd->lun[5], | req->vsr_cmd_rd->lun[4], req->vsr_cmd_rd->lun[5], | ||||
| req->vsr_cmd_rd->lun[6], req->vsr_cmd_rd->lun[7]); | req->vsr_cmd_rd->lun[6], req->vsr_cmd_rd->lun[7]); | ||||
| req->vsr_cmd_wr->response = VIRTIO_SCSI_S_BAD_TARGET; | req->vsr_cmd_wr->response = VIRTIO_SCSI_S_BAD_TARGET; | ||||
| pci_vtscsi_return_request(q, req, 1); | pci_vtscsi_return_request(q, req, 1); | ||||
| Show All 39 Lines | pci_vtscsi_return_request(struct pci_vtscsi_queue *q, | ||||
| pthread_mutex_lock(&q->vsq_qmtx); | pthread_mutex_lock(&q->vsq_qmtx); | ||||
| vq_relchain(q->vsq_vq, idx, iolen); | vq_relchain(q->vsq_vq, idx, iolen); | ||||
| vq_endchains(q->vsq_vq, 0); | vq_endchains(q->vsq_vq, 0); | ||||
| pthread_mutex_unlock(&q->vsq_qmtx); | pthread_mutex_unlock(&q->vsq_qmtx); | ||||
| } | } | ||||
| static int | static int | ||||
| pci_vtscsi_request_handle(struct pci_vtscsi_softc *sc, | pci_vtscsi_request_handle(struct pci_vtscsi_softc *sc, int fd, | ||||
| struct pci_vtscsi_request *req) | struct pci_vtscsi_request *req) | ||||
| { | { | ||||
| union ctl_io *io = req->vsr_ctl_io; | union ctl_io *io = req->vsr_ctl_io; | ||||
| void *ext_data_ptr = NULL; | void *ext_data_ptr = NULL; | ||||
| uint32_t ext_data_len = 0, ext_sg_entries = 0; | uint32_t ext_data_len = 0, ext_sg_entries = 0; | ||||
| int err, nxferred; | int err, nxferred; | ||||
| io->io_hdr.nexus.initid = sc->vss_iid; | io->io_hdr.nexus.initid = sc->vss_iid; | ||||
| io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(req->vsr_cmd_rd->lun); | io->io_hdr.nexus.targ_lun = | ||||
| pci_vtscsi_get_lun(sc, req->vsr_cmd_rd->lun); | |||||
| io->io_hdr.io_type = CTL_IO_SCSI; | io->io_hdr.io_type = CTL_IO_SCSI; | ||||
| if (req->vsr_data_niov_in > 0) { | if (req->vsr_data_niov_in > 0) { | ||||
| ext_data_ptr = (void *)req->vsr_data_iov_in; | ext_data_ptr = (void *)req->vsr_data_iov_in; | ||||
| ext_sg_entries = req->vsr_data_niov_in; | ext_sg_entries = req->vsr_data_niov_in; | ||||
| ext_data_len = count_iov(req->vsr_data_iov_in, | ext_data_len = count_iov(req->vsr_data_iov_in, | ||||
| req->vsr_data_niov_in); | req->vsr_data_niov_in); | ||||
| Show All 34 Lines | pci_vtscsi_request_handle(struct pci_vtscsi_softc *sc, int fd, | ||||
| if (pci_vtscsi_debug) { | if (pci_vtscsi_debug) { | ||||
| struct sbuf *sb = sbuf_new_auto(); | struct sbuf *sb = sbuf_new_auto(); | ||||
| ctl_io_sbuf(io, sb); | ctl_io_sbuf(io, sb); | ||||
| sbuf_finish(sb); | sbuf_finish(sb); | ||||
| DPRINTF("%s", sbuf_data(sb)); | DPRINTF("%s", sbuf_data(sb)); | ||||
| sbuf_delete(sb); | sbuf_delete(sb); | ||||
| } | } | ||||
| err = ioctl(sc->vss_ctl_fd, CTL_IO, io); | err = ioctl(fd, CTL_IO, io); | ||||
| if (err != 0) { | if (err != 0) { | ||||
| WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno)); | WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno)); | ||||
| req->vsr_cmd_wr->response = VIRTIO_SCSI_S_FAILURE; | req->vsr_cmd_wr->response = VIRTIO_SCSI_S_FAILURE; | ||||
| } else { | } else { | ||||
| req->vsr_cmd_wr->sense_len = | req->vsr_cmd_wr->sense_len = | ||||
| MIN(io->scsiio.sense_len, sc->vss_config.sense_size); | MIN(io->scsiio.sense_len, sc->vss_config.sense_size); | ||||
| req->vsr_cmd_wr->residual = ext_data_len - | req->vsr_cmd_wr->residual = ext_data_len - | ||||
| io->scsiio.ext_data_filled; | io->scsiio.ext_data_filled; | ||||
| ▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | for (i = 0; i < VTSCSI_THR_PER_Q; i++) { | ||||
| snprintf(tname, sizeof(tname), "vtscsi:%d-%d", num, i); | snprintf(tname, sizeof(tname), "vtscsi:%d-%d", num, i); | ||||
| pthread_set_name_np(worker->vsw_thread, tname); | pthread_set_name_np(worker->vsw_thread, tname); | ||||
| LIST_INSERT_HEAD(&queue->vsq_workers, worker, vsw_link); | LIST_INSERT_HEAD(&queue->vsq_workers, worker, vsw_link); | ||||
| } | } | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| /* | |||||
| * Create a target config node, return target id. If the target number isn't | |||||
| * given as part of the path argument, use last_id + 1. | |||||
| */ | |||||
| static int | static int | ||||
| pci_vtscsi_add_target_config(nvlist_t *nvl, const char *path, int last_id) | |||||
| { | |||||
| long target; | |||||
| char tmp[4]; | |||||
| if (path == NULL) { | |||||
| EPRINTLN("target path must be specified"); | |||||
| return (-1); | |||||
| } | |||||
| if (path[0] != '/') { | |||||
| char *endptr; | |||||
| target = strtoul(path, &endptr, 10); | |||||
| if (endptr == path || endptr[0] != ':' || target < 0) | |||||
| return (-1); | |||||
| path = endptr + 1; | |||||
| } else { | |||||
| target = last_id + 1; | |||||
| } | |||||
| if (target > VIRTIO_SCSI_MAX_TARGET) { | |||||
| EPRINTLN("max target (%d) reached, can't add another", | |||||
| VIRTIO_SCSI_MAX_TARGET); | |||||
| return (-1); | |||||
| } | |||||
| snprintf(tmp, sizeof(tmp), "%ld", target); | |||||
| set_config_value_node(nvl, tmp, path); | |||||
| return (target); | |||||
| } | |||||
| /* | |||||
| * The following forms are accepted for legacy config options to configure a | |||||
| * single target: | |||||
| * | |||||
| * (0) -s B:D:F,virtio-scsi | |||||
| * (1) -s B:D:F,virtio-scsi,<dev> | |||||
| * (2) -s B:D:F,virtio-scsi,<dev>,<name=value>,... | |||||
| * (3) -s B:D:F,virtio-scsi,<name=value>,... | |||||
| * (4) -s B:D:F,virtio-scsi,<name=value> | |||||
| * | |||||
| * To configure multiple targets, the following form is accepted: | |||||
| * (5) -s B:D:F,virtio-scsi,[target=<dev>,...] | |||||
| */ | |||||
| static int | |||||
| pci_vtscsi_legacy_config(nvlist_t *nvl, const char *opts) | pci_vtscsi_legacy_config(nvlist_t *nvl, const char *opts) | ||||
| { | { | ||||
| char *cp, *devname; | int last_id = -1; | ||||
| char *config, *tofree, *name, *value; | |||||
| nvlist_t *targets; | |||||
| size_t n; | |||||
| if (opts == NULL) | /* Make sure no one accidentally sets "dev" anymore. */ | ||||
| (void) create_relative_config_node(nvl, "dev"); | |||||
| targets = create_relative_config_node(nvl, "target"); | |||||
| /* Handle legacy form (0). */ | |||||
| if (opts == NULL) { | |||||
| pci_vtscsi_add_target_config(targets, "/dev/cam/ctl", 0); | |||||
| return (0); | return (0); | ||||
| } | |||||
| cp = strchr(opts, ','); | n = strcspn(opts, ",="); | ||||
| if (cp == NULL) { | |||||
| set_config_value_node(nvl, "dev", opts); | /* Handle legacy form (1) and (2). */ | ||||
| if (opts[n] == ',' || opts[n] == '\0') { | |||||
| char *tmp = strndup(opts, n); | |||||
| last_id = pci_vtscsi_add_target_config(targets, tmp, last_id); | |||||
| free(tmp); | |||||
| if (last_id < 0) | |||||
| return (-1); | |||||
| opts += n; | |||||
| if (opts[0] == ',' && opts[1] != '\0') | |||||
| opts++; | |||||
| } | |||||
| /* If this was form (1), we're done. */ | |||||
| if (opts[0] == '\0') | |||||
| return (0); | return (0); | ||||
| /* | |||||
| * For form (2), (3), (4), and (5), parse the remaining options. | |||||
| * | |||||
| * Contrary to other options, multiple target=<dev> options create a new | |||||
| * target for each such option. | |||||
| * | |||||
| * For compatibility reasons we also accept dev=<dev> options for | |||||
| * targets. | |||||
| */ | |||||
| config = tofree = strdup(opts); | |||||
| while ((name = strsep(&config, ",")) != NULL) { | |||||
| value = strchr(name, '='); | |||||
| if (value != NULL) | |||||
| *value++ = '\0'; | |||||
| if (strcmp(name, "dev") == 0 || strcmp(name, "target") == 0) { | |||||
| int new_id = pci_vtscsi_add_target_config(targets, | |||||
| value, last_id); | |||||
| if (new_id < 0) { | |||||
| free(tofree); | |||||
| return (-1); | |||||
| } | } | ||||
| devname = strndup(opts, cp - opts); | |||||
| set_config_value_node(nvl, "dev", devname); | if (new_id > last_id) | ||||
| free(devname); | last_id = new_id; | ||||
| return (pci_parse_legacy_config(nvl, cp + 1)); | |||||
| } else if (value != NULL) { | |||||
| set_config_value_node(nvl, name, value); | |||||
| } else { | |||||
| set_config_bool_node(nvl, name, true); | |||||
| } | } | ||||
| } | |||||
| free(tofree); | |||||
| return (0); | |||||
| } | |||||
| static int | static int | ||||
| pci_vtscsi_count_targets(const char *prefix __unused, | |||||
| const nvlist_t *parent __unused, const char *name, int type, void *arg) | |||||
| { | |||||
| struct pci_vtscsi_softc *sc = arg; | |||||
| char *endptr; | |||||
| long target; | |||||
| if (type != NV_TYPE_STRING) { | |||||
| EPRINTLN("invalid target \"%s\" type: not a string", name); | |||||
| errno = EINVAL; | |||||
| return (-1); | |||||
| } | |||||
| target = strtol(name, &endptr, 10); | |||||
| assert(endptr[0] == '\0'); | |||||
| assert(target >= 0); | |||||
| assert(target <= VIRTIO_SCSI_MAX_TARGET); | |||||
| if (target >= sc->vss_num_target) | |||||
| sc->vss_num_target = target + 1; | |||||
| return (0); | |||||
| } | |||||
| static int | |||||
| pci_vtscsi_init_target(const char *prefix __unused, const nvlist_t *parent, | |||||
| const char *name, int type, void *arg) | |||||
| { | |||||
| struct pci_vtscsi_softc *sc = arg; | |||||
| const char *value; | |||||
| char *endptr; | |||||
| long target; | |||||
| assert(type == NV_TYPE_STRING); | |||||
| /* | |||||
| * Get the numeric value of the target id from 'name'. | |||||
| */ | |||||
| target = strtol(name, &endptr, 10); | |||||
| assert(endptr[0] == '\0'); | |||||
| assert(target >= 0); | |||||
| assert(target <= VIRTIO_SCSI_MAX_TARGET); | |||||
| assert(target <= sc->vss_num_target); | |||||
| sc->vss_targets[target].vst_target = target; | |||||
| /* | |||||
| * 'value' contains the CTL device node path of this target. | |||||
| */ | |||||
| value = nvlist_get_string(parent, name); | |||||
| sc->vss_targets[target].vst_fd = open(value, O_RDWR); | |||||
| if (sc->vss_targets[target].vst_fd < 0) { | |||||
| EPRINTLN("cannot open target %ld at %s: %s", target, value, | |||||
| strerror(errno)); | |||||
| return (-1); | |||||
| } | |||||
| return (0); | |||||
| } | |||||
| static int | |||||
| pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl) | pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl) | ||||
| { | { | ||||
| struct pci_vtscsi_softc *sc; | struct pci_vtscsi_softc *sc; | ||||
| const char *devname, *value; | const char *value; | ||||
| int err; | int err; | ||||
| int i; | int i; | ||||
| sc = calloc(1, sizeof(struct pci_vtscsi_softc)); | sc = calloc(1, sizeof(struct pci_vtscsi_softc)); | ||||
| if (sc == NULL) | if (sc == NULL) | ||||
| return (-1); | return (-1); | ||||
| value = get_config_value_node(nvl, "iid"); | value = get_config_value_node(nvl, "iid"); | ||||
| if (value != NULL) | if (value != NULL) | ||||
| sc->vss_iid = strtoul(value, NULL, 10); | sc->vss_iid = strtoul(value, NULL, 10); | ||||
| value = get_config_value_node(nvl, "bootindex"); | value = get_config_value_node(nvl, "bootindex"); | ||||
| if (value != NULL) { | if (value != NULL) { | ||||
| if (pci_emul_add_boot_device(pi, atoi(value))) { | if (pci_emul_add_boot_device(pi, atoi(value))) { | ||||
| EPRINTLN("Invalid bootindex %d", atoi(value)); | EPRINTLN("Invalid bootindex %d", atoi(value)); | ||||
| free(sc); | errno = EINVAL; | ||||
| return (-1); | goto fail; | ||||
| } | } | ||||
| } | } | ||||
| devname = get_config_value_node(nvl, "dev"); | nvl = find_relative_config_node(nvl, "target"); | ||||
| if (devname == NULL) | if (nvl != NULL) { | ||||
| devname = "/dev/cam/ctl"; | err = walk_config_nodes("", nvl, sc, pci_vtscsi_count_targets); | ||||
| sc->vss_ctl_fd = open(devname, O_RDWR); | if (err != 0) | ||||
| if (sc->vss_ctl_fd < 0) { | goto fail; | ||||
| WPRINTF("cannot open %s: %s", devname, strerror(errno)); | |||||
| free(sc); | |||||
| return (1); | |||||
| } | } | ||||
| if (sc->vss_num_target > 0) { | |||||
| sc->vss_targets = malloc(sc->vss_num_target * | |||||
| sizeof(struct pci_vtscsi_target)); | |||||
| memset(sc->vss_targets, -1, sc->vss_num_target * | |||||
| sizeof(struct pci_vtscsi_target)); | |||||
| if (sc->vss_targets == NULL) { | |||||
| EPRINTLN("can't allocate space for %d targets", | |||||
| sc->vss_num_target); | |||||
| goto fail; | |||||
| } | |||||
| err = walk_config_nodes("", nvl, sc, pci_vtscsi_init_target); | |||||
| if (err != 0) | |||||
| goto fail; | |||||
Not Done Inline ActionsThe cleanup handling is a bit inconsistent: we don't free sc->vss_targets here, but further below we do (but other cleanup is missing, e.g., vss_mtx is not destroyed). I would suggest just unconditionally calling free(sc->vss_targets) after the fail label. markj: The cleanup handling is a bit inconsistent: we don't free `sc->vss_targets` here, but further… | |||||
| } | |||||
| /* | |||||
| * All targets should be open now and have a valid fd. | |||||
| */ | |||||
| for (i = 0; i < sc->vss_num_target; i++) { | |||||
| if (sc->vss_targets[i].vst_target == i && | |||||
| sc->vss_targets[i].vst_fd < 0) | |||||
| goto fail; | |||||
| } | |||||
| pthread_mutex_init(&sc->vss_mtx, NULL); | pthread_mutex_init(&sc->vss_mtx, NULL); | ||||
| vi_softc_linkup(&sc->vss_vs, &vtscsi_vi_consts, sc, pi, sc->vss_vq); | vi_softc_linkup(&sc->vss_vs, &vtscsi_vi_consts, sc, pi, sc->vss_vq); | ||||
| sc->vss_vs.vs_mtx = &sc->vss_mtx; | sc->vss_vs.vs_mtx = &sc->vss_mtx; | ||||
| /* | /* | ||||
| * Perform a "reset" before we set up our queues. | * Perform a "reset" before we set up our queues. | ||||
| * | * | ||||
| Show All 16 Lines | pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl) | ||||
| sc->vss_vq[1].vq_notify = pci_vtscsi_eventq_notify; | sc->vss_vq[1].vq_notify = pci_vtscsi_eventq_notify; | ||||
| /* request queues */ | /* request queues */ | ||||
| for (i = 2; i < VTSCSI_MAXQ; i++) { | for (i = 2; i < VTSCSI_MAXQ; i++) { | ||||
| sc->vss_vq[i].vq_qsize = VTSCSI_RINGSZ; | sc->vss_vq[i].vq_qsize = VTSCSI_RINGSZ; | ||||
| sc->vss_vq[i].vq_notify = pci_vtscsi_requestq_notify; | sc->vss_vq[i].vq_notify = pci_vtscsi_requestq_notify; | ||||
| err = pci_vtscsi_init_queue(sc, &sc->vss_queues[i - 2], i - 2); | err = pci_vtscsi_init_queue(sc, &sc->vss_queues[i - 2], i - 2); | ||||
| if (err != 0) | if (err != 0) { | ||||
| return (err); | free(sc->vss_targets); | ||||
| goto fail; | |||||
| } | } | ||||
| } | |||||
| /* initialize config space */ | /* initialize config space */ | ||||
| pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_SCSI); | pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_SCSI); | ||||
| pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR); | pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR); | ||||
| pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE); | pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE); | ||||
| pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_ID_SCSI); | pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_ID_SCSI); | ||||
| pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR); | pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR); | ||||
| if (vi_intr_init(&sc->vss_vs, 1, fbsdrun_virtio_msix())) | err = vi_intr_init(&sc->vss_vs, 1, fbsdrun_virtio_msix()); | ||||
| return (1); | if (err != 0) { | ||||
| free(sc->vss_targets); | |||||
| goto fail; | |||||
| } | |||||
| vi_set_io_bar(&sc->vss_vs, 0); | vi_set_io_bar(&sc->vss_vs, 0); | ||||
| return (0); | return (0); | ||||
| fail: | |||||
| free(sc); | |||||
| return (-1); | |||||
| } | } | ||||
| static const struct pci_devemu pci_de_vscsi = { | static const struct pci_devemu pci_de_vscsi = { | ||||
| .pe_emu = "virtio-scsi", | .pe_emu = "virtio-scsi", | ||||
| .pe_init = pci_vtscsi_init, | .pe_init = pci_vtscsi_init, | ||||
| .pe_legacy_config = pci_vtscsi_legacy_config, | .pe_legacy_config = pci_vtscsi_legacy_config, | ||||
| .pe_barwrite = vi_pci_write, | .pe_barwrite = vi_pci_write, | ||||
| .pe_barread = vi_pci_read | .pe_barread = vi_pci_read | ||||
| }; | }; | ||||
| PCI_EMUL_SET(pci_de_vscsi); | PCI_EMUL_SET(pci_de_vscsi); | ||||
The spacing here looks off.