From patchwork Mon Jun 14 09:44:46 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Nicholas A. Bellinger" X-Patchwork-Id: 105908 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.3) with ESMTP id o5E9iNmd029234 for ; Mon, 14 Jun 2010 09:44:24 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755924Ab0FNJoV (ORCPT ); Mon, 14 Jun 2010 05:44:21 -0400 Received: from smtp127.sbc.mail.sp1.yahoo.com ([69.147.65.186]:29083 "HELO smtp127.sbc.mail.sp1.yahoo.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with SMTP id S1755898Ab0FNJoU (ORCPT ); Mon, 14 Jun 2010 05:44:20 -0400 Received: (qmail 58874 invoked from network); 14 Jun 2010 09:44:18 -0000 Received: from adsl-70-231-239-201.dsl.snfc21.sbcglobal.net (nab@70.231.239.201 with login) by smtp127.sbc.mail.sp1.yahoo.com with SMTP; 14 Jun 2010 02:44:17 -0700 PDT X-Yahoo-SMTP: fzDSGlOswBCWnIOrNw7KwwK1j9PqyNbe5PtLKiS4dDU.UNl_t6bdEZu9tTLW X-YMail-OSG: J5fuqxcVM1lHiY4otpJz0zfXYU0BpoxCv2YEJdEflyQ_eTqXIRY0cNbOSQQK9kRFlepjKYieAO8LpsIuNhax1DzEtfxyHgiFuyE6p3a_35bV.FxRzo8.eXYoojlL1y3OJY0hByaC3hM9Btff3sz4zSsYakWG8AVRyHmTBeuHrRjzaCG_sKhV62_XaLWe2ocMbmNWd0w.SSQo7pBiril3NqfsgYNoS1drouzI8QXJ8LSuhYBRhxSxzg5EJc4Rg7zW3jNoIt55_x9DMn05UE1bcqWSHpDamPedSByPkPyvfAv1lMqWbXAYredKHe6oOT.0kXtI06H6b3KMqzScTQQHZ9tB6WfrDOcJQscSrf9oFD1IhQ-- X-Yahoo-Newman-Property: ymail-3 From: "Nicholas A. Bellinger" To: Gerd Hoffmann , Kevin Wolf , FUJITA Tomonori Cc: Hannes Reinecke , Paul Brook , Christoph Hellwig , kvm-devel , qemu-devel , Nicholas Bellinger Subject: [PATCH 5/5] [scsi-bsg]: Add initial support for BSG based SCSIDeviceInfo Date: Mon, 14 Jun 2010 02:44:46 -0700 Message-Id: <1276508686-3100-1-git-send-email-nab@linux-iscsi.org> X-Mailer: git-send-email 1.5.6.5 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Mon, 14 Jun 2010 09:44:24 +0000 (UTC) diff --git a/Makefile.objs b/Makefile.objs index 188d617..c4fcb72 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,7 +197,7 @@ hw-obj-$(CONFIG_IDE_CMD646) += ide/cmd646.o hw-obj-$(CONFIG_IDE_MACIO) += ide/macio.o # SCSI layer -hw-obj-y += scsi-disk.o scsi-generic.o +hw-obj-y += scsi-disk.o scsi-generic.o scsi-bsg.o hw-obj-y += lsi53c895a.o megasas.o hw-obj-$(CONFIG_ESP) += esp.o diff --git a/hw/scsi-bsg.c b/hw/scsi-bsg.c new file mode 100644 index 0000000..fc76b76 --- /dev/null +++ b/hw/scsi-bsg.c @@ -0,0 +1,588 @@ +/* + * block layer implementation of the sg v4 interface for Linux hosts + * + * Copyright (c) 2010 Rising Tide Systems + * Written by Nicholas A. Bellinger + * + * Based on hw/scsi-generic code by Laurent Vivier, Paul Brook, and Fabrice Bellard + * + * This code is licenced under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "block.h" +#include "scsi.h" +#include "dma.h" +#include "block/raw-posix-aio.h" + +#ifdef __linux__ + +#define DEBUG_BSG +#undef DEBUG_BSG_IO +#undef DEBUG_BSG_MAP + +#ifdef DEBUG_BSG +#define DPRINTF(fmt, ...) \ +do { printf("scsi-bsg: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +#define BADF(fmt, ...) \ +do { fprintf(stderr, "scsi-bsg: " fmt , ## __VA_ARGS__); } while (0) + +#include +#include +#include +#include +#include +#include +#include +#include "scsi-defs.h" + +#define SCSI_SENSE_BUF_SIZE 96 + +#define SG_ERR_DRIVER_TIMEOUT 0x06 +#define SG_ERR_DRIVER_SENSE 0x08 + +#ifndef MAX_UINT +#define MAX_UINT ((unsigned int)-1) +#endif + +typedef struct SCSIBSGState SCSIBSGState; + +typedef struct SCSIBSGReq { + SCSIRequest req; + uint8_t *buf; + int buflen; + QEMUIOVector iov; + QEMUIOVector aio_iov; + struct sg_io_v4 bsg_hdr; +} SCSIBSGReq; + +struct SCSIBSGState { + SCSIDevice qdev; + BlockDriverState *bs; + int lun; + int driver_status; + uint8_t sensebuf[SCSI_SENSE_BUF_SIZE]; + uint8_t senselen; +}; + +static int bsg_read(int fd, void *p_read, int to_read) +{ + int err; + + while (to_read > 0) { + err = read(fd, p_read, to_read); + if (err >= 0) { + to_read -= err; + p_read += err; + } else if (errno == EINTR) + continue; + else { + printf("bsg device %d read failed, errno: %d\n", + fd, errno); + return errno; + } + } + return 0; +} + +static SCSIBSGReq *bsg_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun) +{ + SCSIRequest *req; + SCSIBSGReq *r; + + req = scsi_req_alloc(sizeof(SCSIBSGReq), d, tag, lun); + r = DO_UPCAST(SCSIBSGReq, req, req); + qemu_iovec_init(&r->iov, 1); + qemu_iovec_init(&r->aio_iov, 1); + return r; +} + +static void bsg_remove_request(SCSIBSGReq *r) +{ + qemu_free(r->buf); + qemu_iovec_destroy(&r->iov); + qemu_iovec_destroy(&r->aio_iov); + scsi_req_free(&r->req); +} + +static void bsg_command_complete(void *opaque, int ret) +{ + SCSIBSGReq *r = (SCSIBSGReq *)opaque; + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev); + + s->driver_status = r->bsg_hdr.driver_status; + if (s->driver_status) + s->senselen = SCSI_SENSE_BUF_SIZE; + + if (ret != 0) { + scsi_req_print(&r->req); + fprintf(stderr, "%s: ret %d (%s)\n", __FUNCTION__, + ret, strerror(-ret)); + s->senselen = scsi_build_sense(SENSE_CODE(INVALID_FIELD), + s->sensebuf, SCSI_SENSE_BUF_SIZE, 0); + s->driver_status = SG_ERR_DRIVER_SENSE; + r->req.status = CHECK_CONDITION; + } else { + if (s->driver_status & SG_ERR_DRIVER_TIMEOUT) { + scsi_req_print(&r->req); + fprintf(stderr, "%s: timeout\n", __FUNCTION__); + r->req.status = BUSY << 1; + } else if (r->bsg_hdr.device_status) { + r->req.status = r->bsg_hdr.device_status; + } else if (s->driver_status & SG_ERR_DRIVER_SENSE) { + scsi_req_print(&r->req); + fprintf(stderr, "%s: driver sense\n", __FUNCTION__); + r->req.status = CHECK_CONDITION << 1; + } else { + r->req.status = GOOD << 1; + } + } +#ifdef DEBUG_BSG_IO + DPRINTF("Command complete 0x%p tag=0x%x status=%d\n", + r, r->req.tag, r->req.status); +#endif + scsi_req_complete(&r->req); +} + +static int bsg_execute_command_run(SCSIBSGReq *r, + BlockDriverCompletionFunc *complete) +{ + BlockDriverState *bdrv = r->req.dev->conf.dinfo->bdrv; + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev); + /* + * Following linux/include/linux/bsg.h + */ + /* [i] 'Q' to differentiate from v3 */ + r->bsg_hdr.guard = 'Q'; + r->bsg_hdr.protocol = BSG_PROTOCOL_SCSI; + r->bsg_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + r->bsg_hdr.request_len = r->req.cmd.len; + r->bsg_hdr.request = (unsigned long)r->req.cmd.buf; + r->bsg_hdr.max_response_len = sizeof(s->sensebuf); + /* SCSI: (auto)sense data */ + r->bsg_hdr.response = (unsigned long)s->sensebuf; + /* Unlimited timeout */ + r->bsg_hdr.timeout = MAX_UINT; + /* [i->o] unused internally */ + r->bsg_hdr.usr_ptr = (unsigned long)r; + /* Bsg does Q_AT_HEAD by default */ + r->bsg_hdr.flags |= BSG_FLAG_Q_AT_TAIL; + + qemu_iovec_reset(&r->aio_iov); + qemu_iovec_add(&r->aio_iov, &r->bsg_hdr, sizeof(r->bsg_hdr)); + + r->req.aiocb = paio_submit_len(bdrv, bdrv->fd, 0, &r->aio_iov, + sizeof(r->bsg_hdr), complete, r, QEMU_AIO_WRITE); + if (r->req.aiocb == NULL) { + BADF("execute_command: paio_submit_len() failed\n"); + return -1; + } + + return 0; +} + +static int bsg_execute_command_buf(SCSIBSGReq *r, + BlockDriverCompletionFunc *complete, + uint8_t *buf, uint32_t buflen) +{ + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { + r->bsg_hdr.dout_xferp = (unsigned long)buf; + r->bsg_hdr.dout_xfer_len = buflen; + } else if (r->req.cmd.mode == SCSI_XFER_FROM_DEV) { + r->bsg_hdr.din_xferp = (unsigned long)buf; + r->bsg_hdr.din_xfer_len = buflen; + } +#ifdef DEBUG_BSG_IO + DPRINTF("execute BUF: %p, dxfer_len %u\n", buf, buflen); +#endif + return bsg_execute_command_run(r, complete); +} + +static int bsg_execute_command_iov(SCSIBSGReq *r, + BlockDriverCompletionFunc *complete, + QEMUIOVector *iov) +{ + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { + r->bsg_hdr.dout_iovec_count = iov->niov; + r->bsg_hdr.dout_xferp = (unsigned long)iov->iov; + r->bsg_hdr.dout_xfer_len = iov->size; + } else if (r->req.cmd.mode == SCSI_XFER_FROM_DEV) { + r->bsg_hdr.din_iovec_count = iov->niov; + r->bsg_hdr.din_xferp = (unsigned long)iov->iov; + r->bsg_hdr.din_xfer_len = iov->size; + } +#ifdef DEBUG_BSG_IO + DPRINTF("execute IOV: iovec_count: %u, iov: %p, size: %u\n", + iov->niov, iov->iov, (unsigned int)iov->size); +#endif + return bsg_execute_command_run(r, complete); +} + +static void bsg_write_complete(void *opaque, int ret) +{ + SCSIBSGReq *r = (SCSIBSGReq *)opaque; + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev); +#ifdef DEBUG_BSG_IO + DPRINTF("bsg_write_complete() ret = %d\n", ret); +#endif + if (ret) { + DPRINTF("IO error\n"); + bsg_command_complete(r, ret); + return; + } + /* + * Copied from hw/scsi-generic.c:scsi_write_complete(), is this still + * necessary for BSG..? + */ + if (r->req.cmd.buf[0] == MODE_SELECT && r->req.cmd.buf[4] == 12 && + s->qdev.type == TYPE_TAPE) { + s->qdev.blocksize = (r->buf[9] << 16) | (r->buf[10] << 8) | r->buf[11]; + DPRINTF("block size %d\n", s->qdev.blocksize); + } + + bsg_command_complete(r, ret); +} + +static void bsg_req_fixup(SCSIRequest *req) +{ + return; +} + +static int bsg_get_blocksize(BlockDriverState *bdrv) +{ + uint8_t cmd[10]; + uint8_t buf[8]; + uint8_t sensebuf[8]; + struct sg_io_v4 bsg_hdr; + int ret; + + memset(cmd, 0, sizeof(cmd)); + memset(buf, 0, sizeof(buf)); + cmd[0] = READ_CAPACITY; + + memset(&bsg_hdr, 0, sizeof(bsg_hdr)); + bsg_hdr.guard = 'Q'; + bsg_hdr.protocol = BSG_PROTOCOL_SCSI; + bsg_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + bsg_hdr.request_len = sizeof(cmd); + bsg_hdr.request = (unsigned long)cmd; + bsg_hdr.din_xfer_len = sizeof(buf); + bsg_hdr.din_xferp = (unsigned long)buf; + bsg_hdr.max_response_len = sizeof(sensebuf); + bsg_hdr.response = (unsigned long)sensebuf; + bsg_hdr.timeout = 6000; /* XXX */ + + ret = bdrv_ioctl(bdrv, SG_IO, (void *)&bsg_hdr); + if (ret < 0) + return -1; + + return (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; +} + +static int bsg_get_stream_blocksize(BlockDriverState *bdrv) +{ + uint8_t cmd[6]; + uint8_t buf[12]; + uint8_t sensebuf[8]; + struct sg_io_v4 bsg_hdr; + int ret; + + memset(cmd, 0, sizeof(cmd)); + memset(buf, 0, sizeof(buf)); + cmd[0] = MODE_SENSE; + cmd[4] = sizeof(buf); + + memset(&bsg_hdr, 0, sizeof(bsg_hdr)); + bsg_hdr.guard = 'Q'; + bsg_hdr.protocol = BSG_PROTOCOL_SCSI; + bsg_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + bsg_hdr.request_len = sizeof(cmd); + bsg_hdr.request = (unsigned long)cmd; + bsg_hdr.din_xfer_len = sizeof(buf); + bsg_hdr.din_xferp = (unsigned long)buf; + bsg_hdr.max_response_len = sizeof(sensebuf); + bsg_hdr.response = (unsigned long)sensebuf; + bsg_hdr.timeout = 6000; /* XXX */ + + ret = bdrv_ioctl(bdrv, SG_IO, (void *)&bsg_hdr); + if (ret < 0) + return -1; + + return (buf[9] << 16) | (buf[10] << 8) | buf[11]; +} + +static void bsg_destroy(SCSIDevice *d) +{ + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, d); + SCSIBSGReq *r; + + while (!QTAILQ_EMPTY(&s->qdev.requests)) { + r = DO_UPCAST(SCSIBSGReq, req, QTAILQ_FIRST(&s->qdev.requests)); + bsg_remove_request(r); + } + drive_uninit(s->qdev.conf.dinfo); +} + +struct scsi_idlun { + uint32_t dev_id; + uint32_t host_unique_id; +}; + +static int bsg_generic_initfn(SCSIDevice *dev) +{ + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, dev); + + if (!s->qdev.conf.dinfo || !s->qdev.conf.dinfo->bdrv) { + error_report("scsi-bsg: drive property not set"); + return -1; + } + s->bs = s->qdev.conf.dinfo->bdrv; + + /* check we are really using a /dev/bsg/ * file */ + if (!bdrv_is_bsg(s->bs)) { + error_report("scsi-bsg: not BSG*"); + return -1; + } +#if 0 + /* get LUN of the BSG */ + if (bdrv_ioctl(s->bs, SG_GET_SCSI_ID, &scsiid)) { + error_report("scsi-bsg: SG_GET_SCSI_ID ioctl failed"); + return -1; + } +#endif +// FIXME: Get SCSI lun from BSG + s->lun = 0; +// FIXME: Get SCSI device type from BSG INQUIRY + s->qdev.type = TYPE_DISK; + DPRINTF("LUN %d\n", s->lun); + DPRINTF("device type %d\n", s->qdev.type); + + if (s->qdev.type == TYPE_TAPE) { + s->qdev.blocksize = bsg_get_stream_blocksize(s->bs); + if (s->qdev.blocksize == -1) + s->qdev.blocksize = 0; + } else { + s->qdev.blocksize = bsg_get_blocksize(s->bs); + /* removable media returns 0 if not present */ + if (s->qdev.blocksize <= 0) { + if (s->qdev.type == TYPE_ROM || s->qdev.type == TYPE_WORM) + s->qdev.blocksize = 2048; + else + s->qdev.blocksize = 512; + } + } + DPRINTF("block size %d\n", s->qdev.blocksize); + s->driver_status = 0; + memset(s->sensebuf, 0, sizeof(s->sensebuf)); + return 0; +} + +static void bsg_generic_unmap(SCSIBSGReq *r) +{ + int is_write = !scsi_req_is_write(&r->req); + int i; + + for (i = 0; i < r->iov.niov; i++) { + cpu_physical_memory_unmap(r->iov.iov[i].iov_base, + r->iov.iov[i].iov_len, is_write, + r->iov.iov[i].iov_len); + } + qemu_iovec_reset(&r->iov); +} + +static int bsg_generic_map(SCSIBSGReq *r, QEMUSGList *sg) +{ + int is_write = !scsi_req_is_write(&r->req); + target_phys_addr_t cur_addr, cur_len, cur_offset = 0; + void *mem; + int i; + + qemu_iovec_reset(&r->iov); + for (i = 0; i < sg->nsg;) { + cur_addr = sg->sg[i].base + cur_offset; + cur_len = sg->sg[i].len - cur_offset; +#ifdef DEBUG_BSG_MAP + DPRINTF("Using cur_addr: 0x%016lx cur_len: 0x%016lx\n", + (long unsigned int)cur_addr, (long unsigned int)cur_len); +#endif + mem = cpu_physical_memory_map(cur_addr, &cur_len, is_write); + if (!mem) + goto err; +#ifdef DEBUG_BSG_MAP + DPRINTF("Adding iovec for mem: %p len: 0x%016lx\n", mem, + (long unsigned int)cur_len); +#endif + qemu_iovec_add(&r->iov, mem, cur_len); + + cur_offset += cur_len; + if (cur_offset == sg->sg[i].len) { + cur_offset = 0; + i++; + } + } + + return 0; + +err: + bsg_generic_unmap(r); + return -1; +} + +static SCSIRequest *bsg_generic_req_get(SCSIDevice *d, uint32_t tag, int lun) +{ + SCSIBSGReq *r; + + r = bsg_new_request(d, tag, lun); + return &r->req; +} + +static void bsg_generic_req_cb(void *opaque, int ret) +{ + SCSIRequest *req = opaque; + SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req); + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev); + BlockDriverCompletionFunc *complete; + struct sg_io_v4 io_hdr; + int err; + + req->aiocb = NULL; + + memset(&io_hdr, 0, sizeof(io_hdr)); + /* [i] 'Q' to differentiate from v3 */ + io_hdr.guard = 'Q'; + + err = bsg_read(s->bs->fd, &io_hdr, sizeof(io_hdr)); + if (err) { + printf("bsg_read() failed with ret: %d\n", err); + return; + } + + if (r->iov.niov) + bsg_generic_unmap(r); + + if (scsi_req_is_write(req)) { + req->xferlen = r->bsg_hdr.dout_xfer_len; + complete = bsg_write_complete; + } else { + req->xferlen = r->bsg_hdr.din_xfer_len; + complete = bsg_command_complete; + } + complete(opaque, ret); +} + +static int bsg_generic_req_common(SCSIRequest *req, uint8_t *buffer) +{ + SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req); + SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev); + + bsg_req_fixup(&r->req); + if (req->cmd.buf[0] != REQUEST_SENSE && + (req->lun != s->lun || (req->cmd.buf[1] >> 5) != s->lun)) { + + DPRINTF("Unimplemented LUN %d\n", + req->lun ? req->lun : req->cmd.buf[1] >> 5); + s->senselen = scsi_build_sense(SENSE_CODE(LUN_NOT_SUPPORTED), + s->sensebuf, SCSI_SENSE_BUF_SIZE, 0); + s->driver_status = SG_ERR_DRIVER_SENSE; + req->status = CHECK_CONDITION << 1; + return 1; + } + if (r->req.cmd.buf[0] == REQUEST_SENSE && + s->driver_status & SG_ERR_DRIVER_SENSE) { + req->xferlen = MIN(req->cmd.xfer, s->senselen); + memcpy(buffer, s->sensebuf, req->xferlen); + DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, s->senselen); + DPRINTF("Sense: %d %d %d %d %d %d %d %d\n", + buffer[0], buffer[1], buffer[2], buffer[3], + buffer[4], buffer[5], buffer[6], buffer[7]); + req->status = GOOD; + return 1; + } + return 0; +} + +static int bsg_generic_req_buf(SCSIRequest *req, uint8_t *buffer) +{ + SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req); + int handled, ret; + + handled = bsg_generic_req_common(req, buffer); + if (handled) { + scsi_req_complete(req); + return 0; + } + + req->xferlen = req->cmd.xfer; + ret = bsg_execute_command_buf(r, bsg_generic_req_cb, buffer, req->xferlen); + if (ret == -1) { + bsg_command_complete(r, -EINVAL); + } + return 0; +} + +static int bsg_generic_req_sgl(SCSIRequest *req, QEMUSGList *sg) +{ + SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req); + int handled, ret; + + if (bsg_generic_map(r, sg) != 0) { + /* Hmm ... */ + abort(); + } + handled = bsg_generic_req_common(req, r->iov.iov[0].iov_base); + if (handled) { + bsg_generic_unmap(r); + scsi_req_complete(req); + return 0; + } + + req->xferlen = req->cmd.xfer; + ret = bsg_execute_command_iov(r, bsg_generic_req_cb, &r->iov); + if (ret == -1) { + bsg_generic_unmap(r); + bsg_command_complete(r, -EINVAL); + } + return 0; +} + +static void bsg_generic_req_put(SCSIRequest *req) +{ + SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req); + + if (r->req.aiocb) { + bdrv_aio_cancel(r->req.aiocb); + } + bsg_remove_request(r); +} + +static SCSIDeviceInfo bsg_info = { + .qdev.name = "scsi-bsg", + .qdev.desc = "pass through block layer scsi generic (/dev/bsg/*)", + .qdev.size = sizeof(SCSIBSGState), + .init = bsg_generic_initfn, + .destroy = bsg_destroy, + + /* new */ + .request_get = bsg_generic_req_get, + .request_buf = bsg_generic_req_buf, + .request_sgl = bsg_generic_req_sgl, + .request_put = bsg_generic_req_put, + + .qdev.props = (Property[]) { + DEFINE_BLOCK_PROPERTIES(SCSIBSGState, qdev.conf), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void bsg_register_devices(void) +{ + scsi_qdev_register(&bsg_info); +} +device_init(bsg_register_devices) + +#endif /* __linux__ */