| /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- |
| * vim:expandtab:shiftwidth=8:tabstop=8: |
| * |
| * An implementation of a loadable kernel mode driver providing |
| * multiple kernel/user space bidirectional communications links. |
| * |
| * Author: Alan Cox <alan@cymru.net> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * version 2 as published by the Free Software Foundation. |
| * |
| * Adapted to become the Linux 2.0 Coda pseudo device |
| * Peter Braam <braam@maths.ox.ac.uk> |
| * Michael Callahan <mjc@emmy.smith.edu> |
| * |
| * Changes for Linux 2.1 |
| * Copyright (c) 1997 Carnegie-Mellon University |
| * |
| * Redone again for InterMezzo |
| * Copyright (c) 1998 Peter J. Braam |
| * Copyright (c) 2000 Mountain View Data, Inc. |
| * Copyright (c) 2000 Tacitus Systems, Inc. |
| * Copyright (c) 2001 Cluster File Systems, Inc. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| #include <linux/major.h> |
| #include <linux/sched.h> |
| #include <linux/lp.h> |
| #include <linux/slab.h> |
| #include <linux/ioport.h> |
| #include <linux/fcntl.h> |
| #include <linux/delay.h> |
| #include <linux/skbuff.h> |
| #include <linux/proc_fs.h> |
| #include <linux/vmalloc.h> |
| #include <linux/fs.h> |
| #include <linux/file.h> |
| #include <linux/poll.h> |
| #include <linux/init.h> |
| #include <linux/list.h> |
| #include <linux/devfs_fs_kernel.h> |
| #include <asm/io.h> |
| #include <asm/segment.h> |
| #include <asm/system.h> |
| #include <asm/poll.h> |
| #include <asm/uaccess.h> |
| #include <linux/miscdevice.h> |
| |
| #include <linux/intermezzo_fs.h> |
| #include <linux/intermezzo_psdev.h> |
| |
| |
| #ifdef PRESTO_DEVEL |
| int presto_print_entry = 1; |
| int presto_debug = 4095; |
| #else |
| int presto_print_entry = 0; |
| int presto_debug = 0; |
| #endif |
| |
| /* Like inode.c (presto_sym_iops), the initializer is just to prevent |
| izo_channels from appearing as a COMMON symbol (and therefore |
| interfering with other modules that use the same variable name). */ |
| struct upc_channel izo_channels[MAX_CHANNEL] = {{0}}; |
| |
| int izo_psdev_get_free_channel(void) |
| { |
| int i, result = -1; |
| |
| for (i = 0 ; i < MAX_CHANNEL ; i++ ) { |
| if (list_empty(&(izo_channels[i].uc_cache_list))) { |
| result = i; |
| break; |
| } |
| } |
| return result; |
| } |
| |
| |
| int izo_psdev_setpid(int minor) |
| { |
| struct upc_channel *channel; |
| if (minor < 0 || minor >= MAX_CHANNEL) { |
| return -EINVAL; |
| } |
| |
| channel = &(izo_channels[minor]); |
| /* |
| * This ioctl is performed by each Lento that starts up |
| * and wants to do further communication with presto. |
| */ |
| CDEBUG(D_PSDEV, "Setting current pid to %d channel %d\n", |
| current->pid, minor); |
| channel->uc_pid = current->pid; |
| spin_lock(&channel->uc_lock); |
| if ( !list_empty(&channel->uc_processing) ) { |
| struct list_head *lh; |
| struct upc_req *req; |
| CERROR("WARNING: setpid & processing not empty!\n"); |
| list_for_each(lh, &channel->uc_processing) { |
| req = list_entry(lh, struct upc_req, rq_chain); |
| /* freeing of req and data is done by the sleeper */ |
| wake_up(&req->rq_sleep); |
| } |
| } |
| if ( !list_empty(&channel->uc_processing) ) { |
| CERROR("BAD: FAILDED TO CLEAN PROCESSING LIST!\n"); |
| } |
| spin_unlock(&channel->uc_lock); |
| EXIT; |
| return 0; |
| } |
| |
| int izo_psdev_setchannel(struct file *file, int fd) |
| { |
| |
| struct file *psdev_file = fget(fd); |
| struct presto_cache *cache = presto_get_cache(file->f_dentry->d_inode); |
| |
| if (!psdev_file) { |
| CERROR("%s: no psdev_file!\n", __FUNCTION__); |
| return -EINVAL; |
| } |
| |
| if (!cache) { |
| CERROR("%s: no cache!\n", __FUNCTION__); |
| fput(psdev_file); |
| return -EINVAL; |
| } |
| |
| if (psdev_file->private_data) { |
| CERROR("%s: channel already set!\n", __FUNCTION__); |
| fput(psdev_file); |
| return -EINVAL; |
| } |
| |
| psdev_file->private_data = cache->cache_psdev; |
| fput(psdev_file); |
| EXIT; |
| return 0; |
| } |
| |
| inline int presto_lento_up(int minor) |
| { |
| return izo_channels[minor].uc_pid; |
| } |
| |
| static unsigned int presto_psdev_poll(struct file *file, poll_table * wait) |
| { |
| struct upc_channel *channel = (struct upc_channel *)file->private_data; |
| unsigned int mask = POLLOUT | POLLWRNORM; |
| |
| /* ENTRY; this will flood you */ |
| if ( ! channel ) { |
| CERROR("%s: bad psdev file\n", __FUNCTION__); |
| return -EBADF; |
| } |
| |
| poll_wait(file, &(channel->uc_waitq), wait); |
| |
| spin_lock(&channel->uc_lock); |
| if (!list_empty(&channel->uc_pending)) { |
| CDEBUG(D_PSDEV, "Non-empty pending list.\n"); |
| mask |= POLLIN | POLLRDNORM; |
| } |
| spin_unlock(&channel->uc_lock); |
| |
| /* EXIT; will flood you */ |
| return mask; |
| } |
| |
| /* |
| * Receive a message written by Lento to the psdev |
| */ |
| static ssize_t presto_psdev_write(struct file *file, const char *buf, |
| size_t count, loff_t *off) |
| { |
| struct upc_channel *channel = (struct upc_channel *)file->private_data; |
| struct upc_req *req = NULL; |
| struct upc_req *tmp; |
| struct list_head *lh; |
| struct izo_upcall_resp hdr; |
| int error; |
| |
| if ( ! channel ) { |
| CERROR("%s: bad psdev file\n", __FUNCTION__); |
| return -EBADF; |
| } |
| |
| /* Peek at the opcode, uniquefier */ |
| if ( count < sizeof(hdr) ) { |
| CERROR("presto_psdev_write: Lento didn't write full hdr.\n"); |
| return -EINVAL; |
| } |
| |
| error = copy_from_user(&hdr, buf, sizeof(hdr)); |
| if ( error ) |
| return -EFAULT; |
| |
| CDEBUG(D_PSDEV, "(process,opc,uniq)=(%d,%d,%d)\n", |
| current->pid, hdr.opcode, hdr.unique); |
| |
| spin_lock(&channel->uc_lock); |
| /* Look for the message on the processing queue. */ |
| list_for_each(lh, &channel->uc_processing) { |
| tmp = list_entry(lh, struct upc_req , rq_chain); |
| if (tmp->rq_unique == hdr.unique) { |
| req = tmp; |
| /* unlink here: keeps search length minimal */ |
| list_del_init(&req->rq_chain); |
| CDEBUG(D_PSDEV,"Eureka opc %d uniq %d!\n", |
| hdr.opcode, hdr.unique); |
| break; |
| } |
| } |
| spin_unlock(&channel->uc_lock); |
| if (!req) { |
| CERROR("psdev_write: msg (%d, %d) not found\n", |
| hdr.opcode, hdr.unique); |
| return(-ESRCH); |
| } |
| |
| /* move data into response buffer. */ |
| if (req->rq_bufsize < count) { |
| CERROR("psdev_write: too much cnt: %d, cnt: %d, " |
| "opc: %d, uniq: %d.\n", |
| req->rq_bufsize, count, hdr.opcode, hdr.unique); |
| count = req->rq_bufsize; /* don't have more space! */ |
| } |
| error = copy_from_user(req->rq_data, buf, count); |
| if ( error ) |
| return -EFAULT; |
| |
| /* adjust outsize: good upcalls can be aware of this */ |
| req->rq_rep_size = count; |
| req->rq_flags |= REQ_WRITE; |
| |
| wake_up(&req->rq_sleep); |
| return(count); |
| } |
| |
| /* |
| * Read a message from the kernel to Lento |
| */ |
| static ssize_t presto_psdev_read(struct file * file, char * buf, |
| size_t count, loff_t *off) |
| { |
| struct upc_channel *channel = (struct upc_channel *)file->private_data; |
| struct upc_req *req; |
| int result = count; |
| |
| if ( ! channel ) { |
| CERROR("%s: bad psdev file\n", __FUNCTION__); |
| return -EBADF; |
| } |
| |
| spin_lock(&channel->uc_lock); |
| if (list_empty(&(channel->uc_pending))) { |
| CDEBUG(D_UPCALL, "Empty pending list in read, not good\n"); |
| spin_unlock(&channel->uc_lock); |
| return -EINVAL; |
| } |
| req = list_entry((channel->uc_pending.next), struct upc_req, rq_chain); |
| list_del(&(req->rq_chain)); |
| if (! (req->rq_flags & REQ_ASYNC) ) { |
| list_add(&(req->rq_chain), channel->uc_processing.prev); |
| } |
| spin_unlock(&channel->uc_lock); |
| |
| req->rq_flags |= REQ_READ; |
| |
| /* Move the input args into userspace */ |
| CDEBUG(D_PSDEV, "\n"); |
| if (req->rq_bufsize <= count) { |
| result = req->rq_bufsize; |
| } |
| |
| if (count < req->rq_bufsize) { |
| CERROR ("psdev_read: buffer too small, read %d of %d bytes\n", |
| count, req->rq_bufsize); |
| } |
| |
| if ( copy_to_user(buf, req->rq_data, result) ) { |
| BUG(); |
| return -EFAULT; |
| } |
| |
| /* If request was asynchronous don't enqueue, but free */ |
| if (req->rq_flags & REQ_ASYNC) { |
| CDEBUG(D_PSDEV, "psdev_read: async msg (%d, %d), result %d\n", |
| req->rq_opcode, req->rq_unique, result); |
| PRESTO_FREE(req->rq_data, req->rq_bufsize); |
| PRESTO_FREE(req, sizeof(*req)); |
| return result; |
| } |
| |
| return result; |
| } |
| |
| |
| static int presto_psdev_open(struct inode * inode, struct file * file) |
| { |
| ENTRY; |
| |
| file->private_data = NULL; |
| |
| MOD_INC_USE_COUNT; |
| |
| CDEBUG(D_PSDEV, "Psdev_open: caller: %d, flags: %d\n", current->pid, file->f_flags); |
| |
| EXIT; |
| return 0; |
| } |
| |
| |
| |
| static int presto_psdev_release(struct inode * inode, struct file * file) |
| { |
| struct upc_channel *channel = (struct upc_channel *)file->private_data; |
| struct upc_req *req; |
| struct list_head *lh; |
| ENTRY; |
| |
| if ( ! channel ) { |
| CERROR("%s: bad psdev file\n", __FUNCTION__); |
| return -EBADF; |
| } |
| |
| MOD_DEC_USE_COUNT; |
| CDEBUG(D_PSDEV, "Lento: pid %d\n", current->pid); |
| channel->uc_pid = 0; |
| |
| /* Wake up clients so they can return. */ |
| CDEBUG(D_PSDEV, "Wake up clients sleeping for pending.\n"); |
| spin_lock(&channel->uc_lock); |
| list_for_each(lh, &channel->uc_pending) { |
| req = list_entry(lh, struct upc_req, rq_chain); |
| |
| /* Async requests stay around for a new lento */ |
| if (req->rq_flags & REQ_ASYNC) { |
| continue; |
| } |
| /* the sleeper will free the req and data */ |
| req->rq_flags |= REQ_DEAD; |
| wake_up(&req->rq_sleep); |
| } |
| |
| CDEBUG(D_PSDEV, "Wake up clients sleeping for processing\n"); |
| list_for_each(lh, &channel->uc_processing) { |
| req = list_entry(lh, struct upc_req, rq_chain); |
| /* freeing of req and data is done by the sleeper */ |
| req->rq_flags |= REQ_DEAD; |
| wake_up(&req->rq_sleep); |
| } |
| spin_unlock(&channel->uc_lock); |
| CDEBUG(D_PSDEV, "Done.\n"); |
| |
| EXIT; |
| return 0; |
| } |
| |
| static struct file_operations presto_psdev_fops = { |
| .read = presto_psdev_read, |
| .write = presto_psdev_write, |
| .poll = presto_psdev_poll, |
| .open = presto_psdev_open, |
| .release = presto_psdev_release |
| }; |
| |
| /* modules setup */ |
| static struct miscdevice intermezzo_psdev = { |
| INTERMEZZO_MINOR, |
| "intermezzo", |
| &presto_psdev_fops |
| }; |
| |
| int presto_psdev_init(void) |
| { |
| int i; |
| int err; |
| |
| if ( (err = misc_register(&intermezzo_psdev)) ) { |
| CERROR("%s: cannot register %d err %d\n", |
| __FUNCTION__, INTERMEZZO_MINOR, err); |
| return -EIO; |
| } |
| |
| memset(&izo_channels, 0, sizeof(izo_channels)); |
| for ( i = 0 ; i < MAX_CHANNEL ; i++ ) { |
| struct upc_channel *channel = &(izo_channels[i]); |
| INIT_LIST_HEAD(&channel->uc_pending); |
| INIT_LIST_HEAD(&channel->uc_processing); |
| INIT_LIST_HEAD(&channel->uc_cache_list); |
| init_waitqueue_head(&channel->uc_waitq); |
| channel->uc_lock = SPIN_LOCK_UNLOCKED; |
| channel->uc_hard = 0; |
| channel->uc_no_filter = 0; |
| channel->uc_no_journal = 0; |
| channel->uc_no_upcall = 0; |
| channel->uc_timeout = 30; |
| channel->uc_errorval = 0; |
| channel->uc_minor = i; |
| } |
| return 0; |
| } |
| |
| void presto_psdev_cleanup(void) |
| { |
| int i; |
| |
| misc_deregister(&intermezzo_psdev); |
| |
| for ( i = 0 ; i < MAX_CHANNEL ; i++ ) { |
| struct upc_channel *channel = &(izo_channels[i]); |
| struct list_head *lh, *next; |
| |
| spin_lock(&channel->uc_lock); |
| if ( ! list_empty(&channel->uc_pending)) { |
| CERROR("Weird, tell Peter: module cleanup and pending list not empty dev %d\n", i); |
| } |
| if ( ! list_empty(&channel->uc_processing)) { |
| CERROR("Weird, tell Peter: module cleanup and processing list not empty dev %d\n", i); |
| } |
| if ( ! list_empty(&channel->uc_cache_list)) { |
| CERROR("Weird, tell Peter: module cleanup and cache listnot empty dev %d\n", i); |
| } |
| list_for_each_safe(lh, next, &channel->uc_pending) { |
| struct upc_req *req; |
| |
| req = list_entry(lh, struct upc_req, rq_chain); |
| if ( req->rq_flags & REQ_ASYNC ) { |
| list_del(&(req->rq_chain)); |
| CDEBUG(D_UPCALL, "free pending upcall type %d\n", |
| req->rq_opcode); |
| PRESTO_FREE(req->rq_data, req->rq_bufsize); |
| PRESTO_FREE(req, sizeof(struct upc_req)); |
| } else { |
| req->rq_flags |= REQ_DEAD; |
| wake_up(&req->rq_sleep); |
| } |
| } |
| list_for_each(lh, &channel->uc_processing) { |
| struct upc_req *req; |
| req = list_entry(lh, struct upc_req, rq_chain); |
| list_del(&(req->rq_chain)); |
| req->rq_flags |= REQ_DEAD; |
| wake_up(&req->rq_sleep); |
| } |
| spin_unlock(&channel->uc_lock); |
| } |
| } |
| |
| /* |
| * lento_upcall and lento_downcall routines |
| */ |
| static inline unsigned long lento_waitfor_upcall |
| (struct upc_channel *channel, struct upc_req *req, int minor) |
| { |
| DECLARE_WAITQUEUE(wait, current); |
| unsigned long posttime; |
| |
| req->rq_posttime = posttime = jiffies; |
| |
| add_wait_queue(&req->rq_sleep, &wait); |
| for (;;) { |
| if ( izo_channels[minor].uc_hard == 0 ) |
| set_current_state(TASK_INTERRUPTIBLE); |
| else |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| |
| /* got a reply */ |
| if ( req->rq_flags & (REQ_WRITE | REQ_DEAD) ) |
| break; |
| |
| /* these cases only apply when TASK_INTERRUPTIBLE */ |
| if ( !izo_channels[minor].uc_hard && signal_pending(current) ) { |
| /* if this process really wants to die, let it go */ |
| if (sigismember(&(current->pending.signal), SIGKILL)|| |
| sigismember(&(current->pending.signal), SIGINT) ) |
| break; |
| /* signal is present: after timeout always return |
| really smart idea, probably useless ... */ |
| if ( time_after(jiffies, req->rq_posttime + |
| izo_channels[minor].uc_timeout * HZ) ) |
| break; |
| } |
| schedule(); |
| } |
| |
| spin_lock(&channel->uc_lock); |
| list_del_init(&req->rq_chain); |
| spin_unlock(&channel->uc_lock); |
| remove_wait_queue(&req->rq_sleep, &wait); |
| set_current_state(TASK_RUNNING); |
| |
| CDEBUG(D_SPECIAL, "posttime: %ld, returned: %ld\n", |
| posttime, jiffies-posttime); |
| return (jiffies - posttime); |
| } |
| |
| /* |
| * lento_upcall will return an error in the case of |
| * failed communication with Lento _or_ will peek at Lento |
| * reply and return Lento's error. |
| * |
| * As lento has 2 types of errors, normal errors (positive) and internal |
| * errors (negative), normal errors are negated, while internal errors |
| * are all mapped to -EINTR, while showing a nice warning message. (jh) |
| * |
| * lento_upcall will always free buffer, either directly, when an upcall |
| * is read (in presto_psdev_read), when the filesystem is unmounted, or |
| * when the module is unloaded. |
| */ |
| int izo_upc_upcall(int minor, int *size, struct izo_upcall_hdr *buffer, |
| int async) |
| { |
| unsigned long runtime; |
| struct upc_channel *channel; |
| struct izo_upcall_resp *out; |
| struct upc_req *req; |
| int error = 0; |
| |
| ENTRY; |
| channel = &(izo_channels[minor]); |
| |
| if (channel->uc_no_upcall) { |
| EXIT; |
| goto exit_buf; |
| } |
| if (!channel->uc_pid && !async) { |
| EXIT; |
| error = -ENXIO; |
| goto exit_buf; |
| } |
| |
| /* Format the request message. */ |
| PRESTO_ALLOC(req, sizeof(struct upc_req)); |
| if ( !req ) { |
| EXIT; |
| error = -ENOMEM; |
| goto exit_buf; |
| } |
| req->rq_data = (void *)buffer; |
| req->rq_flags = 0; |
| req->rq_bufsize = *size; |
| req->rq_rep_size = 0; |
| req->rq_opcode = buffer->u_opc; |
| req->rq_unique = ++channel->uc_seq; |
| init_waitqueue_head(&req->rq_sleep); |
| |
| /* Fill in the common input args. */ |
| buffer->u_uniq = req->rq_unique; |
| buffer->u_async = async; |
| |
| /* Remove potential datarace possibility*/ |
| if ( async ) |
| req->rq_flags = REQ_ASYNC; |
| |
| spin_lock(&channel->uc_lock); |
| /* Append msg to pending queue and poke Lento. */ |
| list_add(&req->rq_chain, channel->uc_pending.prev); |
| spin_unlock(&channel->uc_lock); |
| CDEBUG(D_UPCALL, |
| "Proc %d waking Lento %d for(opc,uniq) =(%d,%d) msg at %p.\n", |
| current->pid, channel->uc_pid, req->rq_opcode, |
| req->rq_unique, req); |
| wake_up_interruptible(&channel->uc_waitq); |
| |
| if ( async ) { |
| /* req, rq_data are freed in presto_psdev_read for async */ |
| /* req->rq_flags = REQ_ASYNC;*/ |
| EXIT; |
| return 0; |
| } |
| |
| /* We can be interrupted while we wait for Lento to process |
| * our request. If the interrupt occurs before Lento has read |
| * the request, we dequeue and return. If it occurs after the |
| * read but before the reply, we dequeue, send a signal |
| * message, and return. If it occurs after the reply we ignore |
| * it. In no case do we want to restart the syscall. If it |
| * was interrupted by a lento shutdown (psdev_close), return |
| * ENODEV. */ |
| |
| /* Go to sleep. Wake up on signals only after the timeout. */ |
| runtime = lento_waitfor_upcall(channel, req, minor); |
| |
| CDEBUG(D_TIMING, "opc: %d time: %ld uniq: %d size: %d\n", |
| req->rq_opcode, jiffies - req->rq_posttime, |
| req->rq_unique, req->rq_rep_size); |
| CDEBUG(D_UPCALL, |
| "..process %d woken up by Lento for req at 0x%x, data at %x\n", |
| current->pid, (int)req, (int)req->rq_data); |
| |
| if (channel->uc_pid) { /* i.e. Lento is still alive */ |
| /* Op went through, interrupt or not we go on */ |
| if (req->rq_flags & REQ_WRITE) { |
| out = (struct izo_upcall_resp *)req->rq_data; |
| /* here we map positive Lento errors to kernel errors */ |
| if ( out->result < 0 ) { |
| CERROR("Tell Peter: Lento returns negative error %d, for oc %d!\n", |
| out->result, out->opcode); |
| out->result = EINVAL; |
| } |
| error = -out->result; |
| CDEBUG(D_UPCALL, "upcall: (u,o,r) (%d, %d, %d) out at %p\n", |
| out->unique, out->opcode, out->result, out); |
| *size = req->rq_rep_size; |
| EXIT; |
| goto exit_req; |
| } |
| /* Interrupted before lento read it. */ |
| if ( !(req->rq_flags & REQ_READ) && signal_pending(current)) { |
| CDEBUG(D_UPCALL, |
| "Interrupt before read: (op,un)=(%d,%d), flags %x\n", |
| req->rq_opcode, req->rq_unique, req->rq_flags); |
| /* perhaps the best way to convince the app to give up? */ |
| error = -EINTR; |
| EXIT; |
| goto exit_req; |
| } |
| |
| /* interrupted after Lento did its read, send signal */ |
| if ( (req->rq_flags & REQ_READ) && signal_pending(current) ) { |
| CDEBUG(D_UPCALL,"Interrupt after read: op = %d.%d, flags = %x\n", |
| req->rq_opcode, req->rq_unique, req->rq_flags); |
| |
| error = -EINTR; |
| } else { |
| CERROR("Lento: Strange interruption - tell Peter.\n"); |
| error = -EINTR; |
| } |
| } else { /* If lento died i.e. !UC_OPEN(channel) */ |
| CERROR("lento_upcall: Lento dead on (op,un) (%d.%d) flags %d\n", |
| req->rq_opcode, req->rq_unique, req->rq_flags); |
| error = -ENODEV; |
| } |
| |
| exit_req: |
| PRESTO_FREE(req, sizeof(struct upc_req)); |
| exit_buf: |
| PRESTO_FREE(buffer,*size); |
| return error; |
| } |