From 5052ac1e74052fee0baa33a2121a563a2d1a3177 Mon Sep 17 00:00:00 2001 From: Hans Rosenfeld Date: Fri, 24 May 2019 16:55:30 +0200 Subject: [PATCH] OS-8007 want PORT_SOURCE_DEVICE for device-specific port events --- usr/src/man/man3c/port_associate.3c | 56 +- usr/src/man/man3c/port_create.3c | 19 +- usr/src/uts/common/fs/portfs/port.c | 8 + usr/src/uts/common/fs/portfs/port_dev.c | 653 ++++++++++++++++++++++++ usr/src/uts/common/fs/portfs/port_fop.c | 45 +- usr/src/uts/common/os/port_subr.c | 34 +- usr/src/uts/common/sys/port.h | 19 +- usr/src/uts/common/sys/port_kernel.h | 42 +- usr/src/uts/intel/portfs/Makefile | 4 +- usr/src/uts/sparc/portfs/Makefile | 4 +- 10 files changed, 825 insertions(+), 59 deletions(-) create mode 100644 usr/src/uts/common/fs/portfs/port_dev.c diff --git a/usr/src/man/man3c/port_associate.3c b/usr/src/man/man3c/port_associate.3c index 7dd2250d0c99..f8e3b4cb9dde 100644 --- a/usr/src/man/man3c/port_associate.3c +++ b/usr/src/man/man3c/port_associate.3c @@ -1,9 +1,10 @@ '\" te .\" Copyright (c) 2007, Sun Microsystems, Inc. All Rights Reserved. +.\" Copyright 2019 Joyent, Inc. .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License. .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License. .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner] -.TH PORT_ASSOCIATE 3C "April 9, 2016" +.TH PORT_ASSOCIATE 3C "September 26, 2019*" .SH NAME port_associate, port_dissociate \- associate or dissociate the object with the port @@ -35,11 +36,12 @@ a port. .sp .LP The objects that can be associated with a port by way of the -\fBport_associate()\fR function are objects of type \fBPORT_SOURCE_FD\fR and -\fBPORT_SOURCE_FILE\fR. Objects of other types have type-specific association -mechanisms. A \fBport_notify_t\fR structure, defined in \fB\fR, is used -to specify the event port and an application-defined cookie to associate with -these event sources. See \fBport_create\fR(3C) and \fBsignal.h\fR(3HEAD). +\fBport_associate()\fR function are objects of type \fBPORT_SOURCE_FD\fR, +\fBPORT_SOURCE_FILE\fR, and \fBPORT_SOURCE_DEVICE\fR. Objects of other types +have type-specific association mechanisms. A \fBport_notify_t\fR structure, +defined in \fB\fR, is used to specify the event port and an +application-defined cookie to associate with these event sources. See +\fBport_create\fR(3C) and \fBsignal.h\fR(3HEAD). .sp .LP The \fBport_notify_t\fR structure contains the following members: @@ -149,6 +151,33 @@ owner of the association closes the port . .LP On NFS file systems, events from only the client side (local) access/modifications to files or directories will be delivered. +.sp +.LP +Objects of type \fBPORT_SOURCE_DEVICE\fR are a pointer to the structure +\fBdev_obj_t\fR defined in \fB\fR. This event source provides +notification for device-specific events. A minor node of the device to receive +events from must be opened and the file descriptor be set in in the +\fBdev_obj_t\fR object before associating. The device driver may require +additional data by using a device-specific object, which in this case usually +embeds \fBdev_obj_t\fR as its first member. Detailed information about any +device-specific object structure or the events supported by a device are +described in the man page of the device in question. +.sp +.LP +The current version 1 of the \fBdev_obj_t\fR object contains the following +elements: +.sp +.in +2 +.nf +int32_t do_version; /* PORT_SOURCE_DEVICE interface version */ +int32_t do_fd; /* file descriptor of device minor node */ +.fi +.in -2 +.sp +.LP +PORT_DEVICE_VERSION_DEFAULT can be used to request the current version +of the interface. Version 1 can be explicitly requested by using +PORT_DEVICE_VERSION_1. .SH RETURN VALUES .LP Upon successful completion, 0 is returned. Otherwise, \(mi1 is returned and @@ -172,7 +201,20 @@ The \fIport\fR identifier is not valid. .ad .RS 10n The \fIsource\fR argument is of type \fBPORT_SOURCE_FD\fR and the object -argument is not a valid file descriptor. +argument is not a valid file descriptor, or the \fIsource\fR argument is of type +\fBPORT_SOURCE_DEVICE\fR and the object argument does not contain a valid file +descriptor in the \fIdo_fd\fR element. +.RE + +.sp +.ne 2 +.na +\fB\fBENODEV\fR\fR +.ad +.RS 10n +The \fIsource\fR argument is of type \fBPORT_SOURCE_DEVICE\fR and the +\fIdo_fd\fR element of the object is not a file descriptor of an open device +supporting PORT_SOURCE_DEVICE events. .RE .sp diff --git a/usr/src/man/man3c/port_create.3c b/usr/src/man/man3c/port_create.3c index 3421c6d0be69..157a6bc2fce5 100644 --- a/usr/src/man/man3c/port_create.3c +++ b/usr/src/man/man3c/port_create.3c @@ -1,9 +1,10 @@ '\" te .\" Copyright (c) 2008, Sun Microsystems, Inc. All Rights Reserved. +.\" Copyright 2019 Joyent, Inc. .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License. .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License. .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner] -.TH PORT_CREATE 3C "April 9, 2016" +.TH PORT_CREATE 3C "September 26, 2019" .SH NAME port_create \- create a port .SH SYNOPSIS @@ -36,6 +37,7 @@ T} \fBPORT_SOURCE_USER\fR \fBuintptr_t\fR \fBport_send\fR(3C) \fBPORT_SOURCE_ALERT\fR \fBuintptr_t\fR \fBport_alert\fR(3C) \fBPORT_SOURCE_FILE\fR \fBfile_obj_t\fR \fBport_associate\fR(3C) +\fBPORT_SOURCE_DEVICE\fR \fBdev_obj_t\fR \fBport_associate\fR(3C) .TE .sp @@ -81,6 +83,13 @@ active. It has to be reassociated to activate. A file object is associated or reassociated with a port using the \fBport_associate\fR(3C). .sp .LP +\fBPORT_SOURCE_DEVICE\fR events represent device-specific status changes. The +device object is versioned, in the current version 1 the device is specified +by a file descriptor of an open device minor node. Device drivers may expect +additional data following the dev_obj_t, usually by requiring the use of a +device-specific object that contains a dev_obj_t as first member. +.sp +.LP The \fBport_get\fR(3C) and \fBport_getn\fR(3C) functions retrieve events from a port. They ignore non retrievable events (non-own or non-shareable events). .sp @@ -97,10 +106,10 @@ If a port is exported to other processes, the port is destroyed on last close. .sp .LP \fBPORT_SOURCE_USER\fR and \fBPORT_SOURCE_ALERT\fR events can be distributed -across processes. \fBPORT_SOURCE_FD\fR events can only be shared between -processes when child processes inherit opened file decriptors from the parent -process. See \fBfork\fR(2). \fBPORT_SOURCE_TIMER\fR and \fBPORT_SOURCE_AIO\fR -cannot be shared between processes. +across processes. \fBPORT_SOURCE_FD\fR and \fBPORT_SOURCE_DEVICE\fR events can +only be shared between processes when child processes inherit opened file +decriptors from the parent process. See \fBfork\fR(2). \fBPORT_SOURCE_TIMER\fR +and \fBPORT_SOURCE_AIO\fR cannot be shared between processes. .SH RETURN VALUES .LP Upon successful completion, the \fBport_create()\fR function returns a diff --git a/usr/src/uts/common/fs/portfs/port.c b/usr/src/uts/common/fs/portfs/port.c index 91d998b4b508..23d44c8a558d 100644 --- a/usr/src/uts/common/fs/portfs/port.c +++ b/usr/src/uts/common/fs/portfs/port.c @@ -511,6 +511,7 @@ _init(void) port_control.pc_cache = kmem_cache_create("port_cache", sizeof (port_kevent_t), 0, NULL, NULL, NULL, NULL, NULL, 0); + port_dev_init(); /* init PORT_SOURCE_DEVICE */ port_kstat_init(); /* init port kstats */ return (mod_install(&modlinkage)); } @@ -652,6 +653,10 @@ portfs(int opcode, uintptr_t a0, uintptr_t a1, uintptr_t a2, uintptr_t a3, error = port_associate_fop(pp, (int)a1, (uintptr_t)a2, (int)a3, (void *)a4); break; + case PORT_SOURCE_DEVICE: + error = port_associate_dev(pp, (int)a1, (uintptr_t)a2, + (int)a3, (void *)a4); + break; default: error = EINVAL; break; @@ -688,6 +693,9 @@ portfs(int opcode, uintptr_t a0, uintptr_t a1, uintptr_t a2, uintptr_t a3, case PORT_SOURCE_FILE: error = port_dissociate_fop(pp, (uintptr_t)a2); break; + case PORT_SOURCE_DEVICE: + error = port_dissociate_dev(pp, (uintptr_t)a2); + break; default: error = EINVAL; break; diff --git a/usr/src/uts/common/fs/portfs/port_dev.c b/usr/src/uts/common/fs/portfs/port_dev.c new file mode 100644 index 000000000000..7bf6cea60cc5 --- /dev/null +++ b/usr/src/uts/common/fs/portfs/port_dev.c @@ -0,0 +1,653 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019 Joyent, Inc. + */ + +/* + * Device Events Notification + * -------------------------- + * + * This file implements the generic part of the PORT_SOURCE_DEVICE event source. + * It provides an interface for drivers to register a device instance as an + * event source, to unregister it, and to actually send events on behalf of a + * device. The driver needs to provide entry points to handle device-specific + * functions. Two structures describe the state of the event source and an + * associated object: + * + * port_dev_ops_t contains the interface version as first member, followed by a + * set of pointers to driver entry points implementing the device-specific parts + * of this event source. The event source will keep a hash table of these + * structures indexed by the devices dev_info_t. To register or unregister a + * device a set of two functions is used: + * + * - portfs_register_dev(dev_info_t *, const port_dev_ops_t) + * This function will register a device with the event source and store a + * pointer to the port_dev_ops_t in pd_ops_hash for later use. Multiple + * devices managed by the same driver may share a single port_dev_ops_t, but + * each must be registered separately. + * + * - portfs_unregister_dev(dev_info_t *) + * This function removes a device's port_dev_ops_t from pd_ops_hash. + * + * A port_dev_t holds the state of an associated object. It caches the userspace + * object pointer, the users pid, the requested events, the devices dip, the + * minor vnode, the port it is associated to, and a pointer for device-specific + * data. In addition it will also always have a port_kevent_t ready for sending. + * + * Another interface is provided to portfs, consisting of functions to + * initialize this event source, to associate and dissociate the event source + * to/from a port, and a callback for individual events. + * + * + * Association: + * + * A device to be associated with a port is described by a dev_obj_t structure + * in user memory. This structure is versioned, besides the version number it + * contains a file descriptor of a device minor node. Device-specific additional + * data can follow immediately after the dev_obj_t, this is usually achieved by + * embedding the dev_obj_t as the first member of a device-specific structure. + * + * When port_associate_dev() is called, the dev_obj_t passed by the user is + * read and checked for validity. The file descriptor is used to find the + * dev_info_t of the device that is associated on. + * + * Only one association on an object is permitted per port. A port_dev_t + * corresponding to the object will be created to hold the state of that + * association. The drivers pd_port_dev_fill() entry point is called to fill in + * any device-specific data. The association is completed by calling into the + * drivers pd_port_associate() entry point. Calling port_associate() again on + * the same object can be used to inform the driver about changes in the events + * requested, the user cookie, and device-specific data held in the object. + * + * + * Dissociation: + * + * When port_dissociate_dev() is called it will call into the drivers + * pd_port_dissociate() entry point to clean up any device-specific state. + * The port_dev_t will be destroyed and all resources will be freed. + * + * + * Sending events: + * + * Devices can send events using the port_dev_send_event() function, which + * takes a port_dev_t and the events to be sent. Before sending the event to + * the port this function will create a new port_kevent_t and hook it up to the + * port_dev_t, ensuring that there will always be a port_kevent_t available + * for sending. + * + * When an event is about to be received through port_get(), portfs will call + * the callback function set in the port_kevent_t, port_dev_callback(). This + * will in turn call the device-specific callback pd_port_callback() which can + * do further device-specific processing before the event is delivered. + * + * + * Driver entry points: + * + * - pd_port_dev_fill(port_dev_t *pd) + * This entry point is called when a port_dev_t is created during association, + * and when re-associating on an already associated object. The driver can use + * it to set up the device-specific pd_data member of theport_dev_t. + * + * - pd_port_dev_free(port_dev_t *pd) + * This entry point is called to free any resources that a previous call to + * pd_port_dev_fill() allocated. + * + * - pd_port_associate(port_dev_t *pd, int events, void *user) + * This entry point is called to carry out the device-specific parts of the + * association. + * + * - pd_port_dissociate(port_dev_t *pd) + * This entry point is called to carry out the device-specific parts of + * dissociating from an object. + * + * - pd_port_callback(port_dev_t *pd, int *events, pid_t pid, int flag, + * port_kevent_t *pkevp) + * This entry point is called when an event is about to be sent to the user, + * the port is closed, or dissociated. This should be used to free any + * resources allocated for the event and/or updating the object in userspace + * with additional data. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PD_OPS_HASH_NCHAINS 97 +static mod_hash_t *pd_ops_hash; + +/* + * port_dev_t hash table management + * + * Every port has its own hash table of port_dev_t entries, keyed by the user + * object pointer and the user pid. The hash table is kept in the portsrc_data + * member of the PORT_SOURCE_DEVICE event source in the port. + * + * For each user object pointer a list_t of matching port_dev_t elements is + * stored in the hash table. Only one port_dev_t is allowed for any object/pid + * tuple. + * + * Finding and removing a port_dev_t from the hash table returns the port_dev_t + * in a locked state. + */ +#define PD_PORT_HASH_NCHAINS 13 + +typedef struct port_dev_hash { + mod_hash_t *ph_hash; + kmutex_t ph_lock; +} port_dev_hash_t; + +static port_dev_hash_t * +port_dev_hash_get(port_t *pp) +{ + port_source_t *pse; + + pse = port_getsrc(pp, PORT_SOURCE_DEVICE); + VERIFY(pse != NULL); + + return ((port_dev_hash_t *)pse->portsrc_data); +} + +static void +port_dev_hash_destroy(port_t *pp) +{ + port_source_t *pse; + port_dev_hash_t *pdhash; + + pse = port_getsrc(pp, PORT_SOURCE_DEVICE); + VERIFY(pse != NULL); + + VERIFY(MUTEX_HELD(&pp->port_queue.portq_source_mutex)); + pdhash = (port_dev_hash_t *)pse->portsrc_data; + pse->portsrc_data = NULL; + + if (pdhash != NULL) { + mod_hash_destroy_hash(pdhash->ph_hash); + mutex_destroy(&pdhash->ph_lock); + kmem_free(pdhash, sizeof (port_dev_hash_t)); + } +} + +static void +port_dev_hash_delete(port_dev_hash_t *pdhash, uintptr_t object, list_t *pl) +{ + VERIFY(list_is_empty(pl) != 0); + VERIFY0(mod_hash_remove(pdhash->ph_hash, (void *)object, + (mod_hash_val_t *)pl)); + kmem_free(pl, sizeof (list_t)); +} + +static port_dev_t * +port_dev_hash_find_helper(port_dev_hash_t *pdhash, uintptr_t object, pid_t pid, + list_t **pl) +{ + port_dev_t *pd; + + VERIFY(MUTEX_HELD(&pdhash->ph_lock)); + + if (mod_hash_find(pdhash->ph_hash, (void *)object, + (mod_hash_val_t *)pl) != 0) { + return (NULL); + } + + for (pd = list_head(*pl); pd != NULL; pd = list_next(*pl, pd)) { + mutex_enter(&pd->pd_lock); + if (pd->pd_object == object && + pd->pd_pid == pid) { + /* return pd locked */ + return (pd); + } + mutex_exit(&pd->pd_lock); + } + + return (NULL); +} + +/* + * Find the port_dev_t for this object pointer and pid in the hash table. Return + * with the port_dev_t locked. + */ +static port_dev_t * +port_dev_hash_find(port_dev_hash_t *pdhash, uintptr_t object, pid_t pid) +{ + port_dev_t *pd; + list_t *pl; + + mutex_enter(&pdhash->ph_lock); + pd = port_dev_hash_find_helper(pdhash, object, pid, &pl); + mutex_exit(&pdhash->ph_lock); + + return (pd); +} + +/* + * Find the port_dev_t for this object pointer and pid in the hash table and + * remove it. Return with the port_dev_t locked. + */ +static port_dev_t * +port_dev_hash_remove(port_dev_hash_t *pdhash, uintptr_t object, pid_t pid) +{ + port_dev_t *pd; + list_t *pl; + + mutex_enter(&pdhash->ph_lock); + pd = port_dev_hash_find_helper(pdhash, object, pid, &pl); + if (pd != NULL) { + list_remove(pl, pd); + if (list_is_empty(pl)) { + port_dev_hash_delete(pdhash, object, pl); + } + } + mutex_exit(&pdhash->ph_lock); + + return (pd); +} + +/* + * Insert the port_dev_t into the hash table. Return -1 if a port_dev_t with the + * same object/pid is already in the hash table, return 0 on success. + */ +static int +port_dev_hash_insert(port_dev_hash_t *pdhash, port_dev_t *p_dev) +{ + list_t *pl; + port_dev_t *pd; + + mutex_enter(&pdhash->ph_lock); + + if (mod_hash_find(pdhash->ph_hash, (void *)p_dev->pd_object, + (mod_hash_val_t *)&pl) != 0) { + pl = kmem_zalloc(sizeof (list_t), KM_SLEEP); + list_create(pl, sizeof (port_dev_t), + offsetof(port_dev_t, pd_list)); + + VERIFY0(mod_hash_insert(pdhash->ph_hash, + (void *)p_dev->pd_object, (mod_hash_val_t *)pl)); + } else { + for (pd = list_head(pl); pd != NULL; pd = list_next(pl, pd)) { + mutex_enter(&pd->pd_lock); + if (pd->pd_object == p_dev->pd_object && + pd->pd_pid == p_dev->pd_pid) { + mutex_exit(&pd->pd_lock); + mutex_exit(&pdhash->ph_lock); + return (-1); + } + mutex_exit(&pd->pd_lock); + } + } + + list_insert_tail(pl, p_dev); + mutex_exit(&pdhash->ph_lock); + return (0); +} + +/* + * port_close() calls this function to request the PORT_SOURCE_DEVICE source + * remove/free all resources allocated and associated with the port. + */ +static void +port_dev_close(void *arg, int port, pid_t pid, int lastclose) +{ + port_t *pp = arg; + + if (lastclose == 1) + port_dev_hash_destroy(pp); +} + +static void +port_dev_destroy(port_dev_t *p_dev) +{ + VERIFY0(list_link_active(&p_dev->pd_list)); + + p_dev->pd_ops->pd_port_dissociate(p_dev); + p_dev->pd_ops->pd_port_dev_free(p_dev); + + /* + * Don't free the event here if it currently is in the portq, + * port_close_events() will free it later. + */ + if ((p_dev->pd_kev->portkev_flags & PORT_KEV_DONEQ) == 0) + port_free_event_local(p_dev->pd_kev, 0); + + mutex_destroy(&p_dev->pd_lock); + kmem_free(p_dev, sizeof (port_dev_t)); +} + +static void +port_dev_hash_dtor(mod_hash_val_t val) +{ + list_t *pl = val; + port_dev_t *pd; + + for (pd = list_remove_head(pl); pd != NULL; pd = list_remove_head(pl)) + port_dev_destroy(pd); + + kmem_free(pl, sizeof (list_t)); +} + +static int +port_dev_associate_source(port_dev_hash_t **pdhashp, port_t *pp) +{ + port_dev_hash_t *pdhash; + port_source_t *pse; + int ret; + + /* + * Associate PORT_SOURCE_DEVICE with the port if it is not associated + * yet. Note the PORT_SOURCE_DEVICE source is associated once and will + * not be dissociated. + */ + if ((pse = port_getsrc(pp, PORT_SOURCE_DEVICE)) == NULL) { + ret = port_associate_ksource(pp->port_fd, PORT_SOURCE_DEVICE, + &pse, port_dev_close, pp, NULL); + if (ret != 0) { + *pdhashp = NULL; + return (ret); + } + } + + /* + * Get the port_dev hash table. Create it if necessary. + */ + mutex_enter(&pp->port_queue.portq_source_mutex); + + if (pse->portsrc_data != NULL) { + *pdhashp = pse->portsrc_data; + mutex_exit(&pp->port_queue.portq_source_mutex); + return (0); + } + + pdhash = kmem_zalloc(sizeof (port_dev_hash_t), KM_SLEEP); + mutex_init(&pdhash->ph_lock, NULL, MUTEX_DEFAULT, NULL); + pdhash->ph_hash = mod_hash_create_ptrhash("portfs pd_port_hash", + PD_PORT_HASH_NCHAINS, port_dev_hash_dtor, sizeof (port_dev_t *)); + + pse->portsrc_data = pdhash; + mutex_exit(&pp->port_queue.portq_source_mutex); + + *pdhashp = pdhash; + return (0); +} + +static int +port_dev_callback(void *arg, int *events, pid_t pid, int flag, void *evp) +{ + port_kevent_t *pkevp = evp; + port_dev_t *p_dev = arg; + int error = 0; + + if (flag == PORT_CALLBACK_CLOSE) { + /* + * The port is being closed. We must assume our port_dev_t + * has already been freed, so just return without calling + * the driver callback. + */ + return (0); + } + + if (flag == PORT_CALLBACK_DEFAULT) { + /* + * Event will be delivered to the application. + */ + if (curproc->p_pid != pid) + return (EACCES); + + *events = pkevp->portkev_events; + pkevp->portkev_events = 0; + } else if (flag == PORT_CALLBACK_DISSOCIATE) { + /* + * The object will be dissociated from the port. + */ + ; + } else { + return (EINVAL); + } + + mutex_enter(&p_dev->pd_lock); + error = p_dev->pd_ops->pd_port_callback(p_dev, events, pid, flag, + pkevp); + mutex_exit(&p_dev->pd_lock); + + return (error); +} + +static int +port_dev_setup(port_t *pp, dev_info_t *dip, vnode_t *vp, + port_dev_hash_t *pdhash, uintptr_t object, int events, void *user) +{ + port_dev_ops_t *p_ops; + port_dev_t *p_dev; + port_kevent_t *pkevp; + int ret; + + /* + * If there is an existing association of this object with this port, + * update events and user and give the device a chance to update device- + * specific data. + */ + p_dev = port_dev_hash_find(pdhash, object, curproc->p_pid); + + if (p_dev != NULL) { + p_dev->pd_events = events; + p_dev->pd_kev->portkev_user = user; + ret = p_dev->pd_ops->pd_port_dev_fill(p_dev); + mutex_exit(&p_dev->pd_lock); + return (ret); + } + + /* + * Make sure we have a port_dev_ops for this device. + */ + if (mod_hash_find(pd_ops_hash, dip, (mod_hash_val_t *)&p_ops) != 0) + return (ENODEV); + + /* + * Create a port_kevent_t first. + */ + if ((ret = port_alloc_event_local(pp, PORT_SOURCE_DEVICE, + PORT_ALLOC_DEFAULT, &pkevp)) != 0) + return (ret); + + /* + * Allocate the port_dev_t for this object. + */ + p_dev = kmem_zalloc(sizeof (port_dev_t), KM_SLEEP); + mutex_init(&p_dev->pd_lock, NULL, MUTEX_DEFAULT, NULL); + p_dev->pd_object = object; + p_dev->pd_dip = dip; + p_dev->pd_vp = vp; + p_dev->pd_port = pp; + p_dev->pd_pid = curproc->p_pid; + p_dev->pd_kev = pkevp; + p_dev->pd_events = events; + p_dev->pd_ops = p_ops; + + /* + * Initialize event. We use p_dev as argument to the callback. + */ + port_init_event(pkevp, object, user, port_dev_callback, p_dev); + + /* + * Let the device fill in pd_data. + */ + if ((ret = p_dev->pd_ops->pd_port_dev_fill(p_dev)) != 0) { + mutex_destroy(&p_dev->pd_lock); + port_free_event_local(pkevp, 0); + kmem_free(p_dev, sizeof (port_dev_t)); + return (ret); + } + + ret = port_dev_hash_insert(pdhash, p_dev); + if (ret != 0) { + p_dev->pd_ops->pd_port_dev_free(p_dev); + mutex_destroy(&p_dev->pd_lock); + port_free_event_local(pkevp, 0); + kmem_free(p_dev, sizeof (port_dev_t)); + return (EEXIST); + } + + ret = p_dev->pd_ops->pd_port_associate(p_dev, events, user); + + return (ret); +} + +void +port_dev_send_event(port_dev_t *p_dev, int events) +{ + port_kevent_t *old, *new; + + VERIFY(MUTEX_HELD(&p_dev->pd_lock)); + + old = p_dev->pd_kev; + + /* + * Allocate a new event to replace the one we send. + */ + if (port_dup_event(old, &new, PORT_ALLOC_DEFAULT) != 0) + return; + + port_init_event(new, p_dev->pd_object, old->portkev_user, + port_dev_callback, p_dev); + p_dev->pd_kev = new; + + old->portkev_events = events; + port_send_event(old); +} + +int +port_associate_dev(port_t *pp, int source, uintptr_t object, int events, + void *user) +{ + port_dev_hash_t *pdhash; + dev_info_t *dip; + vnode_t *vp; + file_t *fp; + dev_obj_t dp; + int ret; + + ASSERT(source == PORT_SOURCE_DEVICE); + + if (copyin((void *)object, &dp, sizeof (dev_obj_t)) != 0) + return (EFAULT); + + if (dp.do_version != PORT_DEVICE_VERSION_1) + return (EINVAL); + + if (dp.do_fd < 0) + return (EBADFD); + + if ((fp = getf(dp.do_fd)) == NULL) + return (EBADFD); + + vp = fp->f_vnode; + + if (vp->v_type != VBLK && vp->v_type != VCHR) { + releasef(dp.do_fd); + return (ENODEV); + } + + if ((dip = spec_hold_devi_by_vp(vp)) == NULL) { + releasef(dp.do_fd); + return (ENODEV); + } + + /* make sure the port is associated with PORT_SOURCE_DEVICE */ + if (port_dev_associate_source(&pdhash, pp) != 0) { + ddi_release_devi(dip); + releasef(dp.do_fd); + return (ENODEV); + } + + if ((ret = port_dev_setup(pp, dip, vp, pdhash, object, events, + user)) != 0) { + ddi_release_devi(dip); + releasef(dp.do_fd); + return (ret); + } + + ddi_release_devi(dip); + releasef(dp.do_fd); + + return (ret); +} + +int +port_dissociate_dev(port_t *pp, uintptr_t object) +{ + port_dev_t *p_dev; + port_dev_hash_t *pdhash; + int ret; + + pdhash = port_dev_hash_get(pp); + p_dev = port_dev_hash_remove(pdhash, object, curproc->p_pid); + if (p_dev == NULL) + return (ENODEV); + + p_dev->pd_ops->pd_port_dissociate(p_dev); + mutex_exit(&p_dev->pd_lock); + + port_dev_destroy(p_dev); + + return (0); +} + +void +port_dev_init(void) +{ + pd_ops_hash = mod_hash_create_ptrhash("portfs pd_ops_hash", + PD_OPS_HASH_NCHAINS, mod_hash_null_valdtor, + sizeof (port_dev_ops_t *)); + + /* + * mod_hash_create_ptrhash() is guaranteed not to fail due to use of + * sleeping allocations. + */ + ASSERT(pd_ops_hash != NULL); +} + +int +portfs_register_dev(dev_info_t *dip, const port_dev_ops_t *p_ops) +{ + if (p_ops->pd_version != PORT_DEVICE_VERSION_1) + return (EINVAL); + + if (p_ops->pd_port_dev_fill == NULL || + p_ops->pd_port_dev_free == NULL || + p_ops->pd_port_associate == NULL || + p_ops->pd_port_dissociate == NULL || + p_ops->pd_port_callback == NULL) + return (EINVAL); + + if (mod_hash_insert(pd_ops_hash, dip, (mod_hash_val_t)p_ops) != 0) + return (EINVAL); + + return (0); +} + +void +portfs_unregister_dev(dev_info_t *dip) +{ + int ret; + port_dev_ops_t *p_ops; + + ret = mod_hash_remove(pd_ops_hash, dip, (mod_hash_val_t *)&p_ops); + + ASSERT(ret == 0); +} diff --git a/usr/src/uts/common/fs/portfs/port_fop.c b/usr/src/uts/common/fs/portfs/port_fop.c index 019de0540a50..c625a75e1404 100644 --- a/usr/src/uts/common/fs/portfs/port_fop.c +++ b/usr/src/uts/common/fs/portfs/port_fop.c @@ -24,7 +24,7 @@ */ /* - * Copyright (c) 2018, Joyent, Inc. + * Copyright 2019, Joyent, Inc. */ /* @@ -257,7 +257,7 @@ const fs_operation_def_t port_vnodesrc_template[] = { VOPNAME_READ, { .femop_read = port_fop_read }, VOPNAME_WRITE, { .femop_write = port_fop_write }, VOPNAME_MAP, { .femop_map = port_fop_map }, - VOPNAME_SETATTR, { .femop_setattr = port_fop_setattr }, + VOPNAME_SETATTR, { .femop_setattr = port_fop_setattr }, VOPNAME_CREATE, { .femop_create = port_fop_create }, VOPNAME_REMOVE, { .femop_remove = port_fop_remove }, VOPNAME_LINK, { .femop_link = port_fop_link }, @@ -266,7 +266,7 @@ const fs_operation_def_t port_vnodesrc_template[] = { VOPNAME_RMDIR, { .femop_rmdir = port_fop_rmdir }, VOPNAME_READDIR, { .femop_readdir = port_fop_readdir }, VOPNAME_SYMLINK, { .femop_symlink = port_fop_symlink }, - VOPNAME_SETSECATTR, { .femop_setsecattr = port_fop_setsecattr }, + VOPNAME_SETSECATTR, { .femop_setsecattr = port_fop_setsecattr }, VOPNAME_VNEVENT, { .femop_vnevent = port_fop_vnevent }, NULL, NULL }; @@ -275,7 +275,7 @@ const fs_operation_def_t port_vnodesrc_template[] = { * Fsem - vfs ops hooks */ const fs_operation_def_t port_vfssrc_template[] = { - VFSNAME_UNMOUNT, { .fsemop_unmount = port_fop_unmount }, + VFSNAME_UNMOUNT, { .fsemop_unmount = port_fop_unmount }, NULL, NULL }; @@ -731,7 +731,7 @@ port_cache_lookup_fop(portfop_cache_t *pfcp, pid_t pid, uintptr_t obj) */ int port_fop_getdvp(void *objptr, vnode_t **vp, vnode_t **dvp, - char **cname, int *len, int follow) + char **cname, int *len, int follow) { int error = 0; struct pathname pn; @@ -781,31 +781,6 @@ port_fop_getdvp(void *objptr, vnode_t **vp, vnode_t **dvp, return (error); } -port_source_t * -port_getsrc(port_t *pp, int source) -{ - port_source_t *pse; - int lock = 0; - /* - * get the port source structure. - */ - if (!MUTEX_HELD(&pp->port_queue.portq_source_mutex)) { - mutex_enter(&pp->port_queue.portq_source_mutex); - lock = 1; - } - - pse = pp->port_queue.portq_scache[PORT_SHASH(source)]; - for (; pse != NULL; pse = pse->portsrc_next) { - if (pse->portsrc_source == source) - break; - } - - if (lock) { - mutex_exit(&pp->port_queue.portq_source_mutex); - } - return (pse); -} - /* * Compare time stamps and generate an event if it has changed. @@ -815,7 +790,7 @@ port_getsrc(port_t *pp, int source) */ static void port_check_timestamp(portfop_cache_t *pfcp, vnode_t *vp, vnode_t *dvp, - portfop_t *pfp, void *objptr, uintptr_t object) + portfop_t *pfp, void *objptr, uintptr_t object) { vattr_t vatt; portfop_vp_t *pvp = vp->v_fopdata; @@ -1102,8 +1077,8 @@ port_install_fopdata(vnode_t *vp) */ int port_pfp_setup(portfop_t **pfpp, port_t *pp, vnode_t *vp, portfop_cache_t *pfcp, - uintptr_t object, int events, void *user, char *cname, int clen, - vnode_t *dvp) + uintptr_t object, int events, void *user, char *cname, int clen, + vnode_t *dvp) { portfop_t *pfp = NULL; port_kevent_t *pkevp; @@ -1610,7 +1585,7 @@ port_close_fop(void *arg, int port, pid_t pid, int lastclose) portfop_t *pfpnext; int index, i; port_source_t *pse; - vnode_t *tdvp = NULL; + vnode_t *tdvp = NULL; vnode_t *vpl[PORTFOP_NVP]; pse = port_getsrc(pp, PORT_SOURCE_FILE); @@ -1980,7 +1955,7 @@ port_fop(vnode_t *vp, int op, int retval) event |= FILE_TRUNC; } if (event) { - port_fop_sendevent(vp, event, NULL, NULL); + port_fop_sendevent(vp, event, NULL, NULL); } } diff --git a/usr/src/uts/common/os/port_subr.c b/usr/src/uts/common/os/port_subr.c index 4c1de94e20d6..a5eb6dbd00fa 100644 --- a/usr/src/uts/common/os/port_subr.c +++ b/usr/src/uts/common/os/port_subr.c @@ -23,8 +23,9 @@ * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ - -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * Copyright 2019, Joyent, Inc. + */ /* * This file containts all the functions required for interactions of @@ -220,7 +221,7 @@ port_send_event(port_kevent_t *pkevp) * For that reason the event source should allocate an event slot as early * as possible and be prepared to get an error code instead of the * port event pointer. - * Al current event sources allocate an event slot during a system call + * All current event sources allocate an event slot during a system call * entry. They return an error code to the application if an event slot * could not be reserved. * It is also recommended to associate the event source with the port @@ -518,7 +519,7 @@ port_free_event_local(port_kevent_t *pkevp, int counter) * This function initializes most of the "wired" elements of the port * event structure. This is normally being used just after the allocation * of the port event structure. - * pkevp : pointer to the port event structure + * pev : pointer to the port event structure * object : object associated with this event structure * user : user defined pointer delivered with the association function * port_callback: @@ -799,3 +800,28 @@ free_fopdata(vnode_t *vp) kmem_free(pvp, sizeof (*pvp)); vp->v_fopdata = NULL; } + +port_source_t * +port_getsrc(port_t *pp, int source) +{ + port_source_t *pse; + int lock = 0; + /* + * get the port source structure. + */ + if (!MUTEX_HELD(&pp->port_queue.portq_source_mutex)) { + mutex_enter(&pp->port_queue.portq_source_mutex); + lock = 1; + } + + pse = pp->port_queue.portq_scache[PORT_SHASH(source)]; + for (; pse != NULL; pse = pse->portsrc_next) { + if (pse->portsrc_source == source) + break; + } + + if (lock) { + mutex_exit(&pp->port_queue.portq_source_mutex); + } + return (pse); +} diff --git a/usr/src/uts/common/sys/port.h b/usr/src/uts/common/sys/port.h index d4d74d55eacc..38c801380fc4 100644 --- a/usr/src/uts/common/sys/port.h +++ b/usr/src/uts/common/sys/port.h @@ -25,7 +25,7 @@ */ /* - * Copyright (c) 2012, Joyent, Inc. All rights reserved. + * Copyright 2019, Joyent, Inc. */ #ifndef _SYS_PORT_H @@ -36,6 +36,7 @@ extern "C" { #endif #include +#include /* port sources */ #define PORT_SOURCE_AIO 1 @@ -45,6 +46,7 @@ extern "C" { #define PORT_SOURCE_ALERT 5 #define PORT_SOURCE_MQ 6 #define PORT_SOURCE_FILE 7 +#define PORT_SOURCE_DEVICE 8 typedef struct port_event { int portev_events; /* event data is source specific */ @@ -68,6 +70,11 @@ typedef struct file_obj { char *fo_name; /* Null terminated file name */ } file_obj_t; +typedef struct dev_obj { + int32_t do_version; /* device object version */ + int32_t do_fd; /* file descriptor of device minor */ +} dev_obj_t; + #if defined(_SYSCALL32) typedef struct file_obj32 { @@ -88,7 +95,7 @@ typedef struct port_event32 { typedef struct port_notify32 { int portnfy_port; /* bind request(s) to port */ - caddr32_t portnfy_user; /* user defined */ + caddr32_t portnfy_user; /* user defined */ } port_notify32_t; #endif /* _SYSCALL32 */ @@ -138,6 +145,14 @@ typedef struct port_notify32 { #define FILE_EXCEPTION (UNMOUNTED|FILE_DELETE|FILE_RENAME_TO \ |FILE_RENAME_FROM|MOUNTEDOVER) + +/* + * PORT_SOURCE_DEVICE - version + */ +#define PORT_DEVICE_VERSION_1 1 +#define PORT_DEVICE_VERSION_DEFAULT PORT_DEVICE_VERSION_1 + + #ifdef __cplusplus } #endif diff --git a/usr/src/uts/common/sys/port_kernel.h b/usr/src/uts/common/sys/port_kernel.h index 7456f63573d5..8a40856f4081 100644 --- a/usr/src/uts/common/sys/port_kernel.h +++ b/usr/src/uts/common/sys/port_kernel.h @@ -23,14 +23,16 @@ * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ +/* + * Copyright 2019, Joyent, Inc. + */ #ifndef _SYS_PORT_KERNEL_H #define _SYS_PORT_KERNEL_H -#pragma ident "%Z%%M% %I% %E% SMI" - #include #include +#include #ifdef __cplusplus extern "C" { @@ -52,7 +54,7 @@ extern "C" { typedef struct port_kevent { kmutex_t portkev_lock; /* used by PORT_SOURCE_FD source */ int portkev_source; /* event: source */ - int portkev_events; /* event: data */ + int portkev_events; /* event: data */ int portkev_flags; /* internal flags */ pid_t portkev_pid; /* pid of process using this struct */ long portkev_object; /* event: object */ @@ -152,6 +154,7 @@ int port_associate_ksource(int, int, struct port_source **, void (*)(void *, int, pid_t, int), void *arg, int (*)(port_kevent_t *, int, int, uintptr_t, void *)); int port_dissociate_ksource(int, int, struct port_source *); +port_source_t *port_getsrc(struct port *, int); /* event management */ int port_alloc_event(int, int, int, port_kevent_t **); @@ -166,12 +169,45 @@ int port_associate_fd(struct port *, int, uintptr_t, int, void *); int port_dissociate_fd(struct port *, uintptr_t); int port_associate_fop(struct port *, int, uintptr_t, int, void *); int port_dissociate_fop(struct port *, uintptr_t); +int port_associate_dev(struct port *, int, uintptr_t, int, void *); +int port_dissociate_dev(struct port *, uintptr_t); /* misc functions */ void port_free_event_local(port_kevent_t *, int counter); int port_alloc_event_local(struct port *, int, int, port_kevent_t **); void port_close_pfd(struct portfd *); +/* PORT_SOURCE_DEVICE interfaces */ +typedef struct port_dev { + list_node_t pd_list; + kmutex_t pd_lock; + uintptr_t pd_object; + dev_info_t *pd_dip; + vnode_t *pd_vp; + struct port *pd_port; + port_kevent_t *pd_kev; + pid_t pd_pid; + int pd_events; + void *pd_data; + struct port_dev_ops *pd_ops; +} port_dev_t; + +typedef struct port_dev_ops { + int pd_version; + int (*pd_port_dev_fill)(port_dev_t *); + void (*pd_port_dev_free)(port_dev_t *); + int (*pd_port_associate)(port_dev_t *, int, void *); + void (*pd_port_dissociate)(port_dev_t *); + int (*pd_port_callback)(port_dev_t *, int *, pid_t, int, + port_kevent_t *); +} port_dev_ops_t; + +int portfs_register_dev(dev_info_t *, const port_dev_ops_t *); +void portfs_unregister_dev(dev_info_t *); +void port_dev_send_event(port_dev_t *, int); + +void port_dev_init(void); + #endif /* _KERNEL */ #ifdef __cplusplus diff --git a/usr/src/uts/intel/portfs/Makefile b/usr/src/uts/intel/portfs/Makefile index 162a152398a6..076600b19a4d 100644 --- a/usr/src/uts/intel/portfs/Makefile +++ b/usr/src/uts/intel/portfs/Makefile @@ -23,7 +23,7 @@ # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# Copyright (c) 2018, Joyent, Inc. +# Copyright 2019, Joyent, Inc. # @@ -37,7 +37,7 @@ # UTSBASE = ../.. -PORTFS_OBJS += port.o port_vnops.o port_fd.o port_fop.o +PORTFS_OBJS += port.o port_vnops.o port_fd.o port_fop.o port_dev.o # # Define the module and object file sets. diff --git a/usr/src/uts/sparc/portfs/Makefile b/usr/src/uts/sparc/portfs/Makefile index faad9243ee86..0d97398d4241 100644 --- a/usr/src/uts/sparc/portfs/Makefile +++ b/usr/src/uts/sparc/portfs/Makefile @@ -23,6 +23,8 @@ # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # +# Copyright 2019 Joyent, Inc. + # # This makefile drives the production of the port kernel module. @@ -35,7 +37,7 @@ # UTSBASE = ../.. -PORTFS_OBJS += port.o port_vnops.o port_fd.o port_fop.o +PORTFS_OBJS += port.o port_vnops.o port_fd.o port_fop.o port_dev.o # # Define the module and object file sets.