// SPDX-License-Identifier: GPL-2.0-only/* sunvdc.c: Sun LDOM Virtual Disk Client. * * Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net> */ #include <linux/module.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/blk-mq.h>#include <linux/hdreg.h>#include <linux/cdrom.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/completion.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/list.h>#include <linux/scatterlist.h> #include <asm/vio.h>#include <asm/ldc.h> #define DRV_MODULE_NAME "sunvdc"#define PFX DRV_MODULE_NAME ": "#define DRV_MODULE_VERSION "1.2"#define DRV_MODULE_RELDATE "November 24, 2014" static char version[] = DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");MODULE_DESCRIPTION("Sun LDOM virtual disk client driver");MODULE_LICENSE("GPL");MODULE_VERSION(DRV_MODULE_VERSION); #define VDC_TX_RING_SIZE 512#define VDC_DEFAULT_BLK_SIZE 512 #define MAX_XFER_BLKS (128 * 1024)#define MAX_XFER_SIZE (MAX_XFER_BLKS / VDC_DEFAULT_BLK_SIZE)#define MAX_RING_COOKIES ((MAX_XFER_BLKS / PAGE_SIZE) + 2) #define WAITING_FOR_LINK_UP 0x01#define WAITING_FOR_TX_SPACE 0x02#define WAITING_FOR_GEN_CMD 0x04#define WAITING_FOR_ANY -1 #define VDC_MAX_RETRIES 10 static struct workqueue_struct *sunvdc_wq; struct vdc_req_entry { struct request *req;}; struct vdc_port { struct vio_driver_state vio; struct gendisk *disk; struct vdc_completion *cmp; u64 req_id; u64 seq; struct vdc_req_entry rq_arr[VDC_TX_RING_SIZE]; unsigned long ring_cookies; u64 max_xfer_size; u32 vdisk_block_size; u32 drain; u64 ldc_timeout; struct delayed_work ldc_reset_timer_work; struct work_struct ldc_reset_work; /* The server fills these in for us in the disk attribute * ACK packet. */ u64 operations; u32 vdisk_size; u8 vdisk_type; u8 vdisk_mtype; u32 vdisk_phys_blksz; struct blk_mq_tag_set tag_set; char disk_name[32];}; static void vdc_ldc_reset(struct vdc_port *port);static void vdc_ldc_reset_work(struct work_struct *work);static void vdc_ldc_reset_timer_work(struct work_struct *work); static inline struct vdc_port *to_vdc_port(struct vio_driver_state *vio){ return container_of(vio, struct vdc_port, vio);} /* Ordered from largest major to lowest */static struct vio_version vdc_versions[] = { { .major = 1, .minor = 2 }, { .major = 1, .minor = 1 }, { .major = 1, .minor = 0 },}; static inline int vdc_version_supported(struct vdc_port *port, u16 major, u16 minor){ return port->vio.ver.major == major && port->vio.ver.minor >= minor;} #define VDCBLK_NAME "vdisk"static int vdc_major;#define PARTITION_SHIFT 3 static inline u32 vdc_tx_dring_avail(struct vio_dring_state *dr){ return vio_dring_avail(dr, VDC_TX_RING_SIZE);} static int vdc_getgeo(struct gendisk *disk, struct hd_geometry *geo){ sector_t nsect = get_capacity(disk); sector_t cylinders = nsect; geo->heads = 0xff; geo->sectors = 0x3f; sector_div(cylinders, geo->heads * geo->sectors); geo->cylinders = cylinders; if ((sector_t)(geo->cylinders + 1) * geo->heads * geo->sectors < nsect) geo->cylinders = 0xffff; return 0;} /* Add ioctl/CDROM_GET_CAPABILITY to support cdrom_id in udev * when vdisk_mtype is VD_MEDIA_TYPE_CD or VD_MEDIA_TYPE_DVD. * Needed to be able to install inside an ldom from an iso image. */static int vdc_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned command, unsigned long argument){ struct vdc_port *port = bdev->bd_disk->private_data; int i; switch (command) { case CDROMMULTISESSION: pr_debug(PFX "Multisession CDs not supported\n"); for (i = 0; i < sizeof(struct cdrom_multisession); i++) if (put_user(0, (char __user *)(argument + i))) return -EFAULT; return 0; case CDROM_GET_CAPABILITY: if (!vdc_version_supported(port, 1, 1)) return -EINVAL; switch (port->vdisk_mtype) { case VD_MEDIA_TYPE_CD: case VD_MEDIA_TYPE_DVD: return 0; default: return -EINVAL; } default: pr_debug(PFX "ioctl %08x not supported\n", command); return -EINVAL; }} static const struct block_device_operations vdc_fops = { .owner = THIS_MODULE, .getgeo = vdc_getgeo, .ioctl = vdc_ioctl, .compat_ioctl = blkdev_compat_ptr_ioctl,}; static void vdc_blk_queue_start(struct vdc_port *port){ struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; /* restart blk queue when ring is half emptied. also called after * handshake completes, so check for initial handshake before we've * allocated a disk. */ if (port->disk && vdc_tx_dring_avail(dr) * 100 / VDC_TX_RING_SIZE >= 50) blk_mq_start_stopped_hw_queues(port->disk->queue, true);} static void vdc_finish(struct vio_driver_state *vio, int err, int waiting_for){ if (vio->cmp && (waiting_for == -1 || vio->cmp->waiting_for == waiting_for)) { vio->cmp->err = err; complete(&vio->cmp->com); vio->cmp = NULL; }} static void vdc_handshake_complete(struct vio_driver_state *vio){ struct vdc_port *port = to_vdc_port(vio); cancel_delayed_work(&port->ldc_reset_timer_work); vdc_finish(vio, 0, WAITING_FOR_LINK_UP); vdc_blk_queue_start(port);} static int vdc_handle_unknown(struct vdc_port *port, void *arg){ struct vio_msg_tag *pkt = arg; printk(KERN_ERR PFX "Received unknown msg [%02x:%02x:%04x:%08x]\n", pkt->type, pkt->stype, pkt->stype_env, pkt->sid); printk(KERN_ERR PFX "Resetting connection.\n"); ldc_disconnect(port->vio.lp); return -ECONNRESET;} static int vdc_send_attr(struct vio_driver_state *vio){ struct vdc_port *port = to_vdc_port(vio); struct vio_disk_attr_info pkt; memset(&pkt, 0, sizeof(pkt)); pkt.tag.type = VIO_TYPE_CTRL; pkt.tag.stype = VIO_SUBTYPE_INFO; pkt.tag.stype_env = VIO_ATTR_INFO; pkt.tag.sid = vio_send_sid(vio); pkt.xfer_mode = VIO_DRING_MODE; pkt.vdisk_block_size = port->vdisk_block_size; pkt.max_xfer_size = port->max_xfer_size; viodbg(HS, "SEND ATTR xfer_mode[0x%x] blksz[%u] max_xfer[%llu]\n", pkt.xfer_mode, pkt.vdisk_block_size, pkt.max_xfer_size); return vio_ldc_send(&port->vio, &pkt, sizeof(pkt));} static int vdc_handle_attr(struct vio_driver_state *vio, void *arg){ struct vdc_port *port = to_vdc_port(vio); struct vio_disk_attr_info *pkt = arg; viodbg(HS, "GOT ATTR stype[0x%x] ops[%llx] disk_size[%llu] disk_type[%x] " "mtype[0x%x] xfer_mode[0x%x] blksz[%u] max_xfer[%llu]\n", pkt->tag.stype, pkt->operations, pkt->vdisk_size, pkt->vdisk_type, pkt->vdisk_mtype, pkt->xfer_mode, pkt->vdisk_block_size, pkt->max_xfer_size); if (pkt->tag.stype == VIO_SUBTYPE_ACK) { switch (pkt->vdisk_type) { case VD_DISK_TYPE_DISK: case VD_DISK_TYPE_SLICE: break; default: printk(KERN_ERR PFX "%s: Bogus vdisk_type 0x%x\n", vio->name, pkt->vdisk_type); return -ECONNRESET; } if (pkt->vdisk_block_size > port->vdisk_block_size) { printk(KERN_ERR PFX "%s: BLOCK size increased " "%u --> %u\n", vio->name, port->vdisk_block_size, pkt->vdisk_block_size); return -ECONNRESET; } port->operations = pkt->operations; port->vdisk_type = pkt->vdisk_type; if (vdc_version_supported(port, 1, 1)) { port->vdisk_size = pkt->vdisk_size; port->vdisk_mtype = pkt->vdisk_mtype; } if (pkt->max_xfer_size < port->max_xfer_size) port->max_xfer_size = pkt->max_xfer_size; port->vdisk_block_size = pkt->vdisk_block_size; port->vdisk_phys_blksz = VDC_DEFAULT_BLK_SIZE; if (vdc_version_supported(port, 1, 2)) port->vdisk_phys_blksz = pkt->phys_block_size; return 0; } else { printk(KERN_ERR PFX "%s: Attribute NACK\n", vio->name); return -ECONNRESET; }} static void vdc_end_special(struct vdc_port *port, struct vio_disk_desc *desc){ int err = desc->status; vdc_finish(&port->vio, -err, WAITING_FOR_GEN_CMD);} static void vdc_end_one(struct vdc_port *port, struct vio_dring_state *dr, unsigned int index){ struct vio_disk_desc *desc = vio_dring_entry(dr, index); struct vdc_req_entry *rqe = &port->rq_arr[index]; struct request *req; if (unlikely(desc->hdr.state != VIO_DESC_DONE)) return; ldc_unmap(port->vio.lp, desc->cookies, desc->ncookies); desc->hdr.state = VIO_DESC_FREE; dr->cons = vio_dring_next(dr, index); req = rqe->req; if (req == NULL) { vdc_end_special(port, desc); return; } rqe->req = NULL; blk_mq_end_request(req, desc->status ? BLK_STS_IOERR : 0); vdc_blk_queue_start(port);} static int vdc_ack(struct vdc_port *port, void *msgbuf){ struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; struct vio_dring_data *pkt = msgbuf; if (unlikely(pkt->dring_ident != dr->ident || pkt->start_idx != pkt->end_idx || pkt->start_idx >= VDC_TX_RING_SIZE)) return 0; vdc_end_one(port, dr, pkt->start_idx); return 0;} static int vdc_nack(struct vdc_port *port, void *msgbuf){ /* XXX Implement me XXX */ return 0;} static void vdc_event(void *arg, int event){ struct vdc_port *port = arg; struct vio_driver_state *vio = &port->vio; unsigned long flags; int err; spin_lock_irqsave(&vio->lock, flags); if (unlikely(event == LDC_EVENT_RESET)) { vio_link_state_change(vio, event); queue_work(sunvdc_wq, &port->ldc_reset_work); goto out; } if (unlikely(event == LDC_EVENT_UP)) { vio_link_state_change(vio, event); goto out; } if (unlikely(event != LDC_EVENT_DATA_READY)) { pr_warn(PFX "Unexpected LDC event %d\n", event); goto out; } err = 0; while (1) { union { struct vio_msg_tag tag; u64 raw[8]; } msgbuf; err = ldc_read(vio->lp, &msgbuf, sizeof(msgbuf)); if (unlikely(err < 0)) { if (err == -ECONNRESET) vio_conn_reset(vio); break; } if (err == 0) break; viodbg(DATA, "TAG [%02x:%02x:%04x:%08x]\n", msgbuf.tag.type, msgbuf.tag.stype, msgbuf.tag.stype_env, msgbuf.tag.sid); err = vio_validate_sid(vio, &msgbuf.tag); if (err < 0) break; if (likely(msgbuf.tag.type == VIO_TYPE_DATA)) { if (msgbuf.tag.stype == VIO_SUBTYPE_ACK) err = vdc_ack(port, &msgbuf); else if (msgbuf.tag.stype == VIO_SUBTYPE_NACK) err = vdc_nack(port, &msgbuf); else err = vdc_handle_unknown(port, &msgbuf); } else if (msgbuf.tag.type == VIO_TYPE_CTRL) { err = vio_control_pkt_engine(vio, &msgbuf); } else { err = vdc_handle_unknown(port, &msgbuf); } if (err < 0) break; } if (err < 0) vdc_finish(&port->vio, err, WAITING_FOR_ANY);out: spin_unlock_irqrestore(&vio->lock, flags);} static int __vdc_tx_trigger(struct vdc_port *port){ struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; struct vio_dring_data hdr = { .tag = { .type = VIO_TYPE_DATA, .stype = VIO_SUBTYPE_INFO, .stype_env = VIO_DRING_DATA, .sid = vio_send_sid(&port->vio), }, .dring_ident = dr->ident, .start_idx = dr->prod, .end_idx = dr->prod, }; int err, delay; int retries = 0; hdr.seq = dr->snd_nxt; delay = 1; do { err = vio_ldc_send(&port->vio, &hdr, sizeof(hdr)); if (err > 0) { dr->snd_nxt++; break; } udelay(delay); if ((delay <<= 1) > 128) delay = 128; if (retries++ > VDC_MAX_RETRIES) break; } while (err == -EAGAIN); if (err == -ENOTCONN) vdc_ldc_reset(port); return err;} static int __send_request(struct request *req){ struct vdc_port *port = req->q->disk->private_data; struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; struct scatterlist sg[MAX_RING_COOKIES]; struct vdc_req_entry *rqe; struct vio_disk_desc *desc; unsigned int map_perm; int nsg, err, i; u64 len; u8 op; if (WARN_ON(port->ring_cookies > MAX_RING_COOKIES)) return -EINVAL; map_perm = LDC_MAP_SHADOW | LDC_MAP_DIRECT | LDC_MAP_IO; if (rq_data_dir(req) == READ) { map_perm |= LDC_MAP_W; op = VD_OP_BREAD; } else { map_perm |= LDC_MAP_R; op = VD_OP_BWRITE; } sg_init_table(sg, port->ring_cookies); nsg = blk_rq_map_sg(req, sg); len = 0; for (i = 0; i < nsg; i++) len += sg[i].length; desc = vio_dring_cur(dr); err = ldc_map_sg(port->vio.lp, sg, nsg, desc->cookies, port->ring_cookies, map_perm); if (err < 0) { printk(KERN_ERR PFX "ldc_map_sg() failure, err=%d.\n", err); return err;Lines 1–500 of 1252. Load more
// SPDX-License-Identifier: GPL-2.0-only /* sunvdc.c: Sun LDOM Virtual Disk Client. * * Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net> */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/blk-mq.h> #include <linux/hdreg.h> #include <linux/cdrom.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/completion.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/list.h> #include <linux/scatterlist.h> #include <asm/vio.h> #include <asm/ldc.h> #define DRV_MODULE_NAME "sunvdc" #define PFX DRV_MODULE_NAME ": " #define DRV_MODULE_VERSION "1.2" #define DRV_MODULE_RELDATE "November 24, 2014" static char version[] = DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); MODULE_DESCRIPTION("Sun LDOM virtual disk client driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_MODULE_VERSION); #define VDC_TX_RING_SIZE 512 #define VDC_DEFAULT_BLK_SIZE 512 #define MAX_XFER_BLKS (128 * 1024) #define MAX_XFER_SIZE (MAX_XFER_BLKS / VDC_DEFAULT_BLK_SIZE) #define MAX_RING_COOKIES ((MAX_XFER_BLKS / PAGE_SIZE) + 2) #define WAITING_FOR_LINK_UP 0x01 #define WAITING_FOR_TX_SPACE 0x02 #define WAITING_FOR_GEN_CMD 0x04 #define WAITING_FOR_ANY -1 #define VDC_MAX_RETRIES 10 static struct workqueue_struct *sunvdc_wq; struct vdc_req_entry { struct request *req; }; struct vdc_port { struct vio_driver_state vio; struct gendisk *disk; struct vdc_completion *cmp; u64 req_id; u64 seq; struct vdc_req_entry rq_arr[VDC_TX_RING_SIZE]; unsigned long ring_cookies; u64 max_xfer_size; u32 vdisk_block_size; u32 drain; u64 ldc_timeout; struct delayed_work ldc_reset_timer_work; struct work_struct ldc_reset_work; /* The server fills these in for us in the disk attribute * ACK packet. */ u64 operations; u32 vdisk_size; u8 vdisk_type; u8 vdisk_mtype; u32 vdisk_phys_blksz; struct blk_mq_tag_set tag_set; char disk_name[32]; }; static void vdc_ldc_reset(struct vdc_port *port); static void vdc_ldc_reset_work(struct work_struct *work); static void vdc_ldc_reset_timer_work(struct work_struct *work); static inline struct vdc_port *to_vdc_port(struct vio_driver_state *vio) { return container_of(vio, struct vdc_port, vio); } /* Ordered from largest major to lowest */ static struct vio_version vdc_versions[] = { { .major = 1, .minor = 2 }, { .major = 1, .minor = 1 }, { .major = 1, .minor = 0 }, }; static inline int vdc_version_supported(struct vdc_port *port, u16 major, u16 minor) { return port->vio.ver.major == major && port->vio.ver.minor >= minor; } #define VDCBLK_NAME "vdisk" static int vdc_major; #define PARTITION_SHIFT 3 static inline u32 vdc_tx_dring_avail(struct vio_dring_state *dr) { return vio_dring_avail(dr, VDC_TX_RING_SIZE); } static int vdc_getgeo(struct gendisk *disk, struct hd_geometry *geo) { sector_t nsect = get_capacity(disk); sector_t cylinders = nsect; geo->heads = 0xff; geo->sectors = 0x3f; sector_div(cylinders, geo->heads * geo->sectors); geo->cylinders = cylinders; if ((sector_t)(geo->cylinders + 1) * geo->heads * geo->sectors < nsect) geo->cylinders = 0xffff; return 0; } /* Add ioctl/CDROM_GET_CAPABILITY to support cdrom_id in udev * when vdisk_mtype is VD_MEDIA_TYPE_CD or VD_MEDIA_TYPE_DVD. * Needed to be able to install inside an ldom from an iso image. */ static int vdc_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned command, unsigned long argument) { struct vdc_port *port = bdev->bd_disk->private_data; int i; switch (command) { case CDROMMULTISESSION: pr_debug(PFX "Multisession CDs not supported\n"); for (i = 0; i < sizeof(struct cdrom_multisession); i++) if (put_user(0, (char __user *)(argument + i))) return -EFAULT; return 0; case CDROM_GET_CAPABILITY: if (!vdc_version_supported(port, 1, 1)) return -EINVAL; switch (port->vdisk_mtype) { case VD_MEDIA_TYPE_CD: case VD_MEDIA_TYPE_DVD: return 0; default: return -EINVAL; } default: pr_debug(PFX "ioctl %08x not supported\n", command); return -EINVAL; } } static const struct block_device_operations vdc_fops = { .owner = THIS_MODULE, .getgeo = vdc_getgeo, .ioctl = vdc_ioctl, .compat_ioctl = blkdev_compat_ptr_ioctl, }; static void vdc_blk_queue_start(struct vdc_port *port) { struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; /* restart blk queue when ring is half emptied. also called after * handshake completes, so check for initial handshake before we've * allocated a disk. */ if (port->disk && vdc_tx_dring_avail(dr) * 100 / VDC_TX_RING_SIZE >= 50) blk_mq_start_stopped_hw_queues(port->disk->queue, true); } static void vdc_finish(struct vio_driver_state *vio, int err, int waiting_for) { if (vio->cmp && (waiting_for == -1 || vio->cmp->waiting_for == waiting_for)) { vio->cmp->err = err; complete(&vio->cmp->com); vio->cmp = NULL; } } static void vdc_handshake_complete(struct vio_driver_state *vio) { struct vdc_port *port = to_vdc_port(vio); cancel_delayed_work(&port->ldc_reset_timer_work); vdc_finish(vio, 0, WAITING_FOR_LINK_UP); vdc_blk_queue_start(port); } static int vdc_handle_unknown(struct vdc_port *port, void *arg) { struct vio_msg_tag *pkt = arg; printk(KERN_ERR PFX "Received unknown msg [%02x:%02x:%04x:%08x]\n", pkt->type, pkt->stype, pkt->stype_env, pkt->sid); printk(KERN_ERR PFX "Resetting connection.\n"); ldc_disconnect(port->vio.lp); return -ECONNRESET; } static int vdc_send_attr(struct vio_driver_state *vio) { struct vdc_port *port = to_vdc_port(vio); struct vio_disk_attr_info pkt; memset(&pkt, 0, sizeof(pkt)); pkt.tag.type = VIO_TYPE_CTRL; pkt.tag.stype = VIO_SUBTYPE_INFO; pkt.tag.stype_env = VIO_ATTR_INFO; pkt.tag.sid = vio_send_sid(vio); pkt.xfer_mode = VIO_DRING_MODE; pkt.vdisk_block_size = port->vdisk_block_size; pkt.max_xfer_size = port->max_xfer_size; viodbg(HS, "SEND ATTR xfer_mode[0x%x] blksz[%u] max_xfer[%llu]\n", pkt.xfer_mode, pkt.vdisk_block_size, pkt.max_xfer_size); return vio_ldc_send(&port->vio, &pkt, sizeof(pkt)); } static int vdc_handle_attr(struct vio_driver_state *vio, void *arg) { struct vdc_port *port = to_vdc_port(vio); struct vio_disk_attr_info *pkt = arg; viodbg(HS, "GOT ATTR stype[0x%x] ops[%llx] disk_size[%llu] disk_type[%x] " "mtype[0x%x] xfer_mode[0x%x] blksz[%u] max_xfer[%llu]\n", pkt->tag.stype, pkt->operations, pkt->vdisk_size, pkt->vdisk_type, pkt->vdisk_mtype, pkt->xfer_mode, pkt->vdisk_block_size, pkt->max_xfer_size); if (pkt->tag.stype == VIO_SUBTYPE_ACK) { switch (pkt->vdisk_type) { case VD_DISK_TYPE_DISK: case VD_DISK_TYPE_SLICE: break; default: printk(KERN_ERR PFX "%s: Bogus vdisk_type 0x%x\n", vio->name, pkt->vdisk_type); return -ECONNRESET; } if (pkt->vdisk_block_size > port->vdisk_block_size) { printk(KERN_ERR PFX "%s: BLOCK size increased " "%u --> %u\n", vio->name, port->vdisk_block_size, pkt->vdisk_block_size); return -ECONNRESET; } port->operations = pkt->operations; port->vdisk_type = pkt->vdisk_type; if (vdc_version_supported(port, 1, 1)) { port->vdisk_size = pkt->vdisk_size; port->vdisk_mtype = pkt->vdisk_mtype; } if (pkt->max_xfer_size < port->max_xfer_size) port->max_xfer_size = pkt->max_xfer_size; port->vdisk_block_size = pkt->vdisk_block_size; port->vdisk_phys_blksz = VDC_DEFAULT_BLK_SIZE; if (vdc_version_supported(port, 1, 2)) port->vdisk_phys_blksz = pkt->phys_block_size; return 0; } else { printk(KERN_ERR PFX "%s: Attribute NACK\n", vio->name); return -ECONNRESET; } } static void vdc_end_special(struct vdc_port *port, struct vio_disk_desc *desc) { int err = desc->status; vdc_finish(&port->vio, -err, WAITING_FOR_GEN_CMD); } static void vdc_end_one(struct vdc_port *port, struct vio_dring_state *dr, unsigned int index) { struct vio_disk_desc *desc = vio_dring_entry(dr, index); struct vdc_req_entry *rqe = &port->rq_arr[index]; struct request *req; if (unlikely(desc->hdr.state != VIO_DESC_DONE)) return; ldc_unmap(port->vio.lp, desc->cookies, desc->ncookies); desc->hdr.state = VIO_DESC_FREE; dr->cons = vio_dring_next(dr, index); req = rqe->req; if (req == NULL) { vdc_end_special(port, desc); return; } rqe->req = NULL; blk_mq_end_request(req, desc->status ? BLK_STS_IOERR : 0); vdc_blk_queue_start(port); } static int vdc_ack(struct vdc_port *port, void *msgbuf) { struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; struct vio_dring_data *pkt = msgbuf; if (unlikely(pkt->dring_ident != dr->ident || pkt->start_idx != pkt->end_idx || pkt->start_idx >= VDC_TX_RING_SIZE)) return 0; vdc_end_one(port, dr, pkt->start_idx); return 0; } static int vdc_nack(struct vdc_port *port, void *msgbuf) { /* XXX Implement me XXX */ return 0; } static void vdc_event(void *arg, int event) { struct vdc_port *port = arg; struct vio_driver_state *vio = &port->vio; unsigned long flags; int err; spin_lock_irqsave(&vio->lock, flags); if (unlikely(event == LDC_EVENT_RESET)) { vio_link_state_change(vio, event); queue_work(sunvdc_wq, &port->ldc_reset_work); goto out; } if (unlikely(event == LDC_EVENT_UP)) { vio_link_state_change(vio, event); goto out; } if (unlikely(event != LDC_EVENT_DATA_READY)) { pr_warn(PFX "Unexpected LDC event %d\n", event); goto out; } err = 0; while (1) { union { struct vio_msg_tag tag; u64 raw[8]; } msgbuf; err = ldc_read(vio->lp, &msgbuf, sizeof(msgbuf)); if (unlikely(err < 0)) { if (err == -ECONNRESET) vio_conn_reset(vio); break; } if (err == 0) break; viodbg(DATA, "TAG [%02x:%02x:%04x:%08x]\n", msgbuf.tag.type, msgbuf.tag.stype, msgbuf.tag.stype_env, msgbuf.tag.sid); err = vio_validate_sid(vio, &msgbuf.tag); if (err < 0) break; if (likely(msgbuf.tag.type == VIO_TYPE_DATA)) { if (msgbuf.tag.stype == VIO_SUBTYPE_ACK) err = vdc_ack(port, &msgbuf); else if (msgbuf.tag.stype == VIO_SUBTYPE_NACK) err = vdc_nack(port, &msgbuf); else err = vdc_handle_unknown(port, &msgbuf); } else if (msgbuf.tag.type == VIO_TYPE_CTRL) { err = vio_control_pkt_engine(vio, &msgbuf); } else { err = vdc_handle_unknown(port, &msgbuf); } if (err < 0) break; } if (err < 0) vdc_finish(&port->vio, err, WAITING_FOR_ANY); out: spin_unlock_irqrestore(&vio->lock, flags); } static int __vdc_tx_trigger(struct vdc_port *port) { struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; struct vio_dring_data hdr = { .tag = { .type = VIO_TYPE_DATA, .stype = VIO_SUBTYPE_INFO, .stype_env = VIO_DRING_DATA, .sid = vio_send_sid(&port->vio), }, .dring_ident = dr->ident, .start_idx = dr->prod, .end_idx = dr->prod, }; int err, delay; int retries = 0; hdr.seq = dr->snd_nxt; delay = 1; do { err = vio_ldc_send(&port->vio, &hdr, sizeof(hdr)); if (err > 0) { dr->snd_nxt++; break; } udelay(delay); if ((delay <<= 1) > 128) delay = 128; if (retries++ > VDC_MAX_RETRIES) break; } while (err == -EAGAIN); if (err == -ENOTCONN) vdc_ldc_reset(port); return err; } static int __send_request(struct request *req) { struct vdc_port *port = req->q->disk->private_data; struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; struct scatterlist sg[MAX_RING_COOKIES]; struct vdc_req_entry *rqe; struct vio_disk_desc *desc; unsigned int map_perm; int nsg, err, i; u64 len; u8 op; if (WARN_ON(port->ring_cookies > MAX_RING_COOKIES)) return -EINVAL; map_perm = LDC_MAP_SHADOW | LDC_MAP_DIRECT | LDC_MAP_IO; if (rq_data_dir(req) == READ) { map_perm |= LDC_MAP_W; op = VD_OP_BREAD; } else { map_perm |= LDC_MAP_R; op = VD_OP_BWRITE; } sg_init_table(sg, port->ring_cookies); nsg = blk_rq_map_sg(req, sg); len = 0; for (i = 0; i < nsg; i++) len += sg[i].length; desc = vio_dring_cur(dr); err = ldc_map_sg(port->vio.lp, sg, nsg, desc->cookies, port->ring_cookies, map_perm); if (err < 0) { printk(KERN_ERR PFX "ldc_map_sg() failure, err=%d.\n", err); return err;