/* * devio.c - cdev I/O for service devices * * Copyright (c) 2016 Cog Systems Pty Ltd * Author: Philip Derrin * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #define VSERVICES_DEVICE_MAX (VS_MAX_SERVICES * VS_MAX_SESSIONS) struct vs_devio_priv { struct kref kref; bool running, reset; /* Receive queue */ wait_queue_head_t recv_wq; atomic_t notify_pending; struct list_head recv_queue; }; static void vs_devio_priv_free(struct kref *kref) { struct vs_devio_priv *priv = container_of(kref, struct vs_devio_priv, kref); WARN_ON(priv->running); WARN_ON(!list_empty_careful(&priv->recv_queue)); WARN_ON(waitqueue_active(&priv->recv_wq)); kfree(priv); } static void vs_devio_priv_put(struct vs_devio_priv *priv) { kref_put(&priv->kref, vs_devio_priv_free); } static int vs_devio_service_probe(struct vs_service_device *service) { struct vs_devio_priv *priv; priv = kmalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; kref_init(&priv->kref); priv->running = false; priv->reset = false; init_waitqueue_head(&priv->recv_wq); atomic_set(&priv->notify_pending, 0); INIT_LIST_HEAD(&priv->recv_queue); dev_set_drvdata(&service->dev, priv); wake_up(&service->quota_wq); return 0; } static int vs_devio_service_remove(struct vs_service_device *service) { struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); WARN_ON(priv->running); WARN_ON(!list_empty_careful(&priv->recv_queue)); WARN_ON(waitqueue_active(&priv->recv_wq)); vs_devio_priv_put(priv); return 0; } static int vs_devio_service_receive(struct vs_service_device *service, struct vs_mbuf *mbuf) { struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); WARN_ON(!priv->running); spin_lock(&priv->recv_wq.lock); list_add_tail(&mbuf->queue, &priv->recv_queue); wake_up_locked(&priv->recv_wq); spin_unlock(&priv->recv_wq.lock); return 0; } static void vs_devio_service_notify(struct vs_service_device *service, u32 flags) { struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); int old, cur; WARN_ON(!priv->running); if (!flags) return; /* open-coded atomic_or() */ cur = atomic_read(&priv->notify_pending); while ((old = atomic_cmpxchg(&priv->notify_pending, cur, cur | flags)) != cur) cur = old; wake_up(&priv->recv_wq); } static void vs_devio_service_start(struct vs_service_device *service) { struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); if (!priv->reset) { WARN_ON(priv->running); priv->running = true; wake_up(&service->quota_wq); } } static void vs_devio_service_reset(struct vs_service_device *service) { struct vs_devio_priv *priv = dev_get_drvdata(&service->dev); struct vs_mbuf *mbuf, *tmp; WARN_ON(!priv->running && !priv->reset); /* * Mark the service as being in reset. This flag can never be cleared * on an open device; the user must acknowledge the reset by closing * and reopening the device. */ priv->reset = true; priv->running = false; spin_lock_irq(&priv->recv_wq.lock); list_for_each_entry_safe(mbuf, tmp, &priv->recv_queue, queue) vs_service_free_mbuf(service, mbuf); INIT_LIST_HEAD(&priv->recv_queue); spin_unlock_irq(&priv->recv_wq.lock); wake_up_all(&priv->recv_wq); } /* * This driver will be registered by the core server module, which must also * set its bus and owner function pointers. */ struct vs_service_driver vs_devio_server_driver = { /* No protocol, so the normal bus match will never bind this. */ .protocol = NULL, .is_server = true, .rx_atomic = true, .probe = vs_devio_service_probe, .remove = vs_devio_service_remove, .receive = vs_devio_service_receive, .notify = vs_devio_service_notify, .start = vs_devio_service_start, .reset = vs_devio_service_reset, /* * Set reasonable default quotas. These can be overridden by passing * nonzero values to IOCTL_VS_BIND_SERVER, which will set the * service's *_quota_set fields. */ .in_quota_min = 1, .in_quota_best = 8, .out_quota_min = 1, .out_quota_best = 8, /* Mark the notify counts as invalid; the service's will be used. */ .in_notify_count = (unsigned)-1, .out_notify_count = (unsigned)-1, .driver = { .name = "vservices-server-devio", .owner = NULL, /* set by core server */ .bus = NULL, /* set by core server */ .suppress_bind_attrs = true, /* see vs_devio_poll */ }, }; EXPORT_SYMBOL_GPL(vs_devio_server_driver); static int vs_devio_bind_server(struct vs_service_device *service, struct vs_ioctl_bind *bind) { int ret = -ENODEV; /* Ensure the server module is loaded and the driver is registered. */ if (!try_module_get(vs_devio_server_driver.driver.owner)) goto fail_module_get; device_lock(&service->dev); ret = -EBUSY; if (service->dev.driver != NULL) goto fail_device_unbound; /* Set up the quota and notify counts. */ service->in_quota_set = bind->recv_quota; service->out_quota_set = bind->send_quota; service->notify_send_bits = bind->send_notify_bits; service->notify_recv_bits = bind->recv_notify_bits; /* Manually probe the driver. */ service->dev.driver = &vs_devio_server_driver.driver; ret = service->dev.bus->probe(&service->dev); if (ret < 0) goto fail_probe_driver; ret = device_bind_driver(&service->dev); if (ret < 0) goto fail_bind_driver; /* Pass the allocated quotas back to the user. */ bind->recv_quota = service->recv_quota; bind->send_quota = service->send_quota; bind->msg_size = vs_service_max_mbuf_size(service); device_unlock(&service->dev); module_put(vs_devio_server_driver.driver.owner); return 0; fail_bind_driver: ret = service->dev.bus->remove(&service->dev); fail_probe_driver: service->dev.driver = NULL; fail_device_unbound: device_unlock(&service->dev); module_put(vs_devio_server_driver.driver.owner); fail_module_get: return ret; } /* * This driver will be registered by the core client module, which must also * set its bus and owner pointers. */ struct vs_service_driver vs_devio_client_driver = { /* No protocol, so the normal bus match will never bind this. */ .protocol = NULL, .is_server = false, .rx_atomic = true, .probe = vs_devio_service_probe, .remove = vs_devio_service_remove, .receive = vs_devio_service_receive, .notify = vs_devio_service_notify, .start = vs_devio_service_start, .reset = vs_devio_service_reset, .driver = { .name = "vservices-client-devio", .owner = NULL, /* set by core client */ .bus = NULL, /* set by core client */ .suppress_bind_attrs = true, /* see vs_devio_poll */ }, }; EXPORT_SYMBOL_GPL(vs_devio_client_driver); static int vs_devio_bind_client(struct vs_service_device *service, struct vs_ioctl_bind *bind) { int ret = -ENODEV; /* Ensure the client module is loaded and the driver is registered. */ if (!try_module_get(vs_devio_client_driver.driver.owner)) goto fail_module_get; device_lock(&service->dev); ret = -EBUSY; if (service->dev.driver != NULL) goto fail_device_unbound; /* Manually probe the driver. */ service->dev.driver = &vs_devio_client_driver.driver; ret = service->dev.bus->probe(&service->dev); if (ret < 0) goto fail_probe_driver; ret = device_bind_driver(&service->dev); if (ret < 0) goto fail_bind_driver; /* Pass the allocated quotas back to the user. */ bind->recv_quota = service->recv_quota; bind->send_quota = service->send_quota; bind->msg_size = vs_service_max_mbuf_size(service); bind->send_notify_bits = service->notify_send_bits; bind->recv_notify_bits = service->notify_recv_bits; device_unlock(&service->dev); module_put(vs_devio_client_driver.driver.owner); return 0; fail_bind_driver: ret = service->dev.bus->remove(&service->dev); fail_probe_driver: service->dev.driver = NULL; fail_device_unbound: device_unlock(&service->dev); module_put(vs_devio_client_driver.driver.owner); fail_module_get: return ret; } static struct vs_devio_priv * vs_devio_priv_get_from_service(struct vs_service_device *service) { struct vs_devio_priv *priv = NULL; struct device_driver *drv; if (!service) return NULL; device_lock(&service->dev); drv = service->dev.driver; if ((drv == &vs_devio_client_driver.driver) || (drv == &vs_devio_server_driver.driver)) { vs_service_state_lock(service); priv = dev_get_drvdata(&service->dev); if (priv) kref_get(&priv->kref); vs_service_state_unlock(service); } device_unlock(&service->dev); return priv; } static int vs_devio_open(struct inode *inode, struct file *file) { struct vs_service_device *service; if (imajor(inode) != vservices_cdev_major) return -ENODEV; service = vs_service_lookup_by_devt(inode->i_rdev); if (!service) return -ENODEV; file->private_data = service; return 0; } static int vs_devio_release(struct inode *inode, struct file *file) { struct vs_service_device *service = file->private_data; if (service) { struct vs_devio_priv *priv = vs_devio_priv_get_from_service(service); if (priv) { device_release_driver(&service->dev); vs_devio_priv_put(priv); } file->private_data = NULL; vs_put_service(service); } return 0; } static struct iovec * vs_devio_check_iov(struct vs_ioctl_iovec *io, bool is_send, ssize_t *total) { struct iovec *iov; unsigned i; int ret; if (io->iovcnt > UIO_MAXIOV) return ERR_PTR(-EINVAL); iov = kmalloc(sizeof(*iov) * io->iovcnt, GFP_KERNEL); if (!iov) return ERR_PTR(-ENOMEM); if (copy_from_user(iov, io->iov, sizeof(*iov) * io->iovcnt)) { ret = -EFAULT; goto fail; } *total = 0; for (i = 0; i < io->iovcnt; i++) { ssize_t iov_len = (ssize_t)iov[i].iov_len; if (iov_len > MAX_RW_COUNT - *total) { ret = -EINVAL; goto fail; } if (!access_ok(is_send ? VERIFY_READ : VERIFY_WRITE, iov[i].iov_base, iov_len)) { ret = -EFAULT; goto fail; } *total += iov_len; } return iov; fail: kfree(iov); return ERR_PTR(ret); } static ssize_t vs_devio_send(struct vs_service_device *service, struct iovec *iov, size_t iovcnt, ssize_t to_send, bool nonblocking) { struct vs_mbuf *mbuf = NULL; struct vs_devio_priv *priv; unsigned i; ssize_t offset = 0; ssize_t ret; DEFINE_WAIT(wait); priv = vs_devio_priv_get_from_service(service); ret = -ENODEV; if (!priv) goto fail_priv_get; vs_service_state_lock(service); /* * Waiting alloc. We must open-code this because there is no real * state structure or base state. */ ret = 0; while (!vs_service_send_mbufs_available(service)) { if (nonblocking) { ret = -EAGAIN; break; } if (signal_pending(current)) { ret = -ERESTARTSYS; break; } prepare_to_wait_exclusive(&service->quota_wq, &wait, TASK_INTERRUPTIBLE); vs_service_state_unlock(service); schedule(); vs_service_state_lock(service); if (priv->reset) { ret = -ECONNRESET; break; } if (!priv->running) { ret = -ENOTCONN; break; } } finish_wait(&service->quota_wq, &wait); if (ret) goto fail_alloc; mbuf = vs_service_alloc_mbuf(service, to_send, GFP_KERNEL); if (IS_ERR(mbuf)) { ret = PTR_ERR(mbuf); goto fail_alloc; } /* Ready to send; copy data into the mbuf. */ ret = -EFAULT; for (i = 0; i < iovcnt; i++) { if (copy_from_user(mbuf->data + offset, iov[i].iov_base, iov[i].iov_len)) goto fail_copy; offset += iov[i].iov_len; } mbuf->size = to_send; /* Send the message. */ ret = vs_service_send(service, mbuf); if (ret < 0) goto fail_send; /* Wake the next waiter, if there's more quota available. */ if (waitqueue_active(&service->quota_wq) && vs_service_send_mbufs_available(service) > 0) wake_up(&service->quota_wq); vs_service_state_unlock(service); vs_devio_priv_put(priv); return to_send; fail_send: fail_copy: vs_service_free_mbuf(service, mbuf); wake_up(&service->quota_wq); fail_alloc: vs_service_state_unlock(service); vs_devio_priv_put(priv); fail_priv_get: return ret; } static ssize_t vs_devio_recv(struct vs_service_device *service, struct iovec *iov, size_t iovcnt, u32 *notify_bits, ssize_t recv_space, bool nonblocking) { struct vs_mbuf *mbuf = NULL; struct vs_devio_priv *priv; unsigned i; ssize_t offset = 0; ssize_t ret; DEFINE_WAIT(wait); priv = vs_devio_priv_get_from_service(service); ret = -ENODEV; if (!priv) goto fail_priv_get; /* Take the recv_wq lock, which also protects recv_queue. */ spin_lock_irq(&priv->recv_wq.lock); /* Wait for a message, notification, or reset. */ ret = wait_event_interruptible_exclusive_locked_irq(priv->recv_wq, !list_empty(&priv->recv_queue) || priv->reset || atomic_read(&priv->notify_pending) || nonblocking); if (priv->reset) ret = -ECONNRESET; /* Service reset */ else if (!ret && list_empty(&priv->recv_queue)) ret = -EAGAIN; /* Nonblocking, or notification */ if (ret < 0) { spin_unlock_irq(&priv->recv_wq.lock); goto no_mbuf; } /* Take the first mbuf from the list, and check its size. */ mbuf = list_first_entry(&priv->recv_queue, struct vs_mbuf, queue); if (mbuf->size > recv_space) { spin_unlock_irq(&priv->recv_wq.lock); ret = -EMSGSIZE; goto fail_msg_size; } list_del_init(&mbuf->queue); spin_unlock_irq(&priv->recv_wq.lock); /* Copy to user. */ ret = -EFAULT; for (i = 0; (mbuf->size > offset) && (i < iovcnt); i++) { size_t len = min(mbuf->size - offset, iov[i].iov_len); if (copy_to_user(iov[i].iov_base, mbuf->data + offset, len)) goto fail_copy; offset += len; } ret = offset; no_mbuf: /* * Read and clear the pending notification bits. If any notifications * are received, don't return an error, even if we failed to receive a * message. */ *notify_bits = atomic_xchg(&priv->notify_pending, 0); if ((ret < 0) && *notify_bits) ret = 0; fail_copy: if (mbuf) vs_service_free_mbuf(service, mbuf); fail_msg_size: vs_devio_priv_put(priv); fail_priv_get: return ret; } static int vs_devio_check_perms(struct file *file, unsigned flags) { if ((flags & MAY_READ) & !(file->f_mode & FMODE_READ)) return -EBADF; if ((flags & MAY_WRITE) & !(file->f_mode & FMODE_WRITE)) return -EBADF; return security_file_permission(file, flags); } static long vs_devio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *ptr = (void __user *)arg; struct vs_service_device *service = file->private_data; struct vs_ioctl_bind bind; struct vs_ioctl_iovec io; u32 flags; long ret; ssize_t iov_total; struct iovec *iov; if (!service) return -ENODEV; switch (cmd) { case IOCTL_VS_RESET_SERVICE: ret = vs_devio_check_perms(file, MAY_WRITE); if (ret < 0) break; ret = vs_service_reset(service, service); break; case IOCTL_VS_GET_NAME: ret = vs_devio_check_perms(file, MAY_READ); if (ret < 0) break; if (service->name != NULL) { size_t len = strnlen(service->name, _IOC_SIZE(IOCTL_VS_GET_NAME) - 1); if (copy_to_user(ptr, service->name, len + 1)) ret = -EFAULT; } else { ret = -EINVAL; } break; case IOCTL_VS_GET_PROTOCOL: ret = vs_devio_check_perms(file, MAY_READ); if (ret < 0) break; if (service->protocol != NULL) { size_t len = strnlen(service->protocol, _IOC_SIZE(IOCTL_VS_GET_PROTOCOL) - 1); if (copy_to_user(ptr, service->protocol, len + 1)) ret = -EFAULT; } else { ret = -EINVAL; } break; case IOCTL_VS_BIND_CLIENT: ret = vs_devio_check_perms(file, MAY_EXEC); if (ret < 0) break; ret = vs_devio_bind_client(service, &bind); if (!ret && copy_to_user(ptr, &bind, sizeof(bind))) ret = -EFAULT; break; case IOCTL_VS_BIND_SERVER: ret = vs_devio_check_perms(file, MAY_EXEC); if (ret < 0) break; if (copy_from_user(&bind, ptr, sizeof(bind))) { ret = -EFAULT; break; } ret = vs_devio_bind_server(service, &bind); if (!ret && copy_to_user(ptr, &bind, sizeof(bind))) ret = -EFAULT; break; case IOCTL_VS_NOTIFY: ret = vs_devio_check_perms(file, MAY_WRITE); if (ret < 0) break; if (copy_from_user(&flags, ptr, sizeof(flags))) { ret = -EFAULT; break; } ret = vs_service_notify(service, flags); break; case IOCTL_VS_SEND: ret = vs_devio_check_perms(file, MAY_WRITE); if (ret < 0) break; if (copy_from_user(&io, ptr, sizeof(io))) { ret = -EFAULT; break; } iov = vs_devio_check_iov(&io, true, &iov_total); if (IS_ERR(iov)) { ret = PTR_ERR(iov); break; } ret = vs_devio_send(service, iov, io.iovcnt, iov_total, file->f_flags & O_NONBLOCK); kfree(iov); break; case IOCTL_VS_RECV: ret = vs_devio_check_perms(file, MAY_READ); if (ret < 0) break; if (copy_from_user(&io, ptr, sizeof(io))) { ret = -EFAULT; break; } iov = vs_devio_check_iov(&io, true, &iov_total); if (IS_ERR(iov)) { ret = PTR_ERR(iov); break; } ret = vs_devio_recv(service, iov, io.iovcnt, &io.notify_bits, iov_total, file->f_flags & O_NONBLOCK); kfree(iov); if (ret >= 0) { u32 __user *notify_bits_ptr = ptr + offsetof( struct vs_ioctl_iovec, notify_bits); if (copy_to_user(notify_bits_ptr, &io.notify_bits, sizeof(io.notify_bits))) ret = -EFAULT; } break; default: dev_dbg(&service->dev, "Unknown ioctl %#x, arg: %lx\n", cmd, arg); ret = -ENOSYS; break; } return ret; } #ifdef CONFIG_COMPAT struct vs_compat_ioctl_bind { __u32 send_quota; __u32 recv_quota; __u32 send_notify_bits; __u32 recv_notify_bits; compat_size_t msg_size; }; #define compat_ioctl_bind_conv(dest, src) ({ \ dest.send_quota = src.send_quota; \ dest.recv_quota = src.recv_quota; \ dest.send_notify_bits = src.send_notify_bits; \ dest.recv_notify_bits = src.recv_notify_bits; \ dest.msg_size = (compat_size_t)src.msg_size; \ }) #define COMPAT_IOCTL_VS_BIND_CLIENT _IOR('4', 3, struct vs_compat_ioctl_bind) #define COMPAT_IOCTL_VS_BIND_SERVER _IOWR('4', 4, struct vs_compat_ioctl_bind) struct vs_compat_ioctl_iovec { union { __u32 iovcnt; /* input */ __u32 notify_bits; /* output (recv only) */ }; compat_uptr_t iov; }; #define COMPAT_IOCTL_VS_SEND \ _IOW('4', 6, struct vs_compat_ioctl_iovec) #define COMPAT_IOCTL_VS_RECV \ _IOWR('4', 7, struct vs_compat_ioctl_iovec) static struct iovec * vs_devio_check_compat_iov(struct vs_compat_ioctl_iovec *c_io, bool is_send, ssize_t *total) { struct iovec *iov; struct compat_iovec *c_iov; unsigned i; int ret; if (c_io->iovcnt > UIO_MAXIOV) return ERR_PTR(-EINVAL); c_iov = kzalloc(sizeof(*c_iov) * c_io->iovcnt, GFP_KERNEL); if (!c_iov) return ERR_PTR(-ENOMEM); iov = kzalloc(sizeof(*iov) * c_io->iovcnt, GFP_KERNEL); if (!iov) { kfree(c_iov); return ERR_PTR(-ENOMEM); } if (copy_from_user(c_iov, (struct compat_iovec __user *) compat_ptr(c_io->iov), sizeof(*c_iov) * c_io->iovcnt)) { ret = -EFAULT; goto fail; } *total = 0; for (i = 0; i < c_io->iovcnt; i++) { ssize_t iov_len; iov[i].iov_base = compat_ptr (c_iov[i].iov_base); iov[i].iov_len = (compat_size_t) c_iov[i].iov_len; iov_len = (ssize_t)iov[i].iov_len; if (iov_len > MAX_RW_COUNT - *total) { ret = -EINVAL; goto fail; } if (!access_ok(is_send ? VERIFY_READ : VERIFY_WRITE, iov[i].iov_base, iov_len)) { ret = -EFAULT; goto fail; } *total += iov_len; } kfree (c_iov); return iov; fail: kfree(c_iov); kfree(iov); return ERR_PTR(ret); } static long vs_devio_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *ptr = (void __user *)arg; struct vs_service_device *service = file->private_data; struct vs_ioctl_bind bind; struct vs_compat_ioctl_bind compat_bind; struct vs_compat_ioctl_iovec compat_io; long ret; ssize_t iov_total; struct iovec *iov; if (!service) return -ENODEV; switch (cmd) { case IOCTL_VS_RESET_SERVICE: case IOCTL_VS_GET_NAME: case IOCTL_VS_GET_PROTOCOL: return vs_devio_ioctl (file, cmd, arg); case COMPAT_IOCTL_VS_SEND: ret = vs_devio_check_perms(file, MAY_WRITE); if (ret < 0) break; if (copy_from_user(&compat_io, ptr, sizeof(compat_io))) { ret = -EFAULT; break; } iov = vs_devio_check_compat_iov(&compat_io, true, &iov_total); if (IS_ERR(iov)) { ret = PTR_ERR(iov); break; } ret = vs_devio_send(service, iov, compat_io.iovcnt, iov_total, file->f_flags & O_NONBLOCK); kfree(iov); break; case COMPAT_IOCTL_VS_RECV: ret = vs_devio_check_perms(file, MAY_READ); if (ret < 0) break; if (copy_from_user(&compat_io, ptr, sizeof(compat_io))) { ret = -EFAULT; break; } iov = vs_devio_check_compat_iov(&compat_io, true, &iov_total); if (IS_ERR(iov)) { ret = PTR_ERR(iov); break; } ret = vs_devio_recv(service, iov, compat_io.iovcnt, &compat_io.notify_bits, iov_total, file->f_flags & O_NONBLOCK); kfree(iov); if (ret >= 0) { u32 __user *notify_bits_ptr = ptr + offsetof( struct vs_compat_ioctl_iovec, notify_bits); if (copy_to_user(notify_bits_ptr, &compat_io.notify_bits, sizeof(compat_io.notify_bits))) ret = -EFAULT; } break; case COMPAT_IOCTL_VS_BIND_CLIENT: ret = vs_devio_check_perms(file, MAY_EXEC); if (ret < 0) break; ret = vs_devio_bind_client(service, &bind); compat_ioctl_bind_conv(compat_bind, bind); if (!ret && copy_to_user(ptr, &compat_bind, sizeof(compat_bind))) ret = -EFAULT; break; case COMPAT_IOCTL_VS_BIND_SERVER: ret = vs_devio_check_perms(file, MAY_EXEC); if (ret < 0) break; if (copy_from_user(&compat_bind, ptr, sizeof(compat_bind))) { ret = -EFAULT; break; } compat_ioctl_bind_conv(bind, compat_bind); ret = vs_devio_bind_server(service, &bind); compat_ioctl_bind_conv(compat_bind, bind); if (!ret && copy_to_user(ptr, &compat_bind, sizeof(compat_bind))) ret = -EFAULT; break; default: dev_dbg(&service->dev, "Unknown ioctl %#x, arg: %lx\n", cmd, arg); ret = -ENOSYS; break; } return ret; } #endif /* CONFIG_COMPAT */ static unsigned int vs_devio_poll(struct file *file, struct poll_table_struct *wait) { struct vs_service_device *service = file->private_data; struct vs_devio_priv *priv = vs_devio_priv_get_from_service(service); unsigned int flags = 0; poll_wait(file, &service->quota_wq, wait); if (priv) { /* * Note: there is no way for us to ensure that all poll * waiters on a given workqueue have gone away, other than to * actually close the file. So, this poll_wait() is only safe * if we never release our claim on the service before the * file is closed. * * We try to guarantee this by only unbinding the devio driver * on close, and setting suppress_bind_attrs in the driver so * root can't unbind us with sysfs. */ poll_wait(file, &priv->recv_wq, wait); if (priv->reset) { /* Service reset; raise poll error. */ flags |= POLLERR | POLLHUP; } else if (priv->running) { if (!list_empty_careful(&priv->recv_queue)) flags |= POLLRDNORM | POLLIN; if (atomic_read(&priv->notify_pending)) flags |= POLLRDNORM | POLLIN; if (vs_service_send_mbufs_available(service) > 0) flags |= POLLWRNORM | POLLOUT; } vs_devio_priv_put(priv); } else { /* No driver attached. Return error flags. */ flags |= POLLERR | POLLHUP; } return flags; } static const struct file_operations vs_fops = { .owner = THIS_MODULE, .open = vs_devio_open, .release = vs_devio_release, .unlocked_ioctl = vs_devio_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = vs_devio_compat_ioctl, #endif .poll = vs_devio_poll, }; int vservices_cdev_major; static struct cdev vs_cdev; int __init vs_devio_init(void) { dev_t dev; int r; r = alloc_chrdev_region(&dev, 0, VSERVICES_DEVICE_MAX, "vs_service"); if (r < 0) goto fail_alloc_chrdev; vservices_cdev_major = MAJOR(dev); cdev_init(&vs_cdev, &vs_fops); r = cdev_add(&vs_cdev, dev, VSERVICES_DEVICE_MAX); if (r < 0) goto fail_cdev_add; return 0; fail_cdev_add: unregister_chrdev_region(dev, VSERVICES_DEVICE_MAX); fail_alloc_chrdev: return r; } void __exit vs_devio_exit(void) { cdev_del(&vs_cdev); unregister_chrdev_region(MKDEV(vservices_cdev_major, 0), VSERVICES_DEVICE_MAX); }