psdev.c
上传用户:jlfgdled
上传日期:2013-04-10
资源大小:33168k
文件大小:22k
- /* -*- 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 %dn",
- 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");
- lh = &channel->uc_processing;
- while ( (lh = lh->next) != &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 filen", __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 filen", __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. */
- lh = &channel->uc_processing;
- while ( (lh = lh->next) != &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 foundn",
- 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 filen", __FUNCTION__);
- return -EBADF;
- }
- spin_lock(&channel->uc_lock);
- if (list_empty(&(channel->uc_pending))) {
- CDEBUG(D_UPCALL, "Empty pending list in read, not goodn");
- 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 bytesn",
- 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 %dn",
- 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: %dn", 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 filen", __FUNCTION__);
- return -EBADF;
- }
- MOD_DEC_USE_COUNT;
- CDEBUG(D_PSDEV, "Lento: pid %dn", 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);
- lh = &channel->uc_pending;
- while ( (lh = lh->next) != &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 processingn");
- lh = &channel->uc_processing;
- while ( (lh = lh->next) != &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 %dn",
- __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;
- spin_lock(&channel->uc_lock);
- if ( ! list_empty(&channel->uc_pending)) {
- CERROR("Weird, tell Peter: module cleanup and pending list not empty dev %dn", i);
- }
- if ( ! list_empty(&channel->uc_processing)) {
- CERROR("Weird, tell Peter: module cleanup and processing list not empty dev %dn", i);
- }
- if ( ! list_empty(&channel->uc_cache_list)) {
- CERROR("Weird, tell Peter: module cleanup and cache listnot empty dev %dn", i);
- }
- lh = channel->uc_pending.next;
- while ( lh != &channel->uc_pending) {
- struct upc_req *req;
- req = list_entry(lh, struct upc_req, rq_chain);
- lh = lh->next;
- if ( req->rq_flags & REQ_ASYNC ) {
- list_del(&(req->rq_chain));
- CDEBUG(D_UPCALL, "free pending upcall type %dn",
- 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);
- }
- }
- lh = &channel->uc_processing;
- while ( (lh = lh->next) != &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: %ldn",
- 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;
- 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: %dn",
- 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 %xn",
- 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 %pn",
- 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 %xn",
- 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 = %xn",
- 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 %dn",
- req->rq_opcode, req->rq_unique, req->rq_flags);
- error = -ENODEV;
- }
- exit_req:
- PRESTO_FREE(req, sizeof(struct upc_req));
- exit_buf:
- return error;
- }