From a33b149f6d889b2d32fab6371a329fa167d0f953 Mon Sep 17 00:00:00 2001 From: Aaron Young Date: Wed, 11 Nov 2015 06:24:54 -0800 Subject: [PATCH] SPARC64: UEK4 LDOMS DOMAIN SERVICES UPDATE 1 This update provides several fixes for LDoms on UEK4 (i.e. fixes/updates for the ldoms UEK4 port done previously - See BUG 21644721) and updates for ldoms to work with Zeus. It also provides support for libpri which requires a HV call to retrieve the PRI from the SP and enhancements to the vlds driver to allow multiple process to register/use a service simultaneously - so multiple processes can use libpri at the same time. Signed-off-by: Aaron Young Orabug: 22185080 (cherry picked from commit f078f7f618c8c127d7562d946c630d106ed3b1b8) --- arch/sparc/include/asm/hypervisor.h | 7 + arch/sparc/kernel/ds.c | 517 +++++++++++-- arch/sparc/kernel/hvcalls.S | 9 + arch/sparc/kernel/mdesc.c | 5 +- drivers/char/vldc.c | 100 +-- drivers/char/vlds.c | 1072 +++++++++++++++++++-------- drivers/usb/core/hub.c | 3 +- include/linux/ds.h | 1 - include/uapi/linux/ds.h | 23 +- include/uapi/linux/vlds.h | 15 +- 10 files changed, 1305 insertions(+), 447 deletions(-) diff --git a/arch/sparc/include/asm/hypervisor.h b/arch/sparc/include/asm/hypervisor.h index 05d3a120e865..9e7e203c5e83 100644 --- a/arch/sparc/include/asm/hypervisor.h +++ b/arch/sparc/include/asm/hypervisor.h @@ -2930,6 +2930,13 @@ unsigned long sun4v_ncs_request(unsigned long request, #define HV_FAST_FIRE_GET_PERFREG 0x120 #define HV_FAST_FIRE_SET_PERFREG 0x121 +#define HV_FAST_MACH_PRI 0x170 + +#ifndef __ASSEMBLY__ +unsigned long sun4v_mach_pri(unsigned long buffer_ra, + unsigned long *buffer_size); +#endif + #define HV_FAST_REBOOT_DATA_SET 0x172 #ifndef __ASSEMBLY__ diff --git a/arch/sparc/kernel/ds.c b/arch/sparc/kernel/ds.c index 062787045d77..28ce1ecd226b 100644 --- a/arch/sparc/kernel/ds.c +++ b/arch/sparc/kernel/ds.c @@ -39,10 +39,16 @@ #include "kernel.h" /* - * Def to enable kernel timer bug workaround. + * Def to enable timer bug workaround. * See additional comments below. */ -#define DS_KERNEL_TIMER_BUG_WAR 1 +#define DS_TIMER_BUG_WAR 1 + +/* + * Def to enable ioctl to inject a PRI update + * event for testing purposes. + */ +#define DS_PRI_TEST 1 /* * Theory of operation: @@ -103,11 +109,15 @@ MODULE_VERSION(DRV_MODULE_VERSION); #define DS_DEFAULT_BUF_SIZE 4096 #define DS_DEFAULT_MTU 4096 +/* + * The SP DS needs a huge buffer to handle PRI update messages. + * The largest contiguous buffer the kernel seems to allow is 8MB. + */ +#define DS_DEFAULT_SP_BUF_SIZE (8*1024*1024) +#define DS_DEFAULT_SP_MTU (8*1024*1024) #define DS_PRIMARY_ID 0 -#define DS_INVALID_HANDLE 0xFFFFFFFFFFFFFFFFUL - /* * The DS spec mentions that a DS handle is just any random number. * However, the Solaris code uses some conventions to identify server @@ -142,7 +152,9 @@ static DEFINE_SPINLOCK(ds_data_lock); /* protect ds_data */ /* Timeout to wait for responses for sp-token and var-config DS requests */ #define DS_RESPONSE_TIMEOUT 10 /* in seconds */ -#ifdef DS_KERNEL_TIMER_BUG_WAR +#define DS_LDC_READ_DELAY_CNT 10000 /* each CNT will wait 10ms */ + +#ifdef DS_TIMER_BUG_WAR /* * Define a partial type for ldc_channel so the compiler knows * how to indirect ds->lp->lock. This must match the definition in ldc.c @@ -152,7 +164,7 @@ struct ldc_channel { /* Protects all operations that depend upon channel state. */ spinlock_t lock; }; -#endif /* DS_KERNEL_TIMER_BUG_WAR */ +#endif /* DS_TIMER_BUG_WAR */ /* * DS device structure. There is one of these probed/created per @@ -175,9 +187,6 @@ struct ds_dev { /* flag to indicate if this ds_dev is active */ bool active; - /* flag to indicate if this is a domain DS (versus the SP DS) */ - bool is_domain; - /* LDC connection info for this ds_dev */ struct ldc_channel *lp; u8 hs_state; @@ -190,6 +199,7 @@ struct ds_dev { /* LDC receive data buffer for this ds_dev */ u8 *rcv_buf; int rcv_buf_len; + u32 mtu; /* service registration timer */ struct timer_list ds_reg_tmr; @@ -510,14 +520,6 @@ struct ds_suspend_res { #define SUSPEND_REC_SUCCESS 0x0 -struct ds_pri_msg { - u64 req_num; - u64 type; -#define DS_PRI_REQUEST 0x00 -#define DS_PRI_DATA 0x01 -#define DS_PRI_UPDATE 0x02 -}; - struct ds_var_hdr { u32 type; #define DS_VAR_SET_REQ 0x00 @@ -773,6 +775,28 @@ static int __init ldoms_debug_level_setup(char *level_str) } __setup(LDOMS_DEBUG_LEVEL_SETUP, ldoms_debug_level_setup); +static void *ds_zalloc_pages(unsigned long size, gfp_t alloc_flags) +{ + unsigned long order; + void *buf; + + order = get_order(size); + buf = (void *) __get_free_pages(alloc_flags, order); + + if (buf) + memset(buf, 0, PAGE_SIZE << order); + + return buf; +} + +static void ds_free_pages(void *buf, unsigned long size) +{ + if (!buf) + return; + + free_pages((unsigned long)buf, get_order(size)); +} + static void ds_reset(struct ds_dev *ds) { dprintk("entered.\n"); @@ -1231,7 +1255,7 @@ int ds_cap_init(ds_capability_t *cap, ds_ops_t *ops, u32 flags, struct ds_service_info *svc_info = NULL; unsigned long data_flags = 0; unsigned long ds_flags = 0; - bool is_domain; + bool found; dprintk("entered.\n"); @@ -1256,27 +1280,25 @@ int ds_cap_init(ds_capability_t *cap, ds_ops_t *ops, u32 flags, return -EINVAL; } - is_domain = ((flags & DS_TARGET_IS_DOMAIN) != 0); - /* Find the ds_dev associated with domain_hdl. */ + found = false; spin_lock_irqsave(&ds_data_lock, data_flags); - ds = NULL; list_for_each_entry(ds, &ds_data.ds_dev_list, list) { LOCK_DS_DEV(ds, ds_flags) - if ((is_domain && ds->is_domain && ds->handle == domain_hdl) || - (!is_domain && !ds->is_domain)) + if (ds->handle == domain_hdl) { + found = true; break; + } UNLOCK_DS_DEV(ds, ds_flags) } spin_unlock_irqrestore(&ds_data_lock, data_flags); - if (ds == NULL) { - pr_err("%s: Error: dom_hdl %llu (domain=%d) DS " - "port not found\n", __func__, domain_hdl, - ((flags & DS_TARGET_IS_DOMAIN) != 0)); + if (!found) { + pr_err("%s: Error: dom_hdl %llu DS port not found\n", + __func__, domain_hdl); return -ENODEV; } @@ -1903,6 +1925,8 @@ static void __cpuinit ds_dr_cpu_data_cb(ds_cb_arg_t arg, } #endif /* CONFIG_HOTPLUG_CPU */ +static DEFINE_MUTEX(ds_ioctl_req_sptoken_mutex); +static DEFINE_MUTEX(ds_ioctl_hv_pri_mutex); static DEFINE_MUTEX(ds_var_mutex); static DECLARE_COMPLETION(ds_var_config_cb_complete); static DEFINE_MUTEX(ds_var_complete_mutex); @@ -3370,29 +3394,44 @@ static void ds_exec_reg_timer(unsigned long data) struct ds_service_info *svc_info; int rv; -#ifdef DS_KERNEL_TIMER_BUG_WAR +#ifdef DS_TIMER_BUG_WAR /* - * There appears to be a bug in the UEK kernel where - * timers can execute on a CPU where local interrupts - * have been disabled. Deadlocks have been observed - * where the DS registration timer (ds_reg_tmr) can + * There appears to be a bug (someplace) which allows + * this timer to execute on a CPU while another thread + * is also executing on the same CPU while holding a common lock. + * Specifically, deadlocks have been observed + * where this DS registration timer (ds_reg_tmr) can * execute on a CPU, interrupting a thread on the CPU * which is holding the ds->ds_lock or the ds->lp->lock * resulting in a deadlock when the timer attempts - * to grab the lock. As a workaround, the timer handler will - * first check if the locks are held and if so, simply - * reschedule the timer and exit (without grabbing the - * locks - thus avoiding the deadlock). the kernel needs - * to be fixed at some point since executing timers - * on CPUs with local interrupts disabled is a violation - * of spin_lock_irqsave() semantics. + * to grab the lock. As a workaround until this is fixed, + * the timer handler will first check if the locks are held and + * if so, simply reschedule the timer and exit (without grabbing + * the locks - thus avoiding the deadlock). This is a simple + * workaround and has no negative side effects and thus simply + * makes the code more robust in the face of bugs. + * It is unclear at this time whether this is a kernel + * bug (which is supposed to prevent a timer from executing + * on a CPU where local interrupt have been disabled via + * spin_lock_irqsave) or this driver is indirectly sleeping + * (thus being scheduled out) while holding a lock (which could + * also cause this problem). TBD. */ + if (spin_is_locked(&ds->ds_lock) || spin_is_locked(&ds->lp->lock)) { + /* + * NOTE: We are accessing the ds_dev here here without + * grabbing the lock. Potentially dangerous if the timer + * happened to fire after the ds_dev is removed (which + * can happen if the corresponding guest ldom is removed). + * We make sure to call del_timer() in the remove code *before* + * we remove the ds_dev to prevent this scenario. + */ mod_timer(&ds->ds_reg_tmr, jiffies + msecs_to_jiffies(DS_REG_TIMER_FREQ)); return; } -#endif /* DS_KERNEL_TIMER_BUG_WAR */ +#endif /* DS_TIMER_BUG_WAR */ LOCK_DS_DEV(ds, flags) @@ -3719,10 +3758,53 @@ static void ds_up(struct ds_dev *ds) ds->id, rv); } +static int ds_read_ldc_msg(struct ds_dev *ds, unsigned char *buf, + unsigned int size) +{ + unsigned int bytes_left; + unsigned int bytes_read; + unsigned int read_size; + unsigned int delay_cnt; + int rv; + + bytes_left = size; + bytes_read = 0; + delay_cnt = 0; + while (bytes_left) { + + read_size = min_t(int, bytes_left, ds->mtu); + + rv = ldc_read(ds->lp, (void *)(buf + bytes_read), read_size); + + if (delay_cnt++ < DS_LDC_READ_DELAY_CNT && + (rv == -EAGAIN || rv == 0)) { + /* + * For huge messages (such as PRI Update), + * give the other end of the LDC a chance to + * populate data to the LDC. + */ + mdelay(10); + continue; + } + + if (rv <= 0) + break; + + bytes_left -= rv; + bytes_read += rv; + } + + if (rv < 0) + return rv; + else + return bytes_read; +} + static void ds_event(void *arg, int event) { struct ds_dev *ds = arg; unsigned long flags; + unsigned long buf_size; int rv; dprintk("ds-%llu: CPU[%d] event received = %d\n", ds->id, @@ -3759,6 +3841,8 @@ static void ds_event(void *arg, int event) rv = ldc_read(ds->lp, ds->rcv_buf, sizeof(*tag)); + dprintk("ds-%llu: ldc_read 1 returns rv=%d\n", ds->id, rv); + if (unlikely(rv < 0)) { if (rv == -ECONNRESET) ds_reset(ds); @@ -3771,24 +3855,38 @@ static void ds_event(void *arg, int event) tag = (struct ds_msg_tag *)ds->rcv_buf; /* Make sure the read won't overrun our buffer */ - if (tag->len > (DS_DEFAULT_BUF_SIZE - - sizeof(struct ds_msg_tag))) { - pr_err("ds-%llu: %s: msg tag length too big.\n", - ds->id, __func__); + if (ds->handle == DS_SP_DMN_HANDLE) + buf_size = DS_DEFAULT_SP_BUF_SIZE; + else + buf_size = DS_DEFAULT_BUF_SIZE; + + if (tag->len > (buf_size - sizeof(struct ds_msg_tag))) { + pr_err("ds-%llu: %s: received msg length %d too big.\n", + ds->id, __func__, tag->len); ds_reset(ds); break; } - rv = ldc_read(ds->lp, tag + 1, tag->len); + rv = ds_read_ldc_msg(ds, (unsigned char *)(tag + 1), + tag->len); + + dprintk("ds-%llu: ldc_read 2 returns rv=%d\n", ds->id, rv); if (unlikely(rv < 0)) { if (rv == -ECONNRESET) ds_reset(ds); + else + dprintk("ds-%llu: ldc_read_ldc_msg " + "returned err=%d\n", ds->id, rv); + break; } - if (rv < tag->len) + if (rv < tag->len) { + dprintk("ds-%llu: ldc_read returned %d bytes " + "< taglen=%d\n", ds->id, rv, tag->len); break; + } if (tag->type < DS_DATA) { dprintk("ds-%llu: hs data received (%d bytes)\n", @@ -3824,6 +3922,226 @@ static void ds_event(void *arg, int event) spin_unlock_irqrestore(&ds->ds_lock, flags); } +static void *kpri_buf_cache_align16; +unsigned long kpri_buf_cache_len; + +static int ds_get_hv_pri(const void __user *uarg) +{ + ds_ioctl_pri_get_t pri_get_arg; + unsigned long hv_ret; + unsigned long pri_len; + void *kpri_buf; + void *kpri_buf_align16; + int rv; + + /* Get (and validate) userland args */ + if (uarg == NULL || copy_from_user(&pri_get_arg, uarg, + sizeof(ds_ioctl_pri_get_t)) != 0) { + rv = -EFAULT; + goto error_out1; + } + + if (tlb_type != hypervisor) { + rv = -ENXIO; + goto error_out1; + } + + if (kpri_buf_cache_align16) { + /* Use the cached PRI len if set. */ + pri_len = kpri_buf_cache_len; + } else { + /* Get the len of the PRI from the HV */ + pri_len = 0UL; + hv_ret = sun4v_mach_pri(0ULL, &pri_len); + /* NOTE - the HV returns HV_EINVAL when getting the size here */ + if (hv_ret != HV_EOK && hv_ret != HV_EINVAL) { + dprintk("ds: sun4v_mach_pri 1 failed: rv = %lu " + "pri_len = %lu\n", hv_ret, pri_len); + rv = -EIO; + goto error_out1; + } + } + + dprintk("PRI len=%lu bytes\n", pri_len); + + /* + * If the passed in buflen == 0, just return the pri len. + * This allows the caller to get proper len to alloc the buffer. + * Also, if the pri_len is 0, just return success. + */ + if (pri_get_arg.buflen == 0 || pri_len == 0) { + if (put_user(pri_len, (u64 __user *)(pri_get_arg.pri_lenp)) + != 0) { + rv = -EFAULT; + goto error_out1; + } + return 0; + } + + /* Ensure the user supplied buffer is large enough for the PRI */ + if (pri_get_arg.buflen < pri_len) { + dprintk("Supplied buffer for PRI too small (%llu)\n", + pri_get_arg.buflen); + rv = -EINVAL; + goto error_out1; + } + + if (!kpri_buf_cache_align16) { + /* Allocate a contiguous 16 byte aligned buffer for the PRI */ + kpri_buf = kzalloc(pri_len+15, GFP_KERNEL); + if (kpri_buf == NULL) { + rv = -ENOMEM; + goto error_out1; + } + + /* align the buffer on 16 byte boundary */ + kpri_buf_align16 = (void *)(((unsigned long)kpri_buf+15) + & ~0x0FUL); + + hv_ret = sun4v_mach_pri(__pa(kpri_buf_align16), &pri_len); + if (hv_ret != HV_EOK) { + dprintk("ds: sun4v_mach_pri 2 failed: rv = %lu\n", + hv_ret); + rv = -EIO; + kfree(kpri_buf); + goto error_out1; + } + + /* cache the PRI for future use */ + if (!kpri_buf_cache_align16) { + /* + * NOTE - we don't bother retaining the + * original kpri_buf * because we don't + * ever free the PRI buffer once it's cached. + */ + kpri_buf_cache_align16 = kpri_buf_align16; + kpri_buf_cache_len = pri_len; + } + + /* + * Double check again that the user supplied buffer + * is large enough for the PRI since the PRI could have + * changed since we first checked. + */ + if (pri_get_arg.buflen < pri_len) { + dprintk("Supplied buffer for PRI too small (%llu)\n", + pri_get_arg.buflen); + rv = -EINVAL; + goto error_out1; + } + } + + /* populate the pri_len to the user */ + if (put_user(kpri_buf_cache_len, + (u64 __user *)(pri_get_arg.pri_lenp)) != 0) { + rv = -EFAULT; + goto error_out1; + } + + /* populate the PRI to the user buffer */ + if (copy_to_user((void __user *)(pri_get_arg.bufp), + kpri_buf_cache_align16, pri_len) != 0) { + rv = -EFAULT; + goto error_out1; + } + + dprintk("ds: get PRI SUCCESS\n"); + + return 0; + +error_out1: + + dprintk("ds: failed to get PRI rv = %d\n", rv); + + return rv; +} + +#ifdef DS_PRI_TEST +/* PRI protocol data structures */ +struct ds_pri_hdr { + uint64_t seq_num; + uint64_t type; +#define DS_PRI_REQUEST 0 +#define DS_PRI_DATA 1 +#define DS_PRI_UPDATE 2 +}; + +static int ds_set_pri(void) +{ + struct ds_dev *ds; + struct ds_service_info *svc_info; + unsigned long data_flags = 0; + unsigned long ds_flags = 0; + bool found; + struct ds_pri_hdr pri_update; + int msglen; + struct ds_data_req *hdr; + size_t buflen; + int rv; + + /* find the SP DS (if present ) */ + spin_lock_irqsave(&ds_data_lock, data_flags); + found = false; + list_for_each_entry(ds, &ds_data.ds_dev_list, list) { + + LOCK_DS_DEV(ds, ds_flags) + + if (ds->handle == DS_SP_DMN_HANDLE) { + found = true; + break; + } + + UNLOCK_DS_DEV(ds, ds_flags) + } + spin_unlock_irqrestore(&ds_data_lock, data_flags); + + if (!found) { + pr_err("%s: failed to SP DS.\n", __func__); + return -ENODEV; + } + + /* find the provider service on the SP DS for "pri" service */ + svc_info = ds_find_service_provider_id(ds, "pri"); + if (svc_info == NULL) { + pr_err("%s: failed to find SP DS pri service.\n", __func__); + UNLOCK_DS_DEV(ds, ds_flags) + return -ENODEV; + } + if (!svc_info->is_connected) { + pr_err("%s: Error: pri service not connected\n", __func__); + UNLOCK_DS_DEV(ds, ds_flags) + return -EIO; + } + + /* submit a dummy PRI Update event on the SP DS */ + pri_update.seq_num = 1; /* not used for PRI_UPDATE messages */ + pri_update.type = DS_PRI_UPDATE; + buflen = sizeof(struct ds_pri_hdr); + + /* build the data packet containing the data */ + msglen = sizeof(struct ds_data_req) + buflen; + hdr = kzalloc(msglen, GFP_KERNEL); + if (hdr == NULL) { + pr_err("%s: failed to alloc mem for PRI data msg.\n", + __func__); + UNLOCK_DS_DEV(ds, ds_flags) + return -ENOMEM; + } + hdr->tag.type = DS_DATA; + hdr->tag.len = sizeof(struct ds_data_req_payload) + buflen; + hdr->payload.handle = svc_info->con_handle; + (void) memcpy(hdr->payload.data, (void *)&pri_update, buflen); + + rv = ds_submit_data_cb(ds, (struct ds_msg_tag *)hdr, DS_DTYPE_LDC_REQ); + if (rv < 0) + pr_err("%s: ds_submit_data_cb failed.\n ", __func__); + + UNLOCK_DS_DEV(ds, ds_flags) + + return rv; +} +#endif /* DS_PRI_TEST */ + static long ds_fops_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -3841,7 +4159,7 @@ static long ds_fops_ioctl(struct file *filp, unsigned int cmd, switch (cmd) { case DS_SPTOK_GET: - pr_info("%s Getting sp-token\n", __func__); + dprintk("%s: Getting sp-token\n", __func__); uarg = (ds_ioctl_sptok_data_t __user *)arg; if (get_user(major_version, &uarg->major_version) != 0 || get_user(minor_version, &uarg->minor_version) != 0 || @@ -3856,8 +4174,15 @@ static long ds_fops_ioctl(struct file *filp, unsigned int cmd, __func__, major_version, minor_version); return -EINVAL; } + + /* Only allow one thread at a time to request a SP token */ + mutex_lock(&ds_ioctl_req_sptoken_mutex); + rv = ldom_req_sp_token(service_name, &sp_token_result, &sp_token_data); + + mutex_unlock(&ds_ioctl_req_sptoken_mutex); + if (!rv && sp_token_result == DS_SP_TOKEN_RES_OK) { dprintk("Copying sp token to userland\n"); if (copy_to_user(&uarg->sp_tok, @@ -3867,6 +4192,30 @@ static long ds_fops_ioctl(struct file *filp, unsigned int cmd, } } break; + + case DS_PRI_GET: + + dprintk("%s: Getting HV PRI\n", __func__); + + /* Only allow one thread at a time to access the HV PRI */ + mutex_lock(&ds_ioctl_hv_pri_mutex); + + rv = ds_get_hv_pri((const void __user *)arg); + + mutex_unlock(&ds_ioctl_hv_pri_mutex); + + break; + +#ifdef DS_PRI_TEST + case DS_PRI_SET: + + dprintk("%s: Initiating PRI Update\n", __func__); + + rv = ds_set_pri(); + + break; +#endif + default: pr_err("%s Invalid cmd (%d)\n", __func__, cmd); rv = -EINVAL; @@ -3879,7 +4228,6 @@ static int ds_probe(struct vio_dev *vdev, const struct vio_device_id *id) { struct ldc_channel_config ds_cfg = { .event = ds_event, - .mtu = DS_DEFAULT_MTU, .mode = LDC_MODE_STREAM, }; struct mdesc_handle *hp; @@ -3932,28 +4280,63 @@ static int ds_probe(struct vio_dev *vdev, const struct vio_device_id *id) val = mdesc_get_property(hp, node, "ldc-ids", NULL); is_sp = (val != NULL); - val = mdesc_get_property(hp, node, "vlds-remote-domain-handle", - NULL); - if (val == NULL) { - /* Not all DS ports have a handle (such as the SP DS port). */ - ds->handle = DS_INVALID_HANDLE; + if (is_sp) { + /* + * The SP DS node doesn't have a vlds-remote-domain-handle + * property, so we assign a well-known handle which will not + * be used by other domains. This allows the SP DS to be used + * seemlessly via interfaces that were originially designed + * to work solely with domain DS devices. + */ + ds->handle = DS_SP_DMN_HANDLE; } else { - ds->handle = *val; + val = mdesc_get_property(hp, node, "vlds-remote-domain-handle", + NULL); + if (val == NULL) { + mdesc_release(hp); + rv = -ENXIO; + goto out_free_ds; + } else if (*val == DS_SP_DMN_HANDLE) { + /* + * Catch domain handle conflict with SP here. + * Should not happen - but just in case... + */ + pr_err("%s: domain handle (0x%llx) conflict with SP!\n", + __func__, *val); + mdesc_release(hp); + rv = -ENXIO; + goto out_free_ds; + } else { + ds->handle = *val; + } } mdesc_release(hp); - /* If this is not the SP DS, then this is a domain DS */ - ds->is_domain = !is_sp; + /* allocate receive buffers */ + if (is_sp) { + ds->rcv_buf = ds_zalloc_pages(DS_DEFAULT_SP_BUF_SIZE, + GFP_KERNEL); + if (unlikely(!ds->rcv_buf)) + goto out_free_ds; - ds->rcv_buf = kzalloc(DS_DEFAULT_BUF_SIZE, GFP_KERNEL); - if (unlikely(!ds->rcv_buf)) - goto out_free_ds; + ds->rcv_buf_len = DS_DEFAULT_SP_BUF_SIZE; - ds->rcv_buf_len = DS_DEFAULT_BUF_SIZE; + ds_cfg.mtu = DS_DEFAULT_SP_MTU; - ds->hs_state = DS_HS_LDC_DOWN; + } else { + ds->rcv_buf = kzalloc(DS_DEFAULT_BUF_SIZE, + GFP_KERNEL); + if (unlikely(!ds->rcv_buf)) + goto out_free_ds; + + ds->rcv_buf_len = DS_DEFAULT_BUF_SIZE; + ds_cfg.mtu = DS_DEFAULT_MTU; + } + + ds->mtu = ds_cfg.mtu; + ds->hs_state = DS_HS_LDC_DOWN; ds_cfg.debug = 0; ds_cfg.tx_irq = vdev->tx_irq; ds_cfg.rx_irq = vdev->rx_irq; @@ -4024,7 +4407,10 @@ out_free_ldc: ldc_free(ds->lp); out_free_rcv_buf: - kfree(ds->rcv_buf); + if (is_sp) + ds_free_pages(ds->rcv_buf, DS_DEFAULT_SP_BUF_SIZE); + else + kfree(ds->rcv_buf); out_free_ds: kfree(ds); @@ -4064,7 +4450,7 @@ static int ds_remove(struct vio_dev *vdev) list_del(&ds->list); ds_data.num_ds_dev_list--; - del_timer(&ds->ds_reg_tmr); + del_timer_sync(&ds->ds_reg_tmr); ds_remove_services(ds); @@ -4076,7 +4462,10 @@ static int ds_remove(struct vio_dev *vdev) ldc_free(ds->lp); - kfree(ds->rcv_buf); + if (ds->handle == DS_SP_DMN_HANDLE) + ds_free_pages(ds->rcv_buf, DS_DEFAULT_SP_BUF_SIZE); + else + kfree(ds->rcv_buf); /* free any entries left on the callout list */ list_for_each_entry_safe(qhdrp, tmp, &ds->callout_list, list) { diff --git a/arch/sparc/kernel/hvcalls.S b/arch/sparc/kernel/hvcalls.S index 12a5e6676885..40f7e5e5da25 100644 --- a/arch/sparc/kernel/hvcalls.S +++ b/arch/sparc/kernel/hvcalls.S @@ -799,6 +799,15 @@ ENTRY(sun4v_niagara2_setperf) nop ENDPROC(sun4v_niagara2_setperf) +ENTRY(sun4v_mach_pri) + mov %o1, %o4 + ldx [%o1], %o1 + mov HV_FAST_MACH_PRI, %o5 + ta HV_FAST_TRAP + retl + stx %o1, [%o4] +ENDPROC(sun4v_mach_pri) + ENTRY(sun4v_reboot_data_set) mov HV_FAST_REBOOT_DATA_SET, %o5 ta HV_FAST_TRAP diff --git a/arch/sparc/kernel/mdesc.c b/arch/sparc/kernel/mdesc.c index 4d6242ab5f34..169ef9b48b7a 100644 --- a/arch/sparc/kernel/mdesc.c +++ b/arch/sparc/kernel/mdesc.c @@ -161,7 +161,7 @@ static struct mdesc_handle * __init mdesc_memblock_alloc(unsigned int mdesc_size return hp; } -static void __init mdesc_memblock_free(struct mdesc_handle *hp) +static void mdesc_memblock_free(struct mdesc_handle *hp) { unsigned int alloc_size; unsigned long start; @@ -171,7 +171,8 @@ static void __init mdesc_memblock_free(struct mdesc_handle *hp) alloc_size = PAGE_ALIGN(hp->handle_size); start = __pa(hp); - free_bootmem_late(start, alloc_size); + + (void) memblock_free(start, alloc_size); } static struct mdesc_mem_ops memblock_mdesc_ops = { diff --git a/drivers/char/vldc.c b/drivers/char/vldc.c index ef8d266776ef..b38338c272b8 100644 --- a/drivers/char/vldc.c +++ b/drivers/char/vldc.c @@ -126,14 +126,14 @@ static bool vldc_will_write_block(struct vldc_dev *vldc, size_t count) static int vldc_ldc_send(struct vldc_dev *vldc, void *data, int len) { - int err, limit = 1000; + int err, limit = 100; err = -EINVAL; while (limit-- > 0) { err = ldc_write(vldc->lp, data, len); if (!err || (err != -EAGAIN)) break; - udelay(1); + mdelay(100); } return err; @@ -174,40 +174,6 @@ static ssize_t vldc_fops_write(struct file *filp, const char __user *ubuf, goto done; } - if (vldc_will_write_block(vldc, count) && - (filp->f_flags & O_NONBLOCK)) { - rv = -EAGAIN; - goto done; - } - - /* - * Loop here waiting for write space to become available. - * NOTE: we can't wait on an event here because there is no event - * to indicate that write space has become available. - */ - while (vldc_will_write_block(vldc, count)) { - msleep_interruptible(VLDC_WRITE_BLOCK_SLEEP_DELAY); - if (signal_pending(current)) { - /* task caught a signal during the sleep - abort. */ - rv = -EINTR; - goto done; - } - } - - /* - * Check again if the device has been released/closed - * or has been reset while we were waiting. - */ - if (atomic_read(&vldc->is_released)) { - rv = -ENODEV; - goto done; - } - - if (atomic_read(&vldc->is_reset_asserted)) { - rv = -EIO; - goto done; - } - nbytes_left = count; /* number of bytes left to write */ ubufp = (char *)ubuf; @@ -304,35 +270,6 @@ static ssize_t vldc_fops_read(struct file *filp, char __user *ubuf, goto done; } - if (vldc_will_read_block(vldc) && (filp->f_flags & O_NONBLOCK)) { - rv = -EAGAIN; - goto done; - } - - /* - * NOTE: this will only wait if the vldc_will_read_block - * initially returns true - */ - rv = wait_event_interruptible(vldc->waitqueue, - !vldc_will_read_block(vldc)); - if (rv < 0) - goto done; - - /* - * Check again if the device has been released/closed - * or has been reset while we were waiting - */ - if (atomic_read(&vldc->is_released)) { - /* device was released, exit */ - rv = -ENODEV; - goto done; - } - - if (atomic_read(&vldc->is_reset_asserted)) { - rv = -EIO; - goto done; - } - nbytes_left = count; /* number of bytes left to read */ ubufp = (char *)ubuf; @@ -343,7 +280,10 @@ static ssize_t vldc_fops_read(struct file *filp, char __user *ubuf, if (atomic_read(&vldc->mode) == LDC_MODE_RAW) size = max_t(int, LDC_PACKET_SIZE, nbytes_left); else - size = min_t(int, atomic_read(&vldc->mtu), nbytes_left); + size = nbytes_left; + + /* limit read to vldc->mtu */ + size = min_t(int, atomic_read(&vldc->mtu), size); rv = ldc_read(vldc->lp, vldc->rx_buf, size); @@ -433,6 +373,7 @@ static long vldc_read_cookie(struct vldc_dev *vldc, u64 src_addr, u64 dst_addr, char *ubufp; u32 nbytes_read; u32 nbytes_left; + u32 size; dprintk("entered.\n"); @@ -462,11 +403,6 @@ static long vldc_read_cookie(struct vldc_dev *vldc, u64 src_addr, u64 dst_addr, goto done; } - if (unlikely(len > VLDC_MAX_COOKIE)) { - rv = -E2BIG; - goto done; - } - rv = 0; nbytes_left = (u32)len; /* number of bytes left to read */ ubufp = (char *)src_addr; @@ -474,11 +410,14 @@ static long vldc_read_cookie(struct vldc_dev *vldc, u64 src_addr, u64 dst_addr, /* copy in len bytes or until LDC has no more read data (or error) */ while (nbytes_left > 0) { + /* limit read to size VLDC_MAX_COOKIE */ + size = min_t(int, VLDC_MAX_COOKIE, nbytes_left); + cookie.cookie_addr = dst_addr; - cookie.cookie_size = nbytes_left; + cookie.cookie_size = size; rv = ldc_copy(vldc->lp, LDC_COPY_IN, vldc->cookie_read_buf, - nbytes_left, 0, &cookie, 1); + size, 0, &cookie, 1); dprintk("(%s) ldc_copy() returns %d\n", vldc->name, rv); @@ -518,6 +457,7 @@ static long vldc_write_cookie(struct vldc_dev *vldc, u64 src_addr, u64 dst_addr, char *ubufp; u32 nbytes_written; u32 nbytes_left; + u32 size; dprintk("entered.\n"); @@ -547,11 +487,6 @@ static long vldc_write_cookie(struct vldc_dev *vldc, u64 src_addr, u64 dst_addr, goto done; } - if (unlikely(len > VLDC_MAX_COOKIE)) { - rv = -E2BIG; - goto done; - } - rv = 0; nbytes_left = (u32)len; /* number of bytes left to write */ ubufp = (char *)src_addr; @@ -559,17 +494,20 @@ static long vldc_write_cookie(struct vldc_dev *vldc, u64 src_addr, u64 dst_addr, /* copy in len bytes or until LDC has no more read data (or error) */ while (nbytes_left > 0) { + /* limit write to VLDC_MAX_COOKIE */ + size = min_t(int, VLDC_MAX_COOKIE, nbytes_left); + if (copy_from_user(vldc->cookie_write_buf, - ubufp, nbytes_left) != 0) { + ubufp, size) != 0) { rv = -EFAULT; goto done; } cookie.cookie_addr = dst_addr; - cookie.cookie_size = nbytes_left; + cookie.cookie_size = size; rv = ldc_copy(vldc->lp, LDC_COPY_OUT, vldc->cookie_write_buf, - nbytes_left, 0, &cookie, 1); + size, 0, &cookie, 1); dprintk("(%s) ldc_copy() returns %d\n", vldc->name, rv); diff --git a/drivers/char/vlds.c b/drivers/char/vlds.c index fd65777deab7..be6946db9c25 100644 --- a/drivers/char/vlds.c +++ b/drivers/char/vlds.c @@ -40,14 +40,10 @@ module_param(vldsdbg_level, uint, S_IRUGO|S_IWUSR); #define VLDS_MINOR_BASE 0 #define VLDS_MAX_DEVS 65535 /* need one per guest domain - max is 2^20 */ -#define VLDS_MAX_MSG_SIZE (256 * 1024) #define VLDS_SP_INT_NAME DS_SP_NAME /* SP DS internal name */ -#define VLDS_SP_DEV_NAME "sp" /* SP DS device name */ #define VLDS_PATH_MAX 256 -#define VLDS_INVALID_HANDLE 0xFFFFFFFFFFFFFFFFUL - static char driver_version[] = DRV_NAME ".c:v" DRV_VERSION "\n"; #define dprintk(fmt, args...) do {\ @@ -55,7 +51,7 @@ if (vldsdbg_level > 0)\ printk(KERN_ERR "%s: %s: " fmt, DRV_NAME, __func__, ##args);\ } while (0) -/* Global driver data struct for data common to all devices */ +/* Global driver data struct for common data */ struct vlds_driver_data { struct list_head vlds_dev_list; /* list of all vlds devices */ int num_vlds_dev_list; @@ -65,6 +61,7 @@ struct vlds_driver_data { struct vlds_driver_data vlds_data; static DEFINE_MUTEX(vlds_data_mutex); /* protect vlds_data */ +/* VLDS device */ struct vlds_dev { /* link into the global driver data dev list */ struct list_head list; @@ -74,16 +71,34 @@ struct vlds_dev { dev_t devt; char *int_name; /* internal name for device */ struct device *device; - u64 domain_handle; /* only valid for domain dev */ + u64 domain_handle; + + /* open reference count */ + u64 ref_cnt; + + /* flag to indicate that the device has been removed */ + bool removed; /* list of all services for this vlds device */ struct list_head service_info_list; - }; -/* we maintain a global vlds_dev for the SP device */ +/* for convenience, alias to the vlds_dev for the SP device */ struct vlds_dev *sp_vlds; +#define IS_SP_VLDS(vlds_dev) ((vlds_dev) == sp_vlds) +/* + * Service info to describe a service and process(es) using the service. + * Services can be regsitered as shared (the default) or exclusive. + * Exclusive services can only be registered by the initial + * process which registers it. Multiple processes can + * register a shared service. Data received for a service in + * shared mode will be multiplexed to all the processes that are registered + * for the service. Therefore, processes could receive data messages which + * are responses to requests from other processes. Therefore, processes using + * shared services must be careful to only process messages intended for them + * (by using/checking sequence numbers encoded in the message for example). + */ struct vlds_service_info { /* link into the vlds_dev service info list */ struct list_head list; @@ -91,35 +106,62 @@ struct vlds_service_info { /* name/id of the service */ char *name; + /* state of the service connection with ds */ u64 state; - u64 flags; + /* client service (or provider) */ + bool is_client; - /* the thread group id which is using this service */ - pid_t tgid; + /* exclusive service? */ + bool is_exclusive; /* unique handle assigned to this service */ u64 handle; + /* version that was registered */ + vlds_ver_t reg_vers; + /* version that was negotiated */ vlds_ver_t neg_vers; - /* Queue of received data messages for this service */ + /* next service registration ID to use */ + u32 next_svc_reg_id; + + /* the list of processes (thread group ids) using this service */ + struct list_head tgid_list; + +}; +#define VLDS_SVC_IS_CLIENT(svc) ((svc)->is_client) +#define VLDS_SVC_IS_EXCL(svc) ((svc)->is_exclusive) + +struct vlds_tgid_info { + /* link into the vlds_service_info tgid list */ + struct list_head list; + + /* thread group id for associated process */ + pid_t tgid; + + /* service reg ID assigned to this process/svc */ + u32 svc_reg_id; + + /* does the process expect events for this service? */ + bool event_reg; + + /* Queue of received data messages for this service/process */ struct list_head msg_queue; - u64 msg_queue_size; + /* number of messages on the queue - used to limit the # of messages */ + u64 msg_queue_size; }; -#define VLDS_SVC_IS_CLIENT(svc) ((svc)->flags & VLDS_REG_CLIENT) -#define VLDS_SVC_IS_EVENT(svc) ((svc)->flags & VLDS_REG_EVENT) +#define VLDS_MAX_MSG_LIST_NUM 32 struct vlds_msg_data { - /* link into the vlds_service_info message queue */ + /* link into the vlds_tgid_info message queue */ struct list_head list; size_t size; /* message data size */ u8 data[0]; /* message data */ }; -#define VLDS_MAX_MSG_LIST_NUM 16 /* * If a process registers an event fd, we create an @@ -169,7 +211,7 @@ static int vlds_add_event_info(pid_t tgid, int fd) { struct vlds_event_info *event_info; - dprintk("called\n"); + dprintk("entered\n"); event_info = kzalloc(sizeof(struct vlds_event_info), GFP_KERNEL); if (unlikely(event_info == NULL)) { @@ -219,7 +261,7 @@ static void vlds_remove_event_info(pid_t tgid) struct vlds_event *next; bool found; - dprintk("called\n"); + dprintk("entered\n"); found = false; list_for_each_entry(event_info, &vlds_event_info_list, list) { @@ -327,6 +369,29 @@ static int vlds_add_event(pid_t tgid, struct vlds_service_info *svc_info, } +/* vlds_dev_mutex must be held */ +static void vlds_add_event_all(struct vlds_dev *vlds, + struct vlds_service_info *svc_info, u64 type, vlds_ver_t *neg_vers) +{ + struct vlds_tgid_info *tgid_info; + int rv; + + list_for_each_entry(tgid_info, &svc_info->tgid_list, list) { + + /* Only add an event if it's an event registration */ + if (!tgid_info->event_reg) + continue; + + rv = vlds_add_event(tgid_info->tgid, svc_info, + type, neg_vers); + if (rv) { + /* just give an error if we failed to add the event */ + pr_err("%s: Failed to create event (type = %llu)\n", + vlds->int_name, type); + } + } +} + static struct vlds_event *vlds_get_event(struct vlds_event_info *event_info) { @@ -357,28 +422,116 @@ static void vlds_remove_event(struct vlds_event_info *event_info, kfree(event); } -static void vlds_remove_svc_events(struct vlds_service_info *svc_info) +/* remove all events for a tgid/service */ +static void vlds_remove_svc_events_tgid(struct vlds_service_info *svc_info, + struct vlds_tgid_info *tgid_info) { struct vlds_event_info *event_info; struct vlds_event *event; struct vlds_event *next; + int rv; - mutex_lock(&vlds_event_info_list_mutex); + if (!tgid_info->event_reg) + return; - list_for_each_entry(event_info, &vlds_event_info_list, list) { + mutex_lock(&vlds_event_info_list_mutex); + event_info = NULL; + rv = vlds_get_event_info(tgid_info->tgid, &event_info); + if (rv == 0 && event_info != NULL) { list_for_each_entry_safe(event, next, &event_info->event_list, list) { - if (event->svc_info == svc_info) { - list_del(&event->list); - kfree(event); - } + if (event->svc_info == svc_info) + vlds_remove_event(event_info, event); } } mutex_unlock(&vlds_event_info_list_mutex); } +/* vlds_dev_mutex must be held */ +static void vlds_free_msg_queue(struct vlds_tgid_info *tgid_info) +{ + struct vlds_msg_data *msg_data; + struct vlds_msg_data *next; + + list_for_each_entry_safe(msg_data, next, &tgid_info->msg_queue, + list) { + + list_del(&msg_data->list); + + kfree(msg_data); + + tgid_info->msg_queue_size--; + } +} + + +/* vlds_dev_mutex must be held */ +static struct vlds_tgid_info *vlds_get_tgid_info( + struct vlds_service_info *svc_info, pid_t tgid) +{ + struct vlds_tgid_info *tgid_info; + + list_for_each_entry(tgid_info, &svc_info->tgid_list, list) + if (tgid_info->tgid == tgid) + return tgid_info; + + return NULL; +} + +/* vlds_dev_mutex must be held */ +static int vlds_get_primary_tgid(struct vlds_service_info *svc_info, + pid_t *tgid) +{ + struct vlds_tgid_info *tgid_info; + + tgid_info = list_first_entry(&svc_info->tgid_list, + struct vlds_tgid_info, list); + + if (tgid_info == NULL) + return -ENODEV; + + *tgid = tgid_info->tgid; + + return 0; +} + +/* vlds_dev_mutex must be held */ +static int vlds_add_tgid_info(struct vlds_service_info *svc_info, + pid_t tgid, bool event_reg, struct vlds_tgid_info **tgid_info) +{ + struct vlds_tgid_info *new_tgid_info; + + new_tgid_info = kzalloc(sizeof(struct vlds_tgid_info), GFP_KERNEL); + if (unlikely(new_tgid_info == NULL)) + return -ENOMEM; + + new_tgid_info->tgid = tgid; + new_tgid_info->svc_reg_id = svc_info->next_svc_reg_id++; + new_tgid_info->event_reg = event_reg; + INIT_LIST_HEAD(&new_tgid_info->msg_queue); + new_tgid_info->msg_queue_size = 0; + + list_add_tail(&new_tgid_info->list, &svc_info->tgid_list); + + *tgid_info = new_tgid_info; + + return 0; + +} + +/* vlds_dev_mutex must be held */ +static void vlds_remove_tgid_info(struct vlds_tgid_info *tgid_info) +{ + /* remove all the messages queued on this tgid_info */ + vlds_free_msg_queue(tgid_info); + + list_del(&tgid_info->list); + + kfree(tgid_info); +} + static struct vlds_service_info *vlds_get_svc_info(struct vlds_dev *vlds, char *svc_str, bool is_client) { @@ -407,18 +560,24 @@ static struct vlds_service_info *vlds_get_svc_info_hdl(struct vlds_dev *vlds, return NULL; } -/* Add a message to a service message queue */ -static int vlds_add_msg(struct vlds_service_info *svc_info, void *buf, - size_t buflen) +static void vlds_remove_svc_info(struct vlds_service_info *svc_info) +{ + list_del(&svc_info->list); + kfree(svc_info->name); + kfree(svc_info); +} + +static int vlds_add_msg(struct vlds_tgid_info *tgid_info, + void *buf, size_t buflen) { struct vlds_msg_data *msg_data; /* check if we've reached the max num of queued messages */ - if (svc_info->msg_queue_size > VLDS_MAX_MSG_LIST_NUM) + if (tgid_info->msg_queue_size > VLDS_MAX_MSG_LIST_NUM) return -ENOSPC; /* make sure the message size isn't too large */ - if (buflen > VLDS_MAX_MSG_SIZE) + if (buflen > VLDS_MAX_SENDBUF_LEN) return -EFBIG; /* we don't allow enqueing zero length messages */ @@ -435,30 +594,68 @@ static int vlds_add_msg(struct vlds_service_info *svc_info, void *buf, msg_data->size = buflen; /* add it to the queue */ - list_add_tail(&msg_data->list, &svc_info->msg_queue); + list_add_tail(&msg_data->list, &tgid_info->msg_queue); - svc_info->msg_queue_size++; + tgid_info->msg_queue_size++; return 0; } +/* vlds_dev_mutex must be held */ +static void vlds_add_msg_all(struct vlds_dev *vlds, + struct vlds_service_info *svc_info, void *buf, size_t buflen) +{ + struct vlds_tgid_info *tgid_info; + int rv; + + list_for_each_entry(tgid_info, &svc_info->tgid_list, list) { + + rv = vlds_add_msg(tgid_info, buf, buflen); + if (rv) { + if (rv == -ENOSPC) + dprintk("%s: service %s: message queue " + "overflow! (tgid=%u)\n", vlds->int_name, + svc_info->name, tgid_info->tgid); + else if (rv == -EFBIG) + dprintk("%s: service %s: message too large " + "(%lu bytes)! (tgid=%u)\n", + vlds->int_name, svc_info->name, buflen, + tgid_info->tgid); + else + dprintk("%s: service %s: failed to add message " + "(err = %d)! (tgid=%u)\n", vlds->int_name, + svc_info->name, rv, tgid_info->tgid); + } + } +} + /* - * Get a message (data and size) from a service message queue. + * Get a message (data and size) from a service/tgid message queue. * NOTE: the message remains on the queue. */ -static struct vlds_msg_data *vlds_get_msg(struct vlds_service_info *svc_info) +static struct vlds_msg_data *vlds_get_msg(struct vlds_service_info *svc_info, + pid_t tgid) { + struct vlds_tgid_info *tgid_info; struct vlds_msg_data *msg_data; + bool found; - if (list_empty(&svc_info->msg_queue)) { - /* - * TBD: Block instead of return here - * (unless NONBLOCK flag specified). - */ - return NULL; + /* find the tgid_info associated with the process */ + found = false; + list_for_each_entry(tgid_info, &svc_info->tgid_list, list) { + if (tgid_info->tgid == tgid) { + found = true; + break; + } } - msg_data = list_first_entry(&svc_info->msg_queue, struct vlds_msg_data, + if (!found) + return NULL; + + if (list_empty(&tgid_info->msg_queue)) + return NULL; + + msg_data = list_first_entry(&tgid_info->msg_queue, struct vlds_msg_data, list); BUG_ON(msg_data == NULL); @@ -466,48 +663,48 @@ static struct vlds_msg_data *vlds_get_msg(struct vlds_service_info *svc_info) return msg_data; } -/* Dequeue a message from a service message queue. */ +/* Dequeue a message from a service/tgid message queue. */ static void vlds_dequeue_msg(struct vlds_service_info *svc_info, - struct vlds_msg_data *msg_data) + pid_t tgid, struct vlds_msg_data *msg_data) { - if (msg_data == NULL || list_empty(&svc_info->msg_queue)) - return; - - /* Check here that the message is actually on the queue? TBD */ - - list_del(&msg_data->list); + struct vlds_tgid_info *tgid_info; + bool found; - kfree(msg_data); + if (msg_data == NULL) + return; - svc_info->msg_queue_size--; -} + /* find the tgid_info associated with the process */ + found = false; + list_for_each_entry(tgid_info, &svc_info->tgid_list, list) { + if (tgid_info->tgid == tgid) { + found = true; + break; + } + } -static void vlds_free_msg_queue(struct vlds_service_info *svc_info) -{ - struct vlds_msg_data *msg_data; - struct vlds_msg_data *next; + if (!found) + return; - list_for_each_entry_safe(msg_data, next, &svc_info->msg_queue, - list) { + if (list_empty(&tgid_info->msg_queue)) + return; - list_del(&msg_data->list); + /* Check here that the message is actually on the queue? TBD */ - kfree(msg_data); + list_del(&msg_data->list); - svc_info->msg_queue_size--; - } + kfree(msg_data); + tgid_info->msg_queue_size--; } /* - * Callback ops + * Service callback ops */ static void vlds_ds_reg_cb(ds_cb_arg_t arg, ds_svc_hdl_t hdl, ds_ver_t *ver) { struct vlds_dev *vlds; struct vlds_service_info *svc_info; - int rv; dprintk("entered.\n"); @@ -528,20 +725,13 @@ vlds_ds_reg_cb(ds_cb_arg_t arg, ds_svc_hdl_t hdl, ds_ver_t *ver) svc_info->state = VLDS_HDL_STATE_CONNECTED; /* - * if the service requires events, - * add an event to the process's event_info queue + * For every process that has registered this service + * in EVENT mode, register an event. */ - if (VLDS_SVC_IS_EVENT(svc_info)) { - rv = vlds_add_event(svc_info->tgid, svc_info, - VLDS_EVENT_TYPE_REG, &svc_info->neg_vers); - if (rv) { - /* just give an error if we failed to add the event */ - pr_err("%s: failed to create registration event " - "(%llx)\n", vlds->int_name, hdl); - } - } + vlds_add_event_all(vlds, svc_info, VLDS_EVENT_TYPE_REG, + &svc_info->neg_vers); - dprintk("%s: service %s registered version (%u.%u) hdl=%llx\n", + dprintk("%s: service %s register version (%u.%u) hdl=%llx\n", vlds->int_name, svc_info->name, svc_info->neg_vers.vlds_major, svc_info->neg_vers.vlds_minor, hdl); @@ -554,7 +744,6 @@ vlds_ds_unreg_cb(ds_cb_arg_t arg, ds_svc_hdl_t hdl) { struct vlds_dev *vlds; struct vlds_service_info *svc_info; - int rv; dprintk("entered.\n"); @@ -575,20 +764,12 @@ vlds_ds_unreg_cb(ds_cb_arg_t arg, ds_svc_hdl_t hdl) svc_info->state = VLDS_HDL_STATE_DISCONNECTED; /* - * if the service requires events, - * add an event to the process's event_info queue + * For every process that has registered this service + * in EVENT mode, register an event. */ - if (VLDS_SVC_IS_EVENT(svc_info)) { - rv = vlds_add_event(svc_info->tgid, svc_info, - VLDS_EVENT_TYPE_UNREG, NULL); - if (rv) { - /* just give an error if we failed to add the event */ - pr_err("%s: failed to create unregistration event " - "(%llx)\n", vlds->int_name, hdl); - } - } + vlds_add_event_all(vlds, svc_info, VLDS_EVENT_TYPE_UNREG, NULL); - dprintk("%s: service %s unregistered hdl=%llx\n", + dprintk("%s: service %s unregister hdl=%llx\n", vlds->int_name, svc_info->name, hdl); mutex_unlock(&vlds->vlds_mutex); @@ -600,7 +781,6 @@ vlds_ds_data_cb(ds_cb_arg_t arg, ds_svc_hdl_t hdl, void *buf, size_t buflen) { struct vlds_dev *vlds; struct vlds_service_info *svc_info; - int rv; dprintk("entered.\n"); @@ -616,41 +796,21 @@ vlds_ds_data_cb(ds_cb_arg_t arg, ds_svc_hdl_t hdl, void *buf, size_t buflen) return; } - /* received data is assumed to be 1 complete message */ - rv = vlds_add_msg(svc_info, buf, buflen); - if (rv) { - if (rv == -ENOSPC) - dprintk("%s: service %s: message queue overflow!\n", - vlds->int_name, svc_info->name); - else if (rv == -EFBIG) - dprintk("%s: service %s: message too large " - "(%lu bytes)!\n", vlds->int_name, svc_info->name, - buflen); - else - dprintk("%s: service %s: failed to add message " - "(err = %d)!\n", vlds->int_name, - svc_info->name, rv); - - mutex_unlock(&vlds->vlds_mutex); - - return; - } + /* + * For every process that has registered this service + * populate the message into the msg queue. + * NOTE - received data is assumed to be 1 complete message. + * No partial message support. + */ + vlds_add_msg_all(vlds, svc_info, buf, buflen); /* - * if the service requires events, - * add an event to the process's event_info queue + * For every process that has registered this service + * in EVENT mode, register an event. */ - if (VLDS_SVC_IS_EVENT(svc_info)) { - rv = vlds_add_event(svc_info->tgid, svc_info, - VLDS_EVENT_TYPE_DATA, NULL); - if (rv) { - /* just give an error if we failed to add the event */ - pr_err("%s: failed to create data event (%llx)\n", - vlds->int_name, hdl); - } - } + vlds_add_event_all(vlds, svc_info, VLDS_EVENT_TYPE_DATA, NULL); - dprintk("%s: %s service: Received %lu bytes hdl=%llx\n", + dprintk("%s: service %s: Received %lu bytes hdl=%llx\n", vlds->int_name, svc_info->name, buflen, hdl); mutex_unlock(&vlds->vlds_mutex); @@ -669,17 +829,20 @@ static int vlds_svc_reg(struct vlds_dev *vlds, const void __user *uarg) vlds_svc_reg_arg_t svc_reg; vlds_cap_t cap; - char *svc_str; + char svc_str[VLDS_MAX_NAMELEN + 1]; bool is_client_reg; + bool is_excl_reg; + bool is_event_reg; ds_capability_t dscap; u32 flags; ds_svc_hdl_t ds_hdl; - int rv; + pid_t tgid; struct vlds_service_info *svc_info; + struct vlds_tgid_info *tgid_info; + int rv; dprintk("entered.\n"); - svc_str = NULL; svc_info = NULL; /* Get (and validate) userland args */ @@ -696,6 +859,15 @@ static int vlds_svc_reg(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + /* If present, validate svc reg id */ + if (svc_reg.vlds_svc_reg_idp) { + if (!access_ok(VERIFY_WRITE, + (void __user *)svc_reg.vlds_svc_reg_idp, sizeof(u32))) { + rv = -EFAULT; + goto error_out1; + } + } + if (copy_from_user(&cap, (const void __user *)svc_reg.vlds_capp, sizeof(vlds_cap_t)) != 0) { rv = -EFAULT; @@ -710,38 +882,124 @@ static int vlds_svc_reg(struct vlds_dev *vlds, const void __user *uarg) } /* get the service string from userland */ - svc_str = kzalloc(cap.vlds_service.vlds_strlen + 1, GFP_KERNEL); - if (unlikely(svc_str == NULL)) { - rv = -ENOMEM; - goto error_out1; - } - if (copy_from_user(svc_str, (const void __user *)cap.vlds_service.vlds_strp, cap.vlds_service.vlds_strlen) != 0) { rv = -EFAULT; goto error_out1; } + svc_str[cap.vlds_service.vlds_strlen] = '\0'; is_client_reg = (svc_reg.vlds_reg_flags & VLDS_REG_CLIENT); + is_excl_reg = (svc_reg.vlds_reg_flags & VLDS_REG_EXCLUSIVE); + is_event_reg = (svc_reg.vlds_reg_flags & VLDS_REG_EVENT); + tgid = task_tgid_vnr(current); mutex_lock(&vlds->vlds_mutex); - /* Check if the service is already being used */ + /* Check if the service is already registered */ svc_info = vlds_get_svc_info(vlds, svc_str, is_client_reg); if (svc_info != NULL) { - /* This service is already in use */ - rv = -EBUSY; - svc_info = NULL; - goto error_out2; + + /* make sure this process didn't already register it */ + if (vlds_get_tgid_info(svc_info, tgid)) { + rv = -EBUSY; + svc_info = NULL; + goto error_out2; + } + + /* + * Enforce exclusive registration here: + * Another process has already registered this service. + * If this process is attempting to register it exclusive + * or the service is already registered exclusive, deny + * the request. + */ + if (is_excl_reg || VLDS_SVC_IS_EXCL(svc_info)) { + rv = -EBUSY; + svc_info = NULL; + goto error_out2; + } + + /* + * Make sure the registration versions match. + * i.e. cannot register shared service with different + * versions. + */ + if (svc_info->reg_vers.vlds_major != + cap.vlds_vers.vlds_major || svc_info->reg_vers.vlds_minor != + cap.vlds_vers.vlds_minor) { + rv = -EINVAL; + svc_info = NULL; + goto error_out2; + } + + /* populate the service handle to the user */ + if (put_user(svc_info->handle, + (u64 __user *)(svc_reg.vlds_hdlp)) != 0) { + rv = -EFAULT; + svc_info = NULL; + goto error_out2; + } + + /* + * The service is already registered in shared mode, + * just add another tgid to the service. + */ + rv = vlds_add_tgid_info(svc_info, tgid, is_event_reg, + &tgid_info); + if (unlikely(rv != 0)) { + svc_info = NULL; + goto error_out2; + } + + /* Populate (optional) svc reg ID to the user */ + if (svc_reg.vlds_svc_reg_idp) { + if (put_user(tgid_info->svc_reg_id, + (u32 __user *)(svc_reg.vlds_svc_reg_idp)) != 0) { + vlds_remove_tgid_info(tgid_info); + rv = -EFAULT; + svc_info = NULL; + goto error_out2; + } + } + + /* + * If it's an event based registration and the service has + * already been connected/registered with ds, enqueue a reg + * event for the process - since it will probably expect one. + */ + if (is_event_reg && + svc_info->state == VLDS_HDL_STATE_CONNECTED) { + rv = vlds_add_event(tgid, svc_info, + VLDS_EVENT_TYPE_REG, &svc_info->neg_vers); + if (rv) { + /* just give an error if add event fails */ + pr_err("%s: Failed to create registration " + "event (%llx)\n", vlds->int_name, + svc_info->handle); + } + } + + dprintk("%s: registered tgid %u with service %s (client = %u) " + "(hdl = %llx)\n", vlds->int_name, tgid, svc_str, + VLDS_SVC_IS_CLIENT(svc_info), svc_info->handle); + + mutex_unlock(&vlds->vlds_mutex); + + return 0; } + /* + * This is a new service registration. + */ + /* init the ds capability structure */ dscap.svc_id = svc_str; dscap.vers.major = (u64)cap.vlds_vers.vlds_major; dscap.vers.minor = (u64)cap.vlds_vers.vlds_minor; - /* The svc_info will be passed back as an arg to the cb */ + /* The vlds_dev will be passed back as an arg to the callbacks */ vlds_ds_ops.cb_arg = (void *)vlds; flags = 0x0; @@ -750,20 +1008,16 @@ static int vlds_svc_reg(struct vlds_dev *vlds, const void __user *uarg) else flags |= DS_CAP_IS_PROVIDER; - if (vlds != sp_vlds) - flags |= DS_TARGET_IS_DOMAIN; - ds_hdl = 0; rv = ds_cap_init(&dscap, &vlds_ds_ops, flags, vlds->domain_handle, &ds_hdl); if (rv || ds_hdl == 0) { - dprintk("%s: ds_cap_init failed for %s service\n", + dprintk("%s: ds_cap_init failed for service %s\n", vlds->int_name, svc_str); goto error_out2; } - if (copy_to_user((void __user *)(svc_reg.vlds_hdlp), (u64 *)&ds_hdl, - sizeof(u64)) != 0) { + if (put_user(ds_hdl, (u64 __user *)(svc_reg.vlds_hdlp)) != 0) { (void) ds_cap_fini(ds_hdl); rv = -EFAULT; goto error_out2; @@ -771,26 +1025,49 @@ static int vlds_svc_reg(struct vlds_dev *vlds, const void __user *uarg) /* create a service info for the new service */ svc_info = kzalloc(sizeof(struct vlds_service_info), GFP_KERNEL); - if (unlikely(svc_str == NULL)) { + if (unlikely(svc_info == NULL)) { (void) ds_cap_fini(ds_hdl); rv = -ENOMEM; goto error_out2; } - svc_info->name = svc_str; + svc_info->name = kmemdup(svc_str, (strlen(svc_str) + 1), GFP_KERNEL); + if (unlikely(svc_info->name == NULL)) { + (void) ds_cap_fini(ds_hdl); + rv = -ENOMEM; + goto error_out2; + } svc_info->state = VLDS_HDL_STATE_NOT_YET_CONNECTED; - svc_info->flags = svc_reg.vlds_reg_flags; - svc_info->tgid = task_tgid_vnr(current); + svc_info->is_client = is_client_reg; + svc_info->is_exclusive = is_excl_reg; + svc_info->next_svc_reg_id = 1; /* start at 1 */ + INIT_LIST_HEAD(&svc_info->tgid_list); svc_info->handle = (u64)ds_hdl; - INIT_LIST_HEAD(&svc_info->msg_queue); - svc_info->msg_queue_size = 0; + svc_info->reg_vers = cap.vlds_vers; + + rv = vlds_add_tgid_info(svc_info, tgid, is_event_reg, + &tgid_info); + if (unlikely(rv != 0)) { + (void) ds_cap_fini(ds_hdl); + goto error_out2; + } + + /* Populate (optional) svc reg ID to the user */ + if (svc_reg.vlds_svc_reg_idp) { + if (put_user(tgid_info->svc_reg_id, + (u32 __user *)(svc_reg.vlds_svc_reg_idp)) != 0) { + vlds_remove_tgid_info(tgid_info); + rv = -EFAULT; + goto error_out2; + } + } /* add the service_info to the vlds device */ list_add_tail(&svc_info->list, &vlds->service_info_list); - dprintk("%s: registered %s service (client = %llu) " - "(hdl = %llx) (tgid = %u) with ds\n", vlds->int_name, svc_str, - VLDS_SVC_IS_CLIENT(svc_info), svc_info->handle, svc_info->tgid); + dprintk("%s: registered new service %s (client = %u) " + "(hdl = %llx) (tgid = %u)\n", vlds->int_name, svc_str, + VLDS_SVC_IS_CLIENT(svc_info), svc_info->handle, tgid); mutex_unlock(&vlds->vlds_mutex); @@ -802,14 +1079,11 @@ error_out2: error_out1: - dprintk("%s: failed to register service rv = %d\n", vlds->int_name, rv); + dprintk("Failed to register service rv = %d\n", rv); if (svc_info) kfree(svc_info); - if (svc_str) - kfree(svc_str); - return rv; } @@ -817,6 +1091,8 @@ static int vlds_unreg_hdl(struct vlds_dev *vlds, const void __user *uarg) { vlds_unreg_hdl_arg_t unreg; struct vlds_service_info *svc_info; + struct vlds_tgid_info *tgid_info; + pid_t tgid; int rv; dprintk("entered.\n"); @@ -828,6 +1104,8 @@ static int vlds_unreg_hdl(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + tgid = task_tgid_vnr(current); + mutex_lock(&vlds->vlds_mutex); svc_info = vlds_get_svc_info_hdl(vlds, unreg.vlds_hdl); @@ -836,26 +1114,57 @@ static int vlds_unreg_hdl(struct vlds_dev *vlds, const void __user *uarg) goto error_out2; } - /* unregister the service from ds */ + tgid_info = vlds_get_tgid_info(svc_info, tgid); + if (tgid_info == NULL) { + /* This process doesn't have the service registered */ + rv = -ENODEV; + goto error_out2; + } + + /* + * There may be more than one process that has the + * service registered. So, remove the tgid_info for this + * process (which is unregistering the hdl) and if the + * number of processes using the service goes to zero, + * then unregister the service with ds and remove the + * service_info entirely. + */ + vlds_remove_svc_events_tgid(svc_info, tgid_info); + vlds_remove_tgid_info(tgid_info); + + if (!list_empty(&svc_info->tgid_list)) { + + /* there are still other process(es) using the service */ + dprintk("%s: unregistered tgid %u from service %s " + "(client = %u) (hdl = %llx)\n", vlds->int_name, tgid, + svc_info->name, VLDS_SVC_IS_CLIENT(svc_info), + unreg.vlds_hdl); + + mutex_unlock(&vlds->vlds_mutex); + + return 0; + } + + /* this was the last process using the service */ + + /* + * Unregister the service from ds. + * NOTE - once we call ds_cap_fini(), we should NOT get + * any more callbacks for the service (including an unreg + * event)! + */ rv = ds_cap_fini(unreg.vlds_hdl); if (rv) { - dprintk("%s: ds_cap_fini failed for %s service ", + dprintk("%s: ds_cap_fini failed for service %s\n", vlds->int_name, svc_info->name); goto error_out2; } - dprintk("%s: unregistered %s service (client = %llu) " - "(hdl = %llx) with ds\n", vlds->int_name, svc_info->name, + dprintk("%s: unregistered service %s (client = %u) " + "(hdl = %llx)\n", vlds->int_name, svc_info->name, VLDS_SVC_IS_CLIENT(svc_info), unreg.vlds_hdl); - list_del(&svc_info->list); - - /* remove any events referencing this svc_info */ - vlds_remove_svc_events(svc_info); - - kfree(svc_info->name); - vlds_free_msg_queue(svc_info); - kfree(svc_info); + vlds_remove_svc_info(svc_info); mutex_unlock(&vlds->vlds_mutex); @@ -867,8 +1176,7 @@ error_out2: error_out1: - dprintk("%s: failed to unregister service rv = %d\n", - vlds->int_name, rv); + dprintk("Failed to unregister service rv = %d\n", rv); return rv; } @@ -877,14 +1185,13 @@ static int vlds_hdl_lookup(struct vlds_dev *vlds, const void __user *uarg) { vlds_hdl_lookup_arg_t hdl_lookup; struct vlds_service_info *svc_info; - char *svc_str; + char svc_str[VLDS_MAX_NAMELEN + 1]; u64 num_hdls; + pid_t tgid; int rv; dprintk("entered.\n"); - svc_str = NULL; - /* Get (and validate) userland args */ if (uarg == NULL || copy_from_user(&hdl_lookup, uarg, sizeof(vlds_hdl_lookup_arg_t)) != 0) { @@ -898,8 +1205,6 @@ static int vlds_hdl_lookup(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } - /* get the service string */ - /* make sure the service strlen is sane */ if (hdl_lookup.vlds_service.vlds_strlen == 0 || hdl_lookup.vlds_service.vlds_strlen > VLDS_MAX_NAMELEN) { @@ -908,12 +1213,6 @@ static int vlds_hdl_lookup(struct vlds_dev *vlds, const void __user *uarg) } /* get the service string from userland */ - svc_str = kzalloc(hdl_lookup.vlds_service.vlds_strlen + 1, GFP_KERNEL); - if (unlikely(svc_str == NULL)) { - rv = -ENOMEM; - goto error_out1; - } - if (copy_from_user(svc_str, (const void __user *)hdl_lookup.vlds_service.vlds_strp, hdl_lookup.vlds_service.vlds_strlen) != 0) { @@ -921,6 +1220,8 @@ static int vlds_hdl_lookup(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + tgid = task_tgid_vnr(current); + mutex_lock(&vlds->vlds_mutex); svc_info = vlds_get_svc_info(vlds, svc_str, hdl_lookup.vlds_isclient); @@ -929,8 +1230,14 @@ static int vlds_hdl_lookup(struct vlds_dev *vlds, const void __user *uarg) goto error_out2; } - if (copy_to_user((void __user *)(hdl_lookup.vlds_hdlsp), - &svc_info->handle, sizeof(u64)) != 0) { + if (!vlds_get_tgid_info(svc_info, tgid)) { + /* This process doesn't have the service registered */ + rv = -ENODEV; + goto error_out2; + } + + if (put_user(svc_info->handle, + (u64 __user *)(hdl_lookup.vlds_hdlsp)) != 0) { rv = -EFAULT; goto error_out2; } @@ -955,10 +1262,7 @@ error_out2: error_out1: - dprintk("%s: failed to lookup handle rv = %d\n", vlds->int_name, rv); - - if (svc_str) - kfree(svc_str); + dprintk("Failed to lookup handle rv = %d\n", rv); return rv; @@ -976,30 +1280,37 @@ static int vlds_dmn_lookup(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + mutex_lock(&vlds->vlds_mutex); + /* make sure the string buffer size is sane */ if (dmn_lookup.vlds_dname.vlds_strlen < (strlen(vlds->int_name) + 1)) { rv = -EINVAL; - goto error_out1; + goto error_out2; } if (put_user(vlds->domain_handle, (u64 __user *)(dmn_lookup.vlds_dhdlp)) != 0) { rv = -EFAULT; - goto error_out1; + goto error_out2; } if (copy_to_user((void __user *)(dmn_lookup.vlds_dname.vlds_strp), vlds->int_name, (strlen(vlds->int_name) + 1)) != 0) { rv = -EFAULT; - goto error_out1; + goto error_out2; } + mutex_unlock(&vlds->vlds_mutex); + return 0; +error_out2: + + mutex_unlock(&vlds->vlds_mutex); + error_out1: - dprintk("%s: failed to lookup domain info. rv = %d\n", - vlds->int_name, rv); + dprintk("Failed to lookup domain info. rv = %d\n", rv); return rv; } @@ -1009,6 +1320,7 @@ static int vlds_hdl_get_state(struct vlds_dev *vlds, const void __user *uarg) vlds_hdl_get_state_arg_t hdl_get_state; struct vlds_service_info *svc_info; vlds_hdl_state_t hdl_state; + pid_t tgid; int rv; /* Get (and validate) userland args */ @@ -1018,6 +1330,8 @@ static int vlds_hdl_get_state(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + tgid = task_tgid_vnr(current); + mutex_lock(&vlds->vlds_mutex); svc_info = vlds_get_svc_info_hdl(vlds, hdl_get_state.vlds_hdl); @@ -1026,6 +1340,12 @@ static int vlds_hdl_get_state(struct vlds_dev *vlds, const void __user *uarg) goto error_out2; } + if (!vlds_get_tgid_info(svc_info, tgid)) { + /* This process doesn't have the service registered */ + rv = -ENODEV; + goto error_out2; + } + memset(&hdl_state, 0, sizeof(hdl_state)); hdl_state.state = svc_info->state; /* if the state is connected, return the negotiated version */ @@ -1050,7 +1370,7 @@ error_out2: error_out1: - dprintk("%s: failed to get handle state rv = %d\n", vlds->int_name, rv); + dprintk("Failed to get handle state rv = %d\n", rv); return rv; @@ -1061,6 +1381,8 @@ static int vlds_send_msg(struct vlds_dev *vlds, const void __user *uarg) vlds_send_msg_arg_t send_msg; struct vlds_service_info *svc_info; u8 *send_buf; + pid_t tgid; + pid_t primary_tgid; int rv; dprintk("entered.\n"); @@ -1080,6 +1402,8 @@ static int vlds_send_msg(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + tgid = task_tgid_vnr(current); + mutex_lock(&vlds->vlds_mutex); svc_info = vlds_get_svc_info_hdl(vlds, send_msg.vlds_hdl); @@ -1088,12 +1412,39 @@ static int vlds_send_msg(struct vlds_dev *vlds, const void __user *uarg) goto error_out2; } + /* make sure this process has the service registered */ + if (!vlds_get_tgid_info(svc_info, tgid)) { + rv = -ENODEV; + goto error_out2; + } + /* make sure we are in connected state before sending the data */ if (svc_info->state != VLDS_HDL_STATE_CONNECTED) { rv = -EIO; goto error_out2; } + /* + * The SP DS does not handle multiple outstanding messages + * in the LDC tx queue. So, as a workaround, we only allow + * the primary process (i.e. the process which first registered + * the service) to send messages. Non-primary proceses are in + * "read-only" mode - i.e. they can receive messages only. This + * is basically a workaround for libpri and prevents the situation + * where multiple processes are using libpri and attempt to + * send a PRI request message at the same time. We return a -EPERM + * to read-only processes - which libpri knows how to handle. + */ + if (IS_SP_VLDS(vlds)) { + + rv = vlds_get_primary_tgid(svc_info, &primary_tgid); + + if (rv != 0 || primary_tgid != tgid) { + rv = -EPERM; + goto error_out2; + } + } + send_buf = kzalloc(send_msg.vlds_buflen, GFP_KERNEL); if (unlikely(send_buf == NULL)) { rv = -ENOMEM; @@ -1108,12 +1459,7 @@ static int vlds_send_msg(struct vlds_dev *vlds, const void __user *uarg) rv = ds_cap_send(send_msg.vlds_hdl, send_buf, send_msg.vlds_buflen); if (rv) { - - /* - * TBD: If rv == -EAGAIN, block here trying again in loop - * (unless NONBLOCK flag specified). - */ - dprintk("%s: ds_cap_send failed for %s service (rv=%d)\n", + dprintk("%s: ds_cap_send failed for service %s (rv=%d)\n", vlds->int_name, svc_info->name, rv); goto error_out2; } @@ -1133,7 +1479,7 @@ error_out2: error_out1: - dprintk("%s: failed to send msg rv = %d\n", vlds->int_name, rv); + dprintk("Failed to send msg rv = %d\n", rv); if (send_buf != NULL) kfree(send_buf); @@ -1148,8 +1494,9 @@ static int vlds_recv_msg(struct vlds_dev *vlds, const void __user *uarg) struct vlds_service_info *svc_info; u8 *msg; size_t msglen; - int rv; + pid_t tgid; struct vlds_msg_data *msg_data; + int rv; /* Get (and validate) userland args */ if (uarg == NULL || copy_from_user(&recv_msg, uarg, @@ -1163,6 +1510,8 @@ static int vlds_recv_msg(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } + tgid = task_tgid_vnr(current); + mutex_lock(&vlds->vlds_mutex); svc_info = vlds_get_svc_info_hdl(vlds, recv_msg.vlds_hdl); @@ -1171,7 +1520,13 @@ static int vlds_recv_msg(struct vlds_dev *vlds, const void __user *uarg) goto error_out2; } - msg_data = vlds_get_msg(svc_info); + /* make sure this process has the service registered */ + if (!vlds_get_tgid_info(svc_info, tgid)) { + rv = -ENODEV; + goto error_out2; + } + + msg_data = vlds_get_msg(svc_info, tgid); if (msg_data == NULL) { msg = NULL; msglen = 0; @@ -1233,7 +1588,7 @@ static int vlds_recv_msg(struct vlds_dev *vlds, const void __user *uarg) * We successfully copied the data to user, * so dequeue the message */ - vlds_dequeue_msg(svc_info, msg_data); + vlds_dequeue_msg(svc_info, tgid, msg_data); dprintk("%s: recv msg hdl = %llx (len=%lu) SUCCESS\n", vlds->int_name, recv_msg.vlds_hdl, msglen); @@ -1249,8 +1604,7 @@ error_out2: error_out1: - dprintk("%s: failed to recv msg rv = %d\n", - vlds->int_name, rv); + dprintk("Failed to recv msg rv = %d\n", rv); return rv; } @@ -1261,6 +1615,8 @@ static int vlds_set_event_fd(struct vlds_dev *vlds, const void __user *uarg) int rv; pid_t tgid; + dprintk("entered.\n"); + /* Get (and validate) userland args */ if (uarg == NULL || copy_from_user(&set_event_fd, uarg, sizeof(vlds_set_event_fd_arg_t)) != 0) { @@ -1285,15 +1641,11 @@ static int vlds_set_event_fd(struct vlds_dev *vlds, const void __user *uarg) if (rv) goto error_out1; - dprintk("%s: vlds_set_event_fd: SUCCESS\n", vlds->int_name); - return 0; - error_out1: - dprintk("%s: failed to set event fd: rv = %d\n", - vlds->int_name, rv); + dprintk("Failed to set event fd: rv = %d\n", rv); return rv; } @@ -1302,6 +1654,8 @@ static int vlds_unset_event_fd(struct vlds_dev *vlds, const void __user *uarg) { pid_t tgid; + dprintk("entered.\n"); + tgid = task_tgid_vnr(current); mutex_lock(&vlds_event_info_list_mutex); @@ -1310,10 +1664,7 @@ static int vlds_unset_event_fd(struct vlds_dev *vlds, const void __user *uarg) mutex_unlock(&vlds_event_info_list_mutex); - dprintk("%s: vlds_unset_event_fd: SUCCESS\n", vlds->int_name); - return 0; - } static int vlds_get_next_event(struct vlds_dev *vlds, const void __user *uarg) @@ -1324,9 +1675,10 @@ static int vlds_get_next_event(struct vlds_dev *vlds, const void __user *uarg) struct vlds_msg_data *msg_data; u8 *msg; size_t msglen; + pid_t tgid; int rv; - dprintk("called\n"); + dprintk("entered\n"); /* Get (and validate) userland args */ if (uarg == NULL || copy_from_user(&next_event, uarg, @@ -1377,15 +1729,14 @@ static int vlds_get_next_event(struct vlds_dev *vlds, const void __user *uarg) goto error_out1; } - /* user arg is valid, get the next event */ + tgid = task_tgid_vnr(current); mutex_lock(&vlds->vlds_mutex); mutex_lock(&vlds_event_info_list_mutex); - event_info = NULL; - rv = vlds_get_event_info(task_tgid_vnr(current), &event_info); + rv = vlds_get_event_info(tgid, &event_info); if (rv || event_info == NULL) { /* * Process didn't register an event fd! @@ -1429,10 +1780,10 @@ static int vlds_get_next_event(struct vlds_dev *vlds, const void __user *uarg) /* * if it's a data type event, populate the data buffer - * with next message from the service + * with next queued message from the service */ if (event->type == VLDS_EVENT_TYPE_DATA) { - msg_data = vlds_get_msg(event->svc_info); + msg_data = vlds_get_msg(event->svc_info, tgid); if (msg_data == NULL || msg_data->size == 0) { rv = -EIO; goto error_out2; @@ -1465,7 +1816,7 @@ static int vlds_get_next_event(struct vlds_dev *vlds, const void __user *uarg) } /* we copied the data to user, so dequeue the message */ - vlds_dequeue_msg(event->svc_info, msg_data); + vlds_dequeue_msg(event->svc_info, tgid, msg_data); } /* We successfully transferred the event, remove it from the list */ @@ -1486,8 +1837,7 @@ error_out2: error_out1: if (rv != -ENOENT) - dprintk("%s: failed to get next event: rv = %d\n", - vlds->int_name, rv); + dprintk("Failed to get next event: rv = %d\n", rv); return rv; } @@ -1505,75 +1855,140 @@ static int vlds_fops_open(struct inode *inode, struct file *filp) */ vlds = container_of(inode->i_cdev, struct vlds_dev, cdev); + if (vlds == NULL) + return -ENXIO; + + mutex_lock(&vlds->vlds_mutex); + + if (vlds->removed) { + mutex_unlock(&vlds->vlds_mutex); + return -ENXIO; + } + + vlds->ref_cnt++; + + /* tuck away the vlds_dev for other fops to use */ filp->private_data = vlds; + mutex_unlock(&vlds->vlds_mutex); + return 0; } +/* vlds_dev mutex must be held here! */ static void vlds_unreg_all(struct vlds_dev *vlds) { struct vlds_service_info *svc_info; struct vlds_service_info *next; + struct vlds_tgid_info *tgid_info; + struct vlds_tgid_info *tgid_next; if (vlds == NULL) return; - mutex_lock(&vlds->vlds_mutex); - list_for_each_entry_safe(svc_info, next, &vlds->service_info_list, list) { + list_for_each_entry_safe(tgid_info, tgid_next, + &svc_info->tgid_list, list) { + + vlds_remove_svc_events_tgid(svc_info, tgid_info); + vlds_remove_tgid_info(tgid_info); + + } + + /* + * Unregister the service from ds. + * NOTE - once we call ds_cap_fini(), we should NOT get + * any more callbacks for the service (including an unreg + * event)! + */ (void) ds_cap_fini(svc_info->handle); - dprintk("%s: unregistered %s service (client = %llu) " - "(hdl = %llx) with ds\n", vlds->int_name, + dprintk("%s: unregistered service %s (client = %u) " + "(hdl = %llx)\n", vlds->int_name, svc_info->name, VLDS_SVC_IS_CLIENT(svc_info), svc_info->handle); - list_del(&svc_info->list); - vlds_remove_svc_events(svc_info); - kfree(svc_info->name); - vlds_free_msg_queue(svc_info); - kfree(svc_info); - + vlds_remove_svc_info(svc_info); } - mutex_unlock(&vlds->vlds_mutex); - } +/* vlds_dev mutex must be held! */ static void vlds_unreg_all_tgid(struct vlds_dev *vlds, pid_t tgid) { struct vlds_service_info *svc_info; struct vlds_service_info *next; - - mutex_lock(&vlds->vlds_mutex); + struct vlds_tgid_info *tgid_info; + struct vlds_tgid_info *tgid_next; list_for_each_entry_safe(svc_info, next, &vlds->service_info_list, list) { - if (svc_info->tgid == tgid) { + /* + * Check if the tgid is registered for this service and if + * so, remove the tgid from the list. The the tgid list becomes + * empty, it was the last process using the service, so remove + * the service. + */ + list_for_each_entry_safe(tgid_info, tgid_next, + &svc_info->tgid_list, list) { + + if (tgid_info->tgid != tgid) + continue; + + vlds_remove_svc_events_tgid(svc_info, tgid_info); + vlds_remove_tgid_info(tgid_info); + } + + /* If no more processes using the service, remove it */ + if (list_empty(&svc_info->tgid_list)) { (void) ds_cap_fini(svc_info->handle); - dprintk("%s: unregistered %s service " - "(client = %llu) (hdl = %llx) with ds\n", + dprintk("%s: unregistered service %s " + "(client = %u) (hdl = %llx)\n", vlds->int_name, svc_info->name, - VLDS_SVC_IS_CLIENT(svc_info), svc_info->handle); + VLDS_SVC_IS_CLIENT(svc_info), + svc_info->handle); - list_del(&svc_info->list); - - kfree(svc_info->name); - vlds_free_msg_queue(svc_info); - kfree(svc_info); + vlds_remove_svc_info(svc_info); } - } - mutex_unlock(&vlds->vlds_mutex); +} + +/* data_mutex and vlds_dev mutex must be held here! */ +static void vlds_free_vlds_dev(struct vlds_dev *vlds) +{ + if (vlds == NULL) + return; + + pr_info("Removing (%s)\n", vlds->int_name); + /* + * Unregister all the services associated with this vlds. + * NOTE - once we call ds_cap_fini() out of vlds_unreg_all, + * we should NOT get any more callbacks for the services + * (including an unreg event) - which is required since + * the vlds_dev is the callback arg and we are about to + * free it below! + */ + vlds_unreg_all(vlds); + + /* remove vlds_dev from the global list */ + list_del(&vlds->list); + vlds_data.num_vlds_dev_list--; + + /* free it */ + kfree(vlds->int_name); + mutex_destroy(&vlds->vlds_mutex); + kfree(vlds); + + return; } static int vlds_fops_release(struct inode *inode, struct file *filp) @@ -1588,27 +2003,43 @@ static int vlds_fops_release(struct inode *inode, struct file *filp) vlds = filp->private_data; - if (vlds == NULL) { - /* This should not happen, but... */ - pr_err("vlds_fops_release: ERROR- failed to get " - "associated vlds_dev\n"); - return 0; - } + if (vlds == NULL) + return -ENXIO; tgid = task_tgid_vnr(current); + /* + * Since we may have to remove the vlds_dev + * if this is the last release/close, we need + * to lock down the vlds_data and the vlds_dev. + */ + mutex_lock(&vlds_data_mutex); + + mutex_lock(&vlds->vlds_mutex); + dprintk("%s: unregistering all events and services for tgid = %u\n", vlds->int_name, tgid); - /* Remove all events queued for this tgid */ - mutex_lock(&vlds_event_info_list_mutex); + /* + * Unreg all services used by this process on this vlds_dev. + * Also, remove any events queued for these services. + */ + vlds_unreg_all_tgid(vlds, tgid); - vlds_remove_event_info(tgid); + vlds->ref_cnt--; - mutex_unlock(&vlds_event_info_list_mutex); + /* + * If this is the last reference to the removed + * vlds device, remove the vlds_dev completely. + */ + if (vlds->removed && vlds->ref_cnt == 0) { + vlds_free_vlds_dev(vlds); + /* vlds is freed at this point. Don't access it! */ + } else { + mutex_unlock(&vlds->vlds_mutex); + } - /* Close all services used by this process */ - vlds_unreg_all_tgid(vlds, tgid); + mutex_unlock(&vlds_data_mutex); return 0; } @@ -1623,6 +2054,18 @@ static long vlds_fops_ioctl(struct file *filp, unsigned int cmd, vlds = filp->private_data; + if (vlds == NULL) + return -ENXIO; + + mutex_lock(&vlds->vlds_mutex); + + if (vlds->removed) { + mutex_unlock(&vlds->vlds_mutex); + return -ENXIO; + } + + mutex_unlock(&vlds->vlds_mutex); + switch (cmd) { case VLDS_IOCTL_SVC_REG: @@ -1700,6 +2143,34 @@ static const struct file_operations vlds_fops = { .unlocked_ioctl = vlds_fops_ioctl, }; +/* + * Return whether there is a SP DS port present in the MD. + * A domain-services-port MD node with a ldc-ids property indicates + * the presence of a SP DS. + */ +static bool vlds_sp_ds_present(void) +{ + struct mdesc_handle *hp; + u64 node; + const u64 *val; + bool sp_present; + + hp = mdesc_grab(); + + sp_present = false; + mdesc_for_each_node_by_name(hp, node, "domain-services-port") { + val = mdesc_get_property(hp, node, "ldc-ids", NULL); + if (val != NULL) { + sp_present = true; + break; + } + } + + mdesc_release(hp); + + return sp_present; +} + static int vlds_get_next_avail_minor(void) { struct vlds_dev *vlds; @@ -1784,6 +2255,9 @@ static int vlds_alloc_vlds_dev(char *int_name, char *dev_name, vlds->devt)); dprintk("%s: domain_handle = %llu\n", vlds->int_name, domain_handle); + vlds->ref_cnt = 0; + vlds->removed = false; + /* create/add the associated cdev */ cdev_init(&vlds->cdev, &vlds_fops); vlds->cdev.owner = THIS_MODULE; @@ -1925,28 +2399,6 @@ error: return rv; } -static int vlds_free_vlds_dev(struct vlds_dev *vlds) -{ - - dprintk("entered. (%s)\n", vlds->int_name); - - /* Unregister all the services associated with this vlds. */ - vlds_unreg_all(vlds); - - mutex_lock(&vlds_data_mutex); - list_del(&vlds->list); - vlds_data.num_vlds_dev_list--; - mutex_unlock(&vlds_data_mutex); - - device_destroy(vlds_data.chrdev_class, vlds->devt); - cdev_del(&vlds->cdev); - kfree(vlds->int_name); - mutex_destroy(&vlds->vlds_mutex); - kfree(vlds); - - return 0; -} - static int vlds_remove(struct vio_dev *vdev) { int rv; @@ -1959,12 +2411,32 @@ static int vlds_remove(struct vio_dev *vdev) if (vlds == NULL) { dprintk("failed to get vlds_dev from vio_dev.\n"); rv = -ENXIO; + } + + /* lock things down while we try to remove the vlds device */ + mutex_lock(&vlds_data_mutex); + mutex_lock(&vlds->vlds_mutex); + + /* Cleanup the associated cdev, /sys and /dev entry */ + device_destroy(vlds_data.chrdev_class, vlds->devt); + cdev_del(&vlds->cdev); + + /* + * If there are still outstanding references (opens) + * on this device, then set a flag and remove it on + * last close/release. + */ + if (vlds->ref_cnt > 0) { + vlds->removed = true; + mutex_unlock(&vlds->vlds_mutex); } else { - dprintk("removing (%s)\n", vlds->int_name); - rv = vlds_free_vlds_dev(vlds); + vlds_free_vlds_dev(vlds); + /* vlds is freed at this point. Don't access it! */ } - return rv; + mutex_unlock(&vlds_data_mutex); + + return 0; } static const struct vio_device_id vlds_match[] = { @@ -2031,18 +2503,21 @@ static int __init vlds_init(void) goto error; } - /* set callback to create devices under /dev/ds directory */ + /* set callback to create devices under /dev/vlds directory */ vlds_data.chrdev_class->devnode = vlds_devnode; /* - * Add a device for the SP directly since there is no - * vlds-port MD node for the SP and we need one to provide + * If there is a SP DS present on the system (in the MD), + * add a device for the SP directly since there is no + * vlds-port MD node for the SP and this driver provides * access to SP domain services. */ - rv = vlds_alloc_vlds_dev(VLDS_SP_INT_NAME, VLDS_SP_DEV_NAME, - NULL, VLDS_INVALID_HANDLE, &sp_vlds); - if (rv != 0) - dprintk("Failed to create SP vlds device (%d)\n", rv); + if (vlds_sp_ds_present()) { + rv = vlds_alloc_vlds_dev(VLDS_SP_INT_NAME, VLDS_SP_DEV_NAME, + NULL, DS_SP_DMN_HANDLE, &sp_vlds); + if (rv != 0) + dprintk("Failed to create SP vlds device (%d)\n", rv); + } rv = vio_register_driver(&vlds_driver); if (rv != 0) { @@ -2068,7 +2543,20 @@ static void __exit vlds_exit(void) dprintk("entered.\n"); /* remove the SP vlds */ - vlds_free_vlds_dev(sp_vlds); + if (sp_vlds) { + + mutex_lock(&vlds_data_mutex); + mutex_lock(&sp_vlds->vlds_mutex); + + /* Cleanup the associated cdev, /sys and /dev entry */ + device_destroy(vlds_data.chrdev_class, sp_vlds->devt); + cdev_del(&sp_vlds->cdev); + + vlds_free_vlds_dev(sp_vlds); + sp_vlds = NULL; + + mutex_unlock(&vlds_data_mutex); + } /* * Note - vio_unregister_driver() will invoke a call to diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3b7151687776..6602c64b996d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4338,7 +4338,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { bool did_new_scheme = false; - if (use_new_scheme(udev, retry_counter)) { + if (use_new_scheme(udev, retry_counter) && + !(hcd->driver->flags & HCD_USB3)) { struct usb_device_descriptor *buf; int r = 0; diff --git a/include/linux/ds.h b/include/linux/ds.h index 021acb3fae3f..ea66207547eb 100644 --- a/include/linux/ds.h +++ b/include/linux/ds.h @@ -78,6 +78,5 @@ extern int ds_cap_send(ds_svc_hdl_t hdl, void *buf, size_t buflen); #define DS_CAP_IS_CLIENT 0x0001 /* client service */ #define DS_CAP_IS_PROVIDER 0x0002 /* provider service */ -#define DS_TARGET_IS_DOMAIN 0x0004 /* domain target */ #endif /* _DS_H */ diff --git a/include/uapi/linux/ds.h b/include/uapi/linux/ds.h index c73709808f72..f1c26196e732 100644 --- a/include/uapi/linux/ds.h +++ b/include/uapi/linux/ds.h @@ -18,6 +18,19 @@ #define DS_SP_NAME "sp" /* name assigned to the SP DS dev */ +/* + * Since the SP DS device does not have a domain handle assigned + * in the MD (perhaps it should?!), we declare a well-known domain + * handle here for the SP DS device. + * Domain handles are assigned sequencially and so there should never + * be a conflict with this value (the DS module checks for conflicts to + * be sure). Assigning a well-known domain handle + * to the SP DS device allows upper level interfaces to work without + * modification since the interfaces rely on the domain handle + * to distinguish specific DS entities. + */ +#define DS_SP_DMN_HANDLE 0xFFFFFFFFFFFFFFFEUL + typedef struct ds_sptok { __u32 ds_sptok_ipaddr; /* IP address on SP */ __u32 ds_sptok_portid; /* Port number on SP */ @@ -29,10 +42,18 @@ typedef struct ds_ioctl_sptok_data { __u32 minor_version; char service_name[DS_MAX_SVC_NAME_LEN]; ds_sptok_t sp_tok; -} ds_ioctl_sptok_data_t ; +} ds_ioctl_sptok_data_t; + +typedef struct ds_ioctl_pri_get { + u64 bufp; /* buffer to hold pri */ + u64 buflen; /* buffer length */ + u64 pri_lenp; /* pri length (returned) */ +} ds_ioctl_pri_get_t; #define DS_IOCTL_BASE 'D' #define DS_SPTOK_GET _IOR(DS_IOCTL_BASE, 1, ds_ioctl_sptok_data_t) +#define DS_PRI_GET _IOR(DS_IOCTL_BASE, 2, ds_ioctl_pri_get_t) +#define DS_PRI_SET _IOW(DS_IOCTL_BASE, 10, NULL) #endif /* _UAPI_DS_H */ diff --git a/include/uapi/linux/vlds.h b/include/uapi/linux/vlds.h index 5b8d12bf3382..99f26c561bbc 100644 --- a/include/uapi/linux/vlds.h +++ b/include/uapi/linux/vlds.h @@ -8,8 +8,8 @@ #include #include -#define VLDS_DEV_DIR "/dev/vlds" - +#define VLDS_DEV_DIR "/dev/vlds" +#define VLDS_SP_DEV_NAME "sp" /* SP DS device name */ #define VLDS_DEV_DOMAIN_FILENAME_TAG "host:" /* String arguments to ioctl */ @@ -35,11 +35,16 @@ typedef struct vlds_svc_reg_arg { u64 vlds_hdlp; /* DS Service Handle ptr. (returned) */ u64 vlds_capp; /* DS Capability Structure ptr. */ u64 vlds_reg_flags; /* DS reg flags */ + u64 vlds_svc_reg_idp; /* DS Service Registration ID (returned) */ + /* NOTE: reg_id is optional (can be NULL) */ } vlds_svc_reg_arg_t; -/* vlds_reg_flags */ +/* vlds_reg_flags (common to Solaris) */ #define VLDS_REG_CLIENT 0x01 /* Register as client */ -#define VLDS_REG_EVENT 0x02 /* Event driven service - not polled */ + +/* vlds_reg_flags (Linux-specific) */ +#define VLDS_REG_EVENT 0x200 /* Event driven registration */ +#define VLDS_REG_EXCLUSIVE 0x400 /* Service is NOT shareable */ typedef struct vlds_unreg_hdl_arg { u64 vlds_hdl; /* DS Service Handle */ @@ -63,7 +68,7 @@ typedef struct vlds_send_msg_arg { u64 vlds_bufp; /* buffer */ u64 vlds_buflen; /* message length/buffer size */ } vlds_send_msg_arg_t; -#define VLDS_MAX_SENDBUF_LEN 65535 /* 64k max buf size */ +#define VLDS_MAX_SENDBUF_LEN (32 * 1024 * 1024) /* 32 Meg max buf size */ typedef struct vlds_recv_msg_arg { u64 vlds_hdl; /* DS Service Handle */ -- 2.50.1