From owner-svn-src-all@FreeBSD.ORG Thu Oct 11 23:41:18 2012 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id C16E3274; Thu, 11 Oct 2012 23:41:18 +0000 (UTC) (envelope-from grehan@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id A70088FC14; Thu, 11 Oct 2012 23:41:18 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q9BNfISv005019; Thu, 11 Oct 2012 23:41:18 GMT (envelope-from grehan@svn.freebsd.org) Received: (from grehan@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q9BNfIss005013; Thu, 11 Oct 2012 23:41:18 GMT (envelope-from grehan@svn.freebsd.org) Message-Id: <201210112341.q9BNfIss005013@svn.freebsd.org> From: Peter Grehan Date: Thu, 11 Oct 2012 23:41:18 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r241470 - in head: share/man/man4 sys/dev/virtio sys/dev/virtio/scsi sys/modules/virtio sys/modules/virtio/scsi X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 11 Oct 2012 23:41:18 -0000 Author: grehan Date: Thu Oct 11 23:41:18 2012 New Revision: 241470 URL: http://svn.freebsd.org/changeset/base/241470 Log: Virtio SCSI driver Submitted by: Bryan Venteicher bryanv at daemoninthecloset dot org Reviewed by: grehan Added: head/share/man/man4/virtio_scsi.4 (contents, props changed) head/sys/dev/virtio/scsi/ head/sys/dev/virtio/scsi/virtio_scsi.c (contents, props changed) head/sys/dev/virtio/scsi/virtio_scsi.h (contents, props changed) head/sys/dev/virtio/scsi/virtio_scsivar.h (contents, props changed) head/sys/modules/virtio/scsi/ head/sys/modules/virtio/scsi/Makefile (contents, props changed) Modified: head/share/man/man4/Makefile head/share/man/man4/virtio.4 head/sys/dev/virtio/virtio.c head/sys/dev/virtio/virtio.h head/sys/modules/virtio/Makefile Modified: head/share/man/man4/Makefile ============================================================================== --- head/share/man/man4/Makefile Thu Oct 11 23:03:42 2012 (r241469) +++ head/share/man/man4/Makefile Thu Oct 11 23:41:18 2012 (r241470) @@ -531,6 +531,7 @@ MAN= aac.4 \ ${_virtio.4} \ ${_virtio_balloon.4} \ ${_virtio_blk.4} \ + ${_virtio_scsi.4} \ vkbd.4 \ vlan.4 \ vpo.4 \ @@ -768,6 +769,7 @@ _nxge.4= nxge.4 _virtio.4= virtio.4 _virtio_balloon.4=virtio_balloon.4 _virtio_blk.4= virtio_blk.4 +_virtio_scsi.4= virtio_scsi.4 _vtnet.4= vtnet.4 _vxge.4= vxge.4 _padlock.4= padlock.4 Modified: head/share/man/man4/virtio.4 ============================================================================== --- head/share/man/man4/virtio.4 Thu Oct 11 23:03:42 2012 (r241469) +++ head/share/man/man4/virtio.4 Thu Oct 11 23:41:18 2012 (r241470) @@ -72,6 +72,10 @@ device driver. An emulated disk controller is provided by the .Xr virtio_blk 4 device driver. +.It Nm SCSI +An emulated SCSI HBA is provided by the +.Xr virtio_scsi 4 +device driver. .It Nm Balloon A pseudo-device to allow the VM to release memory back to the hypervisor is provided by the @@ -81,6 +85,7 @@ device driver. .Sh SEE ALSO .Xr virtio_balloon 4 , .Xr virtio_blk 4 , +.Xr virtio_scsi 4 , .Xr vtnet 4 .Sh HISTORY Support for VirtIO first appeared in Added: head/share/man/man4/virtio_scsi.4 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/share/man/man4/virtio_scsi.4 Thu Oct 11 23:41:18 2012 (r241470) @@ -0,0 +1,92 @@ +.\" Copyright (c) 2012 Bryan Venteicher +.\" 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. +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd June 24, 2012 +.Dt VIRTIO_SCSI 4 +.Os +.Sh NAME +.Nm virtio_scsi +.Nd VirtIO SCSI driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device virtio_scsi" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +virtio_scsi_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +device driver provides support for VirtIO SCSI devices. +.Sh LOADER TUNABLES +Tunables can be set at the +.Xr loader 8 +prompt before booting the kernel or stored in +.Xr loader.conf 5 . +.Bl -tag -width "xxxxxx" +.It Va hw.vtscsi.bus_reset_disable +In the initial QEMU release with VirtIO SCSI support, in-flight +operations were not aborted when stopping the device, rendering +bus reset ineffective. This tunable disables attempts to issue +reset bus commands. The default value is 1. +.El +.Sh DEBUGGING +To enable debugging prints from the +.Nm +driver, set the +.Bd -literal -offset indent +hw.vtscsi.X.debug_level +.Ed +.Pp +variable, where X is the adapter number, either in +.Xr loader.conf 5 +or via +.Xr sysctl 8 . +The following bits have the described effects: +.Bl -tag -width 6n -offset indent +.It 0x01 +Enable informational prints. +.It 0x02 +Enable prints for driver errors. +.It 0x04 +Enable tracing prints. +.El +.Sh SEE ALSO +.Xr virtio 4 +.Sh HISTORY +The +.Nm +driver was written by +.An Bryan Venteicher Aq bryanv@daemoninthecloset.org . +It first appeared in +.Fx 10.0 . Added: head/sys/dev/virtio/scsi/virtio_scsi.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/virtio/scsi/virtio_scsi.c Thu Oct 11 23:41:18 2012 (r241470) @@ -0,0 +1,2367 @@ +/*- + * Copyright (c) 2012, Bryan Venteicher + * 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 unmodified, this list of conditions, and the following + * disclaimer. + * 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 ``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 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. + */ + +/* Driver for VirtIO SCSI devices. */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "virtio_if.h" + +static int vtscsi_modevent(module_t, int, void *); + +static int vtscsi_probe(device_t); +static int vtscsi_attach(device_t); +static int vtscsi_detach(device_t); +static int vtscsi_suspend(device_t); +static int vtscsi_resume(device_t); + +static void vtscsi_negotiate_features(struct vtscsi_softc *); +static int vtscsi_maximum_segments(struct vtscsi_softc *, int); +static int vtscsi_alloc_virtqueues(struct vtscsi_softc *); +static void vtscsi_write_device_config(struct vtscsi_softc *); +static int vtscsi_reinit(struct vtscsi_softc *); + +static int vtscsi_alloc_cam(struct vtscsi_softc *); +static int vtscsi_register_cam(struct vtscsi_softc *); +static void vtscsi_free_cam(struct vtscsi_softc *); +static void vtscsi_cam_async(void *, uint32_t, struct cam_path *, void *); +static int vtscsi_register_async(struct vtscsi_softc *); +static void vtscsi_deregister_async(struct vtscsi_softc *); +static void vtscsi_cam_action(struct cam_sim *, union ccb *); +static void vtscsi_cam_poll(struct cam_sim *); + +static void vtscsi_cam_scsi_io(struct vtscsi_softc *, struct cam_sim *, + union ccb *); +static void vtscsi_cam_get_tran_settings(struct vtscsi_softc *, + union ccb *); +static void vtscsi_cam_reset_bus(struct vtscsi_softc *, union ccb *); +static void vtscsi_cam_reset_dev(struct vtscsi_softc *, union ccb *); +static void vtscsi_cam_abort(struct vtscsi_softc *, union ccb *); +static void vtscsi_cam_path_inquiry(struct vtscsi_softc *, + struct cam_sim *, union ccb *); + +static int vtscsi_sg_append_scsi_buf(struct vtscsi_softc *, + struct sglist *, struct ccb_scsiio *); +static int vtscsi_fill_scsi_cmd_sglist(struct vtscsi_softc *, + struct vtscsi_request *, int *, int *); +static int vtscsi_execute_scsi_cmd(struct vtscsi_softc *, + struct vtscsi_request *); +static int vtscsi_start_scsi_cmd(struct vtscsi_softc *, union ccb *); +static void vtscsi_complete_abort_timedout_scsi_cmd(struct vtscsi_softc *, + struct vtscsi_request *); +static int vtscsi_abort_timedout_scsi_cmd(struct vtscsi_softc *, + struct vtscsi_request *); +static void vtscsi_timedout_scsi_cmd(void *); +static cam_status vtscsi_scsi_cmd_cam_status(struct virtio_scsi_cmd_resp *); +static cam_status vtscsi_complete_scsi_cmd_response(struct vtscsi_softc *, + struct ccb_scsiio *, struct virtio_scsi_cmd_resp *); +static void vtscsi_complete_scsi_cmd(struct vtscsi_softc *, + struct vtscsi_request *); + +static void vtscsi_poll_ctrl_req(struct vtscsi_softc *, + struct vtscsi_request *); +static int vtscsi_execute_ctrl_req(struct vtscsi_softc *, + struct vtscsi_request *, struct sglist *, int, int, int); +static void vtscsi_complete_abort_task_cmd(struct vtscsi_softc *c, + struct vtscsi_request *); +static int vtscsi_execute_abort_task_cmd(struct vtscsi_softc *, + struct vtscsi_request *); +static int vtscsi_execute_reset_dev_cmd(struct vtscsi_softc *, + struct vtscsi_request *); + +static void vtscsi_get_request_lun(uint8_t lun[], target_id_t *, lun_id_t *); +static void vtscsi_set_request_lun(struct ccb_hdr *, uint8_t []); +static void vtscsi_init_scsi_cmd_req(struct ccb_scsiio *, + struct virtio_scsi_cmd_req *); +static void vtscsi_init_ctrl_tmf_req(struct ccb_hdr *, uint32_t, + uintptr_t, struct virtio_scsi_ctrl_tmf_req *); + +static void vtscsi_freeze_simq(struct vtscsi_softc *, int); +static int vtscsi_thaw_simq(struct vtscsi_softc *, int); + +static void vtscsi_announce(struct vtscsi_softc *, uint32_t, target_id_t, + lun_id_t); +static void vtscsi_execute_rescan(struct vtscsi_softc *, target_id_t, + lun_id_t); +static void vtscsi_execute_rescan_bus(struct vtscsi_softc *); + +static void vtscsi_handle_event(struct vtscsi_softc *, + struct virtio_scsi_event *); +static int vtscsi_enqueue_event_buf(struct vtscsi_softc *, + struct virtio_scsi_event *); +static int vtscsi_init_event_vq(struct vtscsi_softc *); +static void vtscsi_reinit_event_vq(struct vtscsi_softc *); +static void vtscsi_drain_event_vq(struct vtscsi_softc *); + +static void vtscsi_complete_vqs_locked(struct vtscsi_softc *); +static void vtscsi_complete_vqs(struct vtscsi_softc *); +static void vtscsi_drain_vqs(struct vtscsi_softc *); +static void vtscsi_cancel_request(struct vtscsi_softc *, + struct vtscsi_request *); +static void vtscsi_drain_vq(struct vtscsi_softc *, struct virtqueue *); +static void vtscsi_stop(struct vtscsi_softc *); +static int vtscsi_reset_bus(struct vtscsi_softc *); + +static void vtscsi_init_request(struct vtscsi_softc *, + struct vtscsi_request *); +static int vtscsi_alloc_requests(struct vtscsi_softc *); +static void vtscsi_free_requests(struct vtscsi_softc *); +static void vtscsi_enqueue_request(struct vtscsi_softc *, + struct vtscsi_request *); +static struct vtscsi_request * vtscsi_dequeue_request(struct vtscsi_softc *); + +static void vtscsi_complete_request(struct vtscsi_request *); +static void vtscsi_complete_vq(struct vtscsi_softc *, struct virtqueue *); +static void vtscsi_control_vq_task(void *, int); +static void vtscsi_event_vq_task(void *, int); +static void vtscsi_request_vq_task(void *, int); + +static int vtscsi_control_vq_intr(void *); +static int vtscsi_event_vq_intr(void *); +static int vtscsi_request_vq_intr(void *); +static void vtscsi_disable_vqs_intr(struct vtscsi_softc *); +static void vtscsi_enable_vqs_intr(struct vtscsi_softc *); + +static void vtscsi_get_tunables(struct vtscsi_softc *); +static void vtscsi_add_sysctl(struct vtscsi_softc *); + +static void vtscsi_printf_req(struct vtscsi_request *, const char *, + const char *, ...); + +/* Global tunables. */ +/* + * The current QEMU VirtIO SCSI implementation does not cancel in-flight + * IO during virtio_stop(). So in-flight requests still complete after the + * device reset. We would have to wait for all the in-flight IO to complete, + * which defeats the typical purpose of a bus reset. We could simulate the + * bus reset with either I_T_NEXUS_RESET of all the targets, or with + * LOGICAL_UNIT_RESET of all the LUNs (assuming there is space in the + * control virtqueue). But this isn't very useful if things really go off + * the rails, so default to disabled for now. + */ +static int vtscsi_bus_reset_disable = 1; +TUNABLE_INT("hw.vtscsi.bus_reset_disable", &vtscsi_bus_reset_disable); + +static struct virtio_feature_desc vtscsi_feature_desc[] = { + { VIRTIO_SCSI_F_INOUT, "InOut" }, + { VIRTIO_SCSI_F_HOTPLUG, "Hotplug" }, + + { 0, NULL } +}; + +static device_method_t vtscsi_methods[] = { + /* Device methods. */ + DEVMETHOD(device_probe, vtscsi_probe), + DEVMETHOD(device_attach, vtscsi_attach), + DEVMETHOD(device_detach, vtscsi_detach), + DEVMETHOD(device_suspend, vtscsi_suspend), + DEVMETHOD(device_resume, vtscsi_resume), + + DEVMETHOD_END +}; + +static driver_t vtscsi_driver = { + "vtscsi", + vtscsi_methods, + sizeof(struct vtscsi_softc) +}; +static devclass_t vtscsi_devclass; + +DRIVER_MODULE(virtio_scsi, virtio_pci, vtscsi_driver, vtscsi_devclass, + vtscsi_modevent, 0); +MODULE_VERSION(virtio_scsi, 1); +MODULE_DEPEND(virtio_scsi, virtio, 1, 1, 1); +MODULE_DEPEND(virtio_scsi, cam, 1, 1, 1); + +static int +vtscsi_modevent(module_t mod, int type, void *unused) +{ + int error; + + switch (type) { + case MOD_LOAD: + case MOD_QUIESCE: + case MOD_UNLOAD: + case MOD_SHUTDOWN: + error = 0; + break; + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} + +static int +vtscsi_probe(device_t dev) +{ + + if (virtio_get_device_type(dev) != VIRTIO_ID_SCSI) + return (ENXIO); + + device_set_desc(dev, "VirtIO SCSI Adapter"); + + return (BUS_PROBE_DEFAULT); +} + +static int +vtscsi_attach(device_t dev) +{ + struct vtscsi_softc *sc; + struct virtio_scsi_config scsicfg; + int error; + + sc = device_get_softc(dev); + sc->vtscsi_dev = dev; + + VTSCSI_LOCK_INIT(sc, device_get_nameunit(dev)); + TAILQ_INIT(&sc->vtscsi_req_free); + + vtscsi_get_tunables(sc); + vtscsi_add_sysctl(sc); + + virtio_set_feature_desc(dev, vtscsi_feature_desc); + vtscsi_negotiate_features(sc); + + if (virtio_with_feature(dev, VIRTIO_RING_F_INDIRECT_DESC)) + sc->vtscsi_flags |= VTSCSI_FLAG_INDIRECT; + if (virtio_with_feature(dev, VIRTIO_SCSI_F_INOUT)) + sc->vtscsi_flags |= VTSCSI_FLAG_BIDIRECTIONAL; + if (virtio_with_feature(dev, VIRTIO_SCSI_F_HOTPLUG)) + sc->vtscsi_flags |= VTSCSI_FLAG_HOTPLUG; + + virtio_read_device_config(dev, 0, &scsicfg, + sizeof(struct virtio_scsi_config)); + + sc->vtscsi_max_channel = scsicfg.max_channel; + sc->vtscsi_max_target = scsicfg.max_target; + sc->vtscsi_max_lun = scsicfg.max_lun; + sc->vtscsi_event_buf_size = scsicfg.event_info_size; + + vtscsi_write_device_config(sc); + + sc->vtscsi_max_nsegs = vtscsi_maximum_segments(sc, scsicfg.seg_max); + sc->vtscsi_sglist = sglist_alloc(sc->vtscsi_max_nsegs, M_NOWAIT); + if (sc->vtscsi_sglist == NULL) { + error = ENOMEM; + device_printf(dev, "cannot allocate sglist\n"); + goto fail; + } + + error = vtscsi_alloc_virtqueues(sc); + if (error) { + device_printf(dev, "cannot allocate virtqueues\n"); + goto fail; + } + + error = vtscsi_init_event_vq(sc); + if (error) { + device_printf(dev, "cannot populate the eventvq\n"); + goto fail; + } + + error = vtscsi_alloc_requests(sc); + if (error) { + device_printf(dev, "cannot allocate requests\n"); + goto fail; + } + + error = vtscsi_alloc_cam(sc); + if (error) { + device_printf(dev, "cannot allocate CAM structures\n"); + goto fail; + } + + TASK_INIT(&sc->vtscsi_control_intr_task, 0, + vtscsi_control_vq_task, sc); + TASK_INIT(&sc->vtscsi_event_intr_task, 0, + vtscsi_event_vq_task, sc); + TASK_INIT(&sc->vtscsi_request_intr_task, 0, + vtscsi_request_vq_task, sc); + + sc->vtscsi_tq = taskqueue_create_fast("vtscsi_taskq", M_NOWAIT, + taskqueue_thread_enqueue, &sc->vtscsi_tq); + if (sc->vtscsi_tq == NULL) { + error = ENOMEM; + device_printf(dev, "cannot allocate taskqueue\n"); + goto fail; + } + error = taskqueue_start_threads(&sc->vtscsi_tq, 1, PI_DISK, "%s taskq", + device_get_nameunit(dev)); + if (error) { + device_printf(dev, "cannot start taskqueue threads\n"); + goto fail; + } + + error = virtio_setup_intr(dev, INTR_TYPE_CAM); + if (error) { + device_printf(dev, "cannot setup virtqueue interrupts\n"); + goto fail; + } + + vtscsi_enable_vqs_intr(sc); + + /* + * Register with CAM after interrupts are enabled so we will get + * notified of the probe responses. + */ + error = vtscsi_register_cam(sc); + if (error) { + device_printf(dev, "cannot register with CAM\n"); + goto fail; + } + +fail: + if (error) + vtscsi_detach(dev); + + return (error); +} + +static int +vtscsi_detach(device_t dev) +{ + struct vtscsi_softc *sc; + + sc = device_get_softc(dev); + + VTSCSI_LOCK(sc); + sc->vtscsi_flags |= VTSCSI_FLAG_DETACH; + if (device_is_attached(dev)) + vtscsi_stop(sc); + VTSCSI_UNLOCK(sc); + + if (sc->vtscsi_tq != NULL) { + taskqueue_drain(sc->vtscsi_tq, &sc->vtscsi_control_intr_task); + taskqueue_drain(sc->vtscsi_tq, &sc->vtscsi_event_intr_task); + taskqueue_drain(sc->vtscsi_tq, &sc->vtscsi_request_intr_task); + taskqueue_free(sc->vtscsi_tq); + sc->vtscsi_tq = NULL; + } + + vtscsi_complete_vqs(sc); + vtscsi_drain_vqs(sc); + + vtscsi_free_cam(sc); + vtscsi_free_requests(sc); + + if (sc->vtscsi_sglist != NULL) { + sglist_free(sc->vtscsi_sglist); + sc->vtscsi_sglist = NULL; + } + + VTSCSI_LOCK_DESTROY(sc); + + return (0); +} + +static int +vtscsi_suspend(device_t dev) +{ + + return (0); +} + +static int +vtscsi_resume(device_t dev) +{ + + return (0); +} + +static void +vtscsi_negotiate_features(struct vtscsi_softc *sc) +{ + device_t dev; + uint64_t features; + + dev = sc->vtscsi_dev; + features = virtio_negotiate_features(dev, VTSCSI_FEATURES); + sc->vtscsi_features = features; +} + +static int +vtscsi_maximum_segments(struct vtscsi_softc *sc, int seg_max) +{ + int nsegs; + + nsegs = VTSCSI_MIN_SEGMENTS; + + if (seg_max > 0) { + nsegs += MIN(seg_max, MAXPHYS / PAGE_SIZE + 1); + if (sc->vtscsi_flags & VTSCSI_FLAG_INDIRECT) + nsegs = MIN(nsegs, VIRTIO_MAX_INDIRECT); + } else + nsegs += 1; + + return (nsegs); +} + +static int +vtscsi_alloc_virtqueues(struct vtscsi_softc *sc) +{ + device_t dev; + struct vq_alloc_info vq_info[3]; + int nvqs; + + dev = sc->vtscsi_dev; + nvqs = 3; + + VQ_ALLOC_INFO_INIT(&vq_info[0], 0, vtscsi_control_vq_intr, sc, + &sc->vtscsi_control_vq, "%s control", device_get_nameunit(dev)); + + VQ_ALLOC_INFO_INIT(&vq_info[1], 0, vtscsi_event_vq_intr, sc, + &sc->vtscsi_event_vq, "%s event", device_get_nameunit(dev)); + + VQ_ALLOC_INFO_INIT(&vq_info[2], sc->vtscsi_max_nsegs, + vtscsi_request_vq_intr, sc, &sc->vtscsi_request_vq, + "%s request", device_get_nameunit(dev)); + + return (virtio_alloc_virtqueues(dev, 0, nvqs, vq_info)); +} + +static void +vtscsi_write_device_config(struct vtscsi_softc *sc) +{ + + virtio_write_dev_config_4(sc->vtscsi_dev, + offsetof(struct virtio_scsi_config, sense_size), + VIRTIO_SCSI_SENSE_SIZE); + + /* + * This is the size in the virtio_scsi_cmd_req structure. Note + * this value (32) is larger than the maximum CAM CDB size (16). + */ + virtio_write_dev_config_4(sc->vtscsi_dev, + offsetof(struct virtio_scsi_config, cdb_size), + VIRTIO_SCSI_CDB_SIZE); +} + +static int +vtscsi_reinit(struct vtscsi_softc *sc) +{ + device_t dev; + int error; + + dev = sc->vtscsi_dev; + + error = virtio_reinit(dev, sc->vtscsi_features); + if (error == 0) { + vtscsi_write_device_config(sc); + vtscsi_reinit_event_vq(sc); + virtio_reinit_complete(dev); + + vtscsi_enable_vqs_intr(sc); + } + + vtscsi_dprintf(sc, VTSCSI_TRACE, "error=%d\n", error); + + return (error); +} + +static int +vtscsi_alloc_cam(struct vtscsi_softc *sc) +{ + device_t dev; + struct cam_devq *devq; + int openings; + + dev = sc->vtscsi_dev; + openings = sc->vtscsi_nrequests - VTSCSI_RESERVED_REQUESTS; + + devq = cam_simq_alloc(openings); + if (devq == NULL) { + device_printf(dev, "cannot allocate SIM queue\n"); + return (ENOMEM); + } + + sc->vtscsi_sim = cam_sim_alloc(vtscsi_cam_action, vtscsi_cam_poll, + "vtscsi", sc, device_get_unit(dev), VTSCSI_MTX(sc), 1, + openings, devq); + if (sc->vtscsi_sim == NULL) { + cam_simq_free(devq); + device_printf(dev, "cannot allocate SIM\n"); + return (ENOMEM); + } + + return (0); +} + +static int +vtscsi_register_cam(struct vtscsi_softc *sc) +{ + device_t dev; + int registered, error; + + dev = sc->vtscsi_dev; + registered = 0; + + VTSCSI_LOCK(sc); + + if (xpt_bus_register(sc->vtscsi_sim, dev, 0) != CAM_SUCCESS) { + error = ENOMEM; + device_printf(dev, "cannot register XPT bus\n"); + goto fail; + } + + registered = 1; + + if (xpt_create_path(&sc->vtscsi_path, NULL, + cam_sim_path(sc->vtscsi_sim), CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + error = ENOMEM; + device_printf(dev, "cannot create bus path\n"); + goto fail; + } + + VTSCSI_UNLOCK(sc); + + /* + * The async register apparently needs to be done without + * the lock held, otherwise it can recurse on the lock. + */ + if (vtscsi_register_async(sc) != CAM_REQ_CMP) { + error = EIO; + device_printf(dev, "cannot register async callback\n"); + VTSCSI_LOCK(sc); + goto fail; + } + + return (0); + +fail: + if (sc->vtscsi_path != NULL) { + xpt_free_path(sc->vtscsi_path); + sc->vtscsi_path = NULL; + } + + if (registered != 0) + xpt_bus_deregister(cam_sim_path(sc->vtscsi_sim)); + + VTSCSI_UNLOCK(sc); + + return (error); +} + +static void +vtscsi_free_cam(struct vtscsi_softc *sc) +{ + + VTSCSI_LOCK(sc); + + if (sc->vtscsi_path != NULL) { + vtscsi_deregister_async(sc); + + xpt_free_path(sc->vtscsi_path); + sc->vtscsi_path = NULL; + + xpt_bus_deregister(cam_sim_path(sc->vtscsi_sim)); + } + + if (sc->vtscsi_sim != NULL) { + cam_sim_free(sc->vtscsi_sim, 1); + sc->vtscsi_sim = NULL; + } + + VTSCSI_UNLOCK(sc); +} + +static void +vtscsi_cam_async(void *cb_arg, uint32_t code, struct cam_path *path, void *arg) +{ + struct cam_sim *sim; + struct vtscsi_softc *sc; + + sim = cb_arg; + sc = cam_sim_softc(sim); + + vtscsi_dprintf(sc, VTSCSI_TRACE, "code=%u\n", code); + + /* + * TODO Once QEMU supports event reporting, we should + * (un)subscribe to events here. + */ + switch (code) { + case AC_FOUND_DEVICE: + break; + case AC_LOST_DEVICE: + break; + } +} + +static int +vtscsi_register_async(struct vtscsi_softc *sc) +{ + struct ccb_setasync csa; + + VTSCSI_LOCK_NOTOWNED(sc); + + xpt_setup_ccb(&csa.ccb_h, sc->vtscsi_path, 5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE | AC_FOUND_DEVICE; + csa.callback = vtscsi_cam_async; + csa.callback_arg = sc->vtscsi_sim; + + xpt_action((union ccb *) &csa); + + return (csa.ccb_h.status); +} + +static void +vtscsi_deregister_async(struct vtscsi_softc *sc) +{ + struct ccb_setasync csa; + + xpt_setup_ccb(&csa.ccb_h, sc->vtscsi_path, 5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = 0; + csa.callback = vtscsi_cam_async; + csa.callback_arg = sc->vtscsi_sim; + + xpt_action((union ccb *) &csa); +} + +static void +vtscsi_cam_action(struct cam_sim *sim, union ccb *ccb) +{ + struct vtscsi_softc *sc; + struct ccb_hdr *ccbh; + + sc = cam_sim_softc(sim); + ccbh = &ccb->ccb_h; + + VTSCSI_LOCK_OWNED(sc); + + if (sc->vtscsi_flags & VTSCSI_FLAG_DETACH) { + /* + * The VTSCSI_MTX is briefly dropped between setting + * VTSCSI_FLAG_DETACH and deregistering with CAM, so + * drop any CCBs that come in during that window. + */ + ccbh->status = CAM_NO_HBA; + xpt_done(ccb); + return; + } + + switch (ccbh->func_code) { + case XPT_SCSI_IO: + vtscsi_cam_scsi_io(sc, sim, ccb); + break; + + case XPT_SET_TRAN_SETTINGS: + ccbh->status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + + case XPT_GET_TRAN_SETTINGS: + vtscsi_cam_get_tran_settings(sc, ccb); + break; + + case XPT_RESET_BUS: + vtscsi_cam_reset_bus(sc, ccb); + break; + + case XPT_RESET_DEV: + vtscsi_cam_reset_dev(sc, ccb); + break; + + case XPT_ABORT: + vtscsi_cam_abort(sc, ccb); + break; + + case XPT_CALC_GEOMETRY: + cam_calc_geometry(&ccb->ccg, 1); + xpt_done(ccb); + break; + + case XPT_PATH_INQ: + vtscsi_cam_path_inquiry(sc, sim, ccb); + break; + + default: + vtscsi_dprintf(sc, VTSCSI_ERROR, + "invalid ccb=%p func=%#x\n", ccb, ccbh->func_code); + + ccbh->status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +} + +static void +vtscsi_cam_poll(struct cam_sim *sim) +{ + struct vtscsi_softc *sc; + + sc = cam_sim_softc(sim); + + vtscsi_complete_vqs_locked(sc); +} + +static void +vtscsi_cam_scsi_io(struct vtscsi_softc *sc, struct cam_sim *sim, + union ccb *ccb) +{ + struct ccb_hdr *ccbh; + struct ccb_scsiio *csio; + int error; + + ccbh = &ccb->ccb_h; + csio = &ccb->csio; + + if (csio->cdb_len > VIRTIO_SCSI_CDB_SIZE) { + error = EINVAL; + ccbh->status = CAM_REQ_INVALID; + goto done; + } + + if ((ccbh->flags & CAM_DIR_MASK) == CAM_DIR_BOTH && + (sc->vtscsi_flags & VTSCSI_FLAG_BIDIRECTIONAL) == 0) { + error = EINVAL; + ccbh->status = CAM_REQ_INVALID; + goto done; + } + + error = vtscsi_start_scsi_cmd(sc, ccb); + +done: + if (error) { + vtscsi_dprintf(sc, VTSCSI_ERROR, + "error=%d ccb=%p status=%#x\n", error, ccb, ccbh->status); + xpt_done(ccb); + } +} + +static void +vtscsi_cam_get_tran_settings(struct vtscsi_softc *sc, union ccb *ccb) +{ + struct ccb_trans_settings *cts; + struct ccb_trans_settings_scsi *scsi; + + cts = &ccb->cts; + scsi = &cts->proto_specific.scsi; + + cts->protocol = PROTO_SCSI; + cts->protocol_version = SCSI_REV_SPC3; + cts->transport = XPORT_SAS; + cts->transport_version = 0; + + scsi->valid = CTS_SCSI_VALID_TQ; + scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); +} + +static void +vtscsi_cam_reset_bus(struct vtscsi_softc *sc, union ccb *ccb) +{ + int error; + + error = vtscsi_reset_bus(sc); + if (error == 0) + ccb->ccb_h.status = CAM_REQ_CMP; + else + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + + vtscsi_dprintf(sc, VTSCSI_TRACE, "error=%d ccb=%p status=%#x\n", + error, ccb, ccb->ccb_h.status); + + xpt_done(ccb); +} + +static void +vtscsi_cam_reset_dev(struct vtscsi_softc *sc, union ccb *ccb) +{ + struct ccb_hdr *ccbh; + struct vtscsi_request *req; + int error; + + ccbh = &ccb->ccb_h; + + req = vtscsi_dequeue_request(sc); + if (req == NULL) { + error = EAGAIN; + vtscsi_freeze_simq(sc, VTSCSI_REQUEST); + goto fail; + } + + req->vsr_ccb = ccb; + + error = vtscsi_execute_reset_dev_cmd(sc, req); + if (error == 0) + return; + + vtscsi_enqueue_request(sc, req); + +fail: + vtscsi_dprintf(sc, VTSCSI_ERROR, "error=%d req=%p ccb=%p\n", + error, req, ccb); + + if (error == EAGAIN) + ccbh->status = CAM_RESRC_UNAVAIL; *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***