From 92f268eefe38c62aaf9ee9cea5e05f2abf9d3d60 Mon Sep 17 00:00:00 2001 From: Andreas Larsson Date: Fri, 5 Dec 2014 10:55:28 +0100 Subject: [PATCH 1/5] sparc32, leon: Align ccall_info to prevent unaligned traps on crosscall It is being filled in using std in leon_cross_call. Signed-off-by: Andreas Larsson Signed-off-by: David S. Miller --- arch/sparc/kernel/leon_smp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/sparc/kernel/leon_smp.c b/arch/sparc/kernel/leon_smp.c index ea2bad306f93..71e16f2241c2 100644 --- a/arch/sparc/kernel/leon_smp.c +++ b/arch/sparc/kernel/leon_smp.c @@ -368,7 +368,7 @@ static struct smp_funcall { unsigned long arg5; unsigned long processors_in[NR_CPUS]; /* Set when ipi entered. */ unsigned long processors_out[NR_CPUS]; /* Set when ipi exited. */ -} ccall_info; +} ccall_info __attribute__((aligned(8))); static DEFINE_SPINLOCK(cross_call_lock); From 31f4888f51afb038f7f8e7e4b3f0a80587c92c9b Mon Sep 17 00:00:00 2001 From: Dwight Engen Date: Thu, 11 Dec 2014 12:25:42 -0500 Subject: [PATCH 2/5] sunvdc: fix module unload/reload Free resources allocated during port/disk probing so that the module may be successfully reloaded after unloading. Signed-off-by: Dwight Engen Signed-off-by: David S. Miller --- drivers/block/sunvdc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 0ebadf93b6c5..089ff9035de6 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -896,8 +896,19 @@ static int vdc_port_remove(struct vio_dev *vdev) struct vdc_port *port = dev_get_drvdata(&vdev->dev); if (port) { + unsigned long flags; + + spin_lock_irqsave(&port->vio.lock, flags); + blk_stop_queue(port->disk->queue); + spin_unlock_irqrestore(&port->vio.lock, flags); + del_timer_sync(&port->vio.timer); + del_gendisk(port->disk); + blk_cleanup_queue(port->disk->queue); + put_disk(port->disk); + port->disk = NULL; + vdc_free_tx_ring(port); vio_ldc_free(&port->vio); From fe47c3c2623d6914655f507a317a6b881bc7c6a4 Mon Sep 17 00:00:00 2001 From: Dwight Engen Date: Thu, 11 Dec 2014 12:26:15 -0500 Subject: [PATCH 3/5] vio: create routines for inc,dec vio dring indexes Both sunvdc and sunvnet implemented distinct functionality for incrementing and decrementing dring indexes. Create common functions for use by both from the sunvnet versions, which were chosen since they will still work correctly in case a non power of two ring size is used. Signed-off-by: Dwight Engen Signed-off-by: David S. Miller --- arch/sparc/include/asm/vio.h | 15 +++++++++++++++ drivers/block/sunvdc.c | 6 +++--- drivers/net/ethernet/sun/sunvnet.c | 30 +++++++----------------------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/arch/sparc/include/asm/vio.h b/arch/sparc/include/asm/vio.h index fb124feb363b..8174f6cdbbbb 100644 --- a/arch/sparc/include/asm/vio.h +++ b/arch/sparc/include/asm/vio.h @@ -300,6 +300,21 @@ static inline u32 vio_dring_avail(struct vio_dring_state *dr, ((dr->prod - dr->cons) & (ring_size - 1)) - 1); } +static inline u32 vio_dring_next(struct vio_dring_state *dr, u32 index) +{ + if (++index == dr->num_entries) + index = 0; + return index; +} + +static inline u32 vio_dring_prev(struct vio_dring_state *dr, u32 index) +{ + if (index == 0) + return dr->num_entries - 1; + else + return index - 1; +} + #define VIO_MAX_TYPE_LEN 32 #define VIO_MAX_COMPAT_LEN 64 diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 089ff9035de6..65cec156cfb4 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -269,7 +269,7 @@ static void vdc_end_one(struct vdc_port *port, struct vio_dring_state *dr, ldc_unmap(port->vio.lp, desc->cookies, desc->ncookies); desc->hdr.state = VIO_DESC_FREE; - dr->cons = (index + 1) & (VDC_TX_RING_SIZE - 1); + dr->cons = vio_dring_next(dr, index); req = rqe->req; if (req == NULL) { @@ -472,7 +472,7 @@ static int __send_request(struct request *req) printk(KERN_ERR PFX "vdc_tx_trigger() failure, err=%d\n", err); } else { port->req_id++; - dr->prod = (dr->prod + 1) & (VDC_TX_RING_SIZE - 1); + dr->prod = vio_dring_next(dr, dr->prod); } return err; @@ -626,7 +626,7 @@ static int generic_request(struct vdc_port *port, u8 op, void *buf, int len) err = __vdc_tx_trigger(port); if (err >= 0) { port->req_id++; - dr->prod = (dr->prod + 1) & (VDC_TX_RING_SIZE - 1); + dr->prod = vio_dring_next(dr, dr->prod); spin_unlock_irqrestore(&port->vio.lock, flags); wait_for_completion(&comp.com); diff --git a/drivers/net/ethernet/sun/sunvnet.c b/drivers/net/ethernet/sun/sunvnet.c index 90c86cd3be14..45c408ef67d0 100644 --- a/drivers/net/ethernet/sun/sunvnet.c +++ b/drivers/net/ethernet/sun/sunvnet.c @@ -466,23 +466,6 @@ static int vnet_send_ack(struct vnet_port *port, struct vio_dring_state *dr, return err; } -static u32 next_idx(u32 idx, struct vio_dring_state *dr) -{ - if (++idx == dr->num_entries) - idx = 0; - return idx; -} - -static u32 prev_idx(u32 idx, struct vio_dring_state *dr) -{ - if (idx == 0) - idx = dr->num_entries - 1; - else - idx--; - - return idx; -} - static struct vio_net_desc *get_rx_desc(struct vnet_port *port, struct vio_dring_state *dr, u32 index) @@ -556,7 +539,8 @@ static int vnet_walk_rx(struct vnet_port *port, struct vio_dring_state *dr, int ack_start = -1, ack_end = -1; bool send_ack = true; - end = (end == (u32) -1) ? prev_idx(start, dr) : next_idx(end, dr); + end = (end == (u32) -1) ? vio_dring_prev(dr, start) + : vio_dring_next(dr, end); viodbg(DATA, "vnet_walk_rx start[%08x] end[%08x]\n", start, end); @@ -570,7 +554,7 @@ static int vnet_walk_rx(struct vnet_port *port, struct vio_dring_state *dr, if (ack_start == -1) ack_start = start; ack_end = start; - start = next_idx(start, dr); + start = vio_dring_next(dr, start); if (ack && start != end) { err = vnet_send_ack(port, dr, ack_start, ack_end, VIO_DRING_ACTIVE); @@ -584,7 +568,7 @@ static int vnet_walk_rx(struct vnet_port *port, struct vio_dring_state *dr, } } if (unlikely(ack_start == -1)) - ack_start = ack_end = prev_idx(start, dr); + ack_start = ack_end = vio_dring_prev(dr, start); if (send_ack) { port->napi_resume = false; return vnet_send_ack(port, dr, ack_start, ack_end, @@ -633,7 +617,7 @@ static int idx_is_pending(struct vio_dring_state *dr, u32 end) found = 1; break; } - idx = next_idx(idx, dr); + idx = vio_dring_next(dr, idx); } return found; } @@ -663,7 +647,7 @@ static int vnet_ack(struct vnet_port *port, void *msgbuf) /* sync for race conditions with vnet_start_xmit() and tell xmit it * is time to send a trigger. */ - dr->cons = next_idx(end, dr); + dr->cons = vio_dring_next(dr, end); desc = vio_dring_entry(dr, dr->cons); if (desc->hdr.state == VIO_DESC_READY && !port->start_cons) { /* vnet_start_xmit() just populated this dring but missed @@ -784,7 +768,7 @@ ldc_ctrl: pkt->tag.stype = VIO_SUBTYPE_INFO; pkt->tag.stype_env = VIO_DRING_DATA; pkt->seq = dr->rcv_nxt; - pkt->start_idx = next_idx(port->napi_stop_idx, dr); + pkt->start_idx = vio_dring_next(dr, port->napi_stop_idx); pkt->end_idx = -1; goto napi_resume; } From 1678c2bd131313b58c7492487c00dca982be7f28 Mon Sep 17 00:00:00 2001 From: Dwight Engen Date: Thu, 11 Dec 2014 12:26:16 -0500 Subject: [PATCH 4/5] sparc/ldc: create separate ldc_unbind from ldc_free Signed-off-by: Dwight Engen Signed-off-by: David S. Miller --- arch/sparc/include/asm/ldc.h | 1 + arch/sparc/kernel/ldc.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/arch/sparc/include/asm/ldc.h b/arch/sparc/include/asm/ldc.h index 58ab64de25d2..6e9004aa6f25 100644 --- a/arch/sparc/include/asm/ldc.h +++ b/arch/sparc/include/asm/ldc.h @@ -61,6 +61,7 @@ void ldc_free(struct ldc_channel *lp); /* Register TX and RX queues of the link with the hypervisor. */ int ldc_bind(struct ldc_channel *lp); +void ldc_unbind(struct ldc_channel *lp); /* For non-RAW protocols we need to complete a handshake before * communication can proceed. ldc_connect() does that, if the diff --git a/arch/sparc/kernel/ldc.c b/arch/sparc/kernel/ldc.c index 4310332872d4..274a9f59d95c 100644 --- a/arch/sparc/kernel/ldc.c +++ b/arch/sparc/kernel/ldc.c @@ -1222,11 +1222,12 @@ out_err: } EXPORT_SYMBOL(ldc_alloc); -void ldc_free(struct ldc_channel *lp) +void ldc_unbind(struct ldc_channel *lp) { if (lp->flags & LDC_FLAG_REGISTERED_IRQS) { free_irq(lp->cfg.rx_irq, lp); free_irq(lp->cfg.tx_irq, lp); + lp->flags &= ~LDC_FLAG_REGISTERED_IRQS; } if (lp->flags & LDC_FLAG_REGISTERED_QUEUES) { @@ -1240,10 +1241,15 @@ void ldc_free(struct ldc_channel *lp) lp->flags &= ~LDC_FLAG_ALLOCED_QUEUES; } + ldc_set_state(lp, LDC_STATE_INIT); +} +EXPORT_SYMBOL(ldc_unbind); + +void ldc_free(struct ldc_channel *lp) +{ + ldc_unbind(lp); hlist_del(&lp->list); - kfree(lp->mssbuf); - ldc_iommu_release(lp); kfree(lp); From 76e74bbe0a38c6720217425ed64dbb448c643b9d Mon Sep 17 00:00:00 2001 From: Dwight Engen Date: Thu, 11 Dec 2014 12:26:17 -0500 Subject: [PATCH 5/5] sunvdc: reconnect ldc after vds service domain restarts This change enables the sunvdc driver to reconnect and recover if a vds service domain is disconnected or bounced. By default, it will wait indefinitely for the service domain to become available again, but will honor a non-zero vdc-timout md property if one is set. If a timeout is reached, any in-progress I/O's are completed with -EIO. Signed-off-by: Dwight Engen Reviewed-by: Chris Hyser Signed-off-by: David S. Miller --- drivers/block/sunvdc.c | 205 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 183 insertions(+), 22 deletions(-) diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 65cec156cfb4..4b911ed96ea3 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -23,8 +23,8 @@ #define DRV_MODULE_NAME "sunvdc" #define PFX DRV_MODULE_NAME ": " -#define DRV_MODULE_VERSION "1.1" -#define DRV_MODULE_RELDATE "February 13, 2013" +#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"; @@ -40,6 +40,8 @@ MODULE_VERSION(DRV_MODULE_VERSION); #define WAITING_FOR_GEN_CMD 0x04 #define WAITING_FOR_ANY -1 +static struct workqueue_struct *sunvdc_wq; + struct vdc_req_entry { struct request *req; }; @@ -60,6 +62,10 @@ struct vdc_port { u64 max_xfer_size; u32 vdisk_block_size; + u64 ldc_timeout; + struct timer_list ldc_reset_timer; + struct work_struct ldc_reset_work; + /* The server fills these in for us in the disk attribute * ACK packet. */ @@ -71,6 +77,10 @@ struct vdc_port { 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(unsigned long _arg); + static inline struct vdc_port *to_vdc_port(struct vio_driver_state *vio) { return container_of(vio, struct vdc_port, vio); @@ -150,6 +160,21 @@ static const struct block_device_operations vdc_fops = { .ioctl = vdc_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 && blk_queue_stopped(port->disk->queue) && + vdc_tx_dring_avail(dr) * 100 / VDC_TX_RING_SIZE >= 50) { + blk_start_queue(port->disk->queue); + } + +} + static void vdc_finish(struct vio_driver_state *vio, int err, int waiting_for) { if (vio->cmp && @@ -163,7 +188,11 @@ static void vdc_finish(struct vio_driver_state *vio, int err, int waiting_for) static void vdc_handshake_complete(struct vio_driver_state *vio) { + struct vdc_port *port = to_vdc_port(vio); + + del_timer(&port->ldc_reset_timer); vdc_finish(vio, 0, WAITING_FOR_LINK_UP); + vdc_blk_queue_start(port); } static int vdc_handle_unknown(struct vdc_port *port, void *arg) @@ -281,10 +310,7 @@ static void vdc_end_one(struct vdc_port *port, struct vio_dring_state *dr, __blk_end_request(req, (desc->status ? -EIO : 0), desc->size); - /* restart blk queue when ring is half emptied */ - if (blk_queue_stopped(port->disk->queue) && - vdc_tx_dring_avail(dr) * 100 / VDC_TX_RING_SIZE >= 50) - blk_start_queue(port->disk->queue); + vdc_blk_queue_start(port); } static int vdc_ack(struct vdc_port *port, void *msgbuf) @@ -317,17 +343,20 @@ static void vdc_event(void *arg, int event) spin_lock_irqsave(&vio->lock, flags); - if (unlikely(event == LDC_EVENT_RESET || - event == LDC_EVENT_UP)) { + if (unlikely(event == LDC_EVENT_RESET)) { vio_link_state_change(vio, event); - spin_unlock_irqrestore(&vio->lock, flags); - return; + 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)) { - printk(KERN_WARNING PFX "Unexpected LDC event %d\n", event); - spin_unlock_irqrestore(&vio->lock, flags); - return; + pr_warn(PFX "Unexpected LDC event %d\n", event); + goto out; } err = 0; @@ -371,6 +400,7 @@ static void vdc_event(void *arg, int event) } if (err < 0) vdc_finish(&port->vio, err, WAITING_FOR_ANY); +out: spin_unlock_irqrestore(&vio->lock, flags); } @@ -403,6 +433,8 @@ static int __vdc_tx_trigger(struct vdc_port *port) delay = 128; } while (err == -EAGAIN); + if (err == -ENOTCONN) + vdc_ldc_reset(port); return err; } @@ -690,12 +722,9 @@ static void vdc_free_tx_ring(struct vdc_port *port) } } -static int probe_disk(struct vdc_port *port) +static int vdc_port_up(struct vdc_port *port) { struct vio_completion comp; - struct request_queue *q; - struct gendisk *g; - int err; init_completion(&comp.com); comp.err = 0; @@ -703,10 +732,27 @@ static int probe_disk(struct vdc_port *port) port->vio.cmp = ∁ vio_port_up(&port->vio); - wait_for_completion(&comp.com); - if (comp.err) - return comp.err; + return comp.err; +} + +static void vdc_port_down(struct vdc_port *port) +{ + ldc_disconnect(port->vio.lp); + ldc_unbind(port->vio.lp); + vdc_free_tx_ring(port); + vio_ldc_free(&port->vio); +} + +static int probe_disk(struct vdc_port *port) +{ + struct request_queue *q; + struct gendisk *g; + int err; + + err = vdc_port_up(port); + if (err) + return err; if (vdc_version_supported(port, 1, 1)) { /* vdisk_size should be set during the handshake, if it wasn't @@ -819,6 +865,7 @@ static int vdc_port_probe(struct vio_dev *vdev, const struct vio_device_id *id) struct mdesc_handle *hp; struct vdc_port *port; int err; + const u64 *ldc_timeout; print_version(); @@ -848,6 +895,16 @@ static int vdc_port_probe(struct vio_dev *vdev, const struct vio_device_id *id) VDCBLK_NAME "%c", 'a' + ((int)vdev->dev_no % 26)); port->vdisk_size = -1; + /* Actual wall time may be double due to do_generic_file_read() doing + * a readahead I/O first, and once that fails it will try to read a + * single page. + */ + ldc_timeout = mdesc_get_property(hp, vdev->mp, "vdc-timeout", NULL); + port->ldc_timeout = ldc_timeout ? *ldc_timeout : 0; + setup_timer(&port->ldc_reset_timer, vdc_ldc_reset_timer, + (unsigned long)port); + INIT_WORK(&port->ldc_reset_work, vdc_ldc_reset_work); + err = vio_driver_init(&port->vio, vdev, VDEV_DISK, vdc_versions, ARRAY_SIZE(vdc_versions), &vdc_vio_ops, port->disk_name); @@ -902,6 +959,8 @@ static int vdc_port_remove(struct vio_dev *vdev) blk_stop_queue(port->disk->queue); spin_unlock_irqrestore(&port->vio.lock, flags); + flush_work(&port->ldc_reset_work); + del_timer_sync(&port->ldc_reset_timer); del_timer_sync(&port->vio.timer); del_gendisk(port->disk); @@ -919,6 +978,102 @@ static int vdc_port_remove(struct vio_dev *vdev) return 0; } +static void vdc_requeue_inflight(struct vdc_port *port) +{ + struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; + u32 idx; + + for (idx = dr->cons; idx != dr->prod; idx = vio_dring_next(dr, idx)) { + struct vio_disk_desc *desc = vio_dring_entry(dr, idx); + struct vdc_req_entry *rqe = &port->rq_arr[idx]; + struct request *req; + + ldc_unmap(port->vio.lp, desc->cookies, desc->ncookies); + desc->hdr.state = VIO_DESC_FREE; + dr->cons = vio_dring_next(dr, idx); + + req = rqe->req; + if (req == NULL) { + vdc_end_special(port, desc); + continue; + } + + rqe->req = NULL; + blk_requeue_request(port->disk->queue, req); + } +} + +static void vdc_queue_drain(struct vdc_port *port) +{ + struct request *req; + + while ((req = blk_fetch_request(port->disk->queue)) != NULL) + __blk_end_request_all(req, -EIO); +} + +static void vdc_ldc_reset_timer(unsigned long _arg) +{ + struct vdc_port *port = (struct vdc_port *) _arg; + struct vio_driver_state *vio = &port->vio; + unsigned long flags; + + spin_lock_irqsave(&vio->lock, flags); + if (!(port->vio.hs_state & VIO_HS_COMPLETE)) { + pr_warn(PFX "%s ldc down %llu seconds, draining queue\n", + port->disk_name, port->ldc_timeout); + vdc_queue_drain(port); + vdc_blk_queue_start(port); + } + spin_unlock_irqrestore(&vio->lock, flags); +} + +static void vdc_ldc_reset_work(struct work_struct *work) +{ + struct vdc_port *port; + struct vio_driver_state *vio; + unsigned long flags; + + port = container_of(work, struct vdc_port, ldc_reset_work); + vio = &port->vio; + + spin_lock_irqsave(&vio->lock, flags); + vdc_ldc_reset(port); + spin_unlock_irqrestore(&vio->lock, flags); +} + +static void vdc_ldc_reset(struct vdc_port *port) +{ + int err; + + assert_spin_locked(&port->vio.lock); + + pr_warn(PFX "%s ldc link reset\n", port->disk_name); + blk_stop_queue(port->disk->queue); + vdc_requeue_inflight(port); + vdc_port_down(port); + + err = vio_ldc_alloc(&port->vio, &vdc_ldc_cfg, port); + if (err) { + pr_err(PFX "%s vio_ldc_alloc:%d\n", port->disk_name, err); + return; + } + + err = vdc_alloc_tx_ring(port); + if (err) { + pr_err(PFX "%s vio_alloc_tx_ring:%d\n", port->disk_name, err); + goto err_free_ldc; + } + + if (port->ldc_timeout) + mod_timer(&port->ldc_reset_timer, + round_jiffies(jiffies + HZ * port->ldc_timeout)); + mod_timer(&port->vio.timer, round_jiffies(jiffies + HZ)); + return; + +err_free_ldc: + vio_ldc_free(&port->vio); +} + static const struct vio_device_id vdc_port_match[] = { { .type = "vdc-port", @@ -938,9 +1093,13 @@ static int __init vdc_init(void) { int err; + sunvdc_wq = alloc_workqueue("sunvdc", 0, 0); + if (!sunvdc_wq) + return -ENOMEM; + err = register_blkdev(0, VDCBLK_NAME); if (err < 0) - goto out_err; + goto out_free_wq; vdc_major = err; @@ -954,7 +1113,8 @@ out_unregister_blkdev: unregister_blkdev(vdc_major, VDCBLK_NAME); vdc_major = 0; -out_err: +out_free_wq: + destroy_workqueue(sunvdc_wq); return err; } @@ -962,6 +1122,7 @@ static void __exit vdc_exit(void) { vio_unregister_driver(&vdc_port_driver); unregister_blkdev(vdc_major, VDCBLK_NAME); + destroy_workqueue(sunvdc_wq); } module_init(vdc_init);