forked from Minki/linux
42e8c509cf
Update license boilerplate to specify GPLv2 and remove the (at your option clause). This change was agreed to by all the copyright holders (approvals can be found on v9fs-developer mailing list). Signed-off-by: Eric Van Hensbergen <ericvh@gmail.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
994 lines
22 KiB
C
994 lines
22 KiB
C
/*
|
|
* linux/fs/9p/mux.c
|
|
*
|
|
* Protocol Multiplexer
|
|
*
|
|
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
|
|
* Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to:
|
|
* Free Software Foundation
|
|
* 51 Franklin Street, Fifth Floor
|
|
* Boston, MA 02111-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/mutex.h>
|
|
|
|
#include "debug.h"
|
|
#include "v9fs.h"
|
|
#include "9p.h"
|
|
#include "conv.h"
|
|
#include "transport.h"
|
|
#include "mux.h"
|
|
|
|
#define ERREQFLUSH 1
|
|
#define SCHED_TIMEOUT 10
|
|
#define MAXPOLLWADDR 2
|
|
|
|
enum {
|
|
Rworksched = 1, /* read work scheduled or running */
|
|
Rpending = 2, /* can read */
|
|
Wworksched = 4, /* write work scheduled or running */
|
|
Wpending = 8, /* can write */
|
|
};
|
|
|
|
struct v9fs_mux_poll_task;
|
|
|
|
struct v9fs_req {
|
|
int tag;
|
|
struct v9fs_fcall *tcall;
|
|
struct v9fs_fcall *rcall;
|
|
int err;
|
|
v9fs_mux_req_callback cb;
|
|
void *cba;
|
|
struct list_head req_list;
|
|
};
|
|
|
|
struct v9fs_mux_data {
|
|
spinlock_t lock;
|
|
struct list_head mux_list;
|
|
struct v9fs_mux_poll_task *poll_task;
|
|
int msize;
|
|
unsigned char *extended;
|
|
struct v9fs_transport *trans;
|
|
struct v9fs_idpool tagpool;
|
|
int err;
|
|
wait_queue_head_t equeue;
|
|
struct list_head req_list;
|
|
struct list_head unsent_req_list;
|
|
struct v9fs_fcall *rcall;
|
|
int rpos;
|
|
char *rbuf;
|
|
int wpos;
|
|
int wsize;
|
|
char *wbuf;
|
|
wait_queue_t poll_wait[MAXPOLLWADDR];
|
|
wait_queue_head_t *poll_waddr[MAXPOLLWADDR];
|
|
poll_table pt;
|
|
struct work_struct rq;
|
|
struct work_struct wq;
|
|
unsigned long wsched;
|
|
};
|
|
|
|
struct v9fs_mux_poll_task {
|
|
struct task_struct *task;
|
|
struct list_head mux_list;
|
|
int muxnum;
|
|
};
|
|
|
|
struct v9fs_mux_rpc {
|
|
struct v9fs_mux_data *m;
|
|
struct v9fs_req *req;
|
|
int err;
|
|
struct v9fs_fcall *rcall;
|
|
wait_queue_head_t wqueue;
|
|
};
|
|
|
|
static int v9fs_poll_proc(void *);
|
|
static void v9fs_read_work(void *);
|
|
static void v9fs_write_work(void *);
|
|
static void v9fs_pollwait(struct file *filp, wait_queue_head_t * wait_address,
|
|
poll_table * p);
|
|
static u16 v9fs_mux_get_tag(struct v9fs_mux_data *);
|
|
static void v9fs_mux_put_tag(struct v9fs_mux_data *, u16);
|
|
|
|
static DEFINE_MUTEX(v9fs_mux_task_lock);
|
|
static struct workqueue_struct *v9fs_mux_wq;
|
|
|
|
static int v9fs_mux_num;
|
|
static int v9fs_mux_poll_task_num;
|
|
static struct v9fs_mux_poll_task v9fs_mux_poll_tasks[100];
|
|
|
|
int v9fs_mux_global_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(v9fs_mux_poll_tasks); i++)
|
|
v9fs_mux_poll_tasks[i].task = NULL;
|
|
|
|
v9fs_mux_wq = create_workqueue("v9fs");
|
|
if (!v9fs_mux_wq)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void v9fs_mux_global_exit(void)
|
|
{
|
|
destroy_workqueue(v9fs_mux_wq);
|
|
}
|
|
|
|
/**
|
|
* v9fs_mux_calc_poll_procs - calculates the number of polling procs
|
|
* based on the number of mounted v9fs filesystems.
|
|
*
|
|
* The current implementation returns sqrt of the number of mounts.
|
|
*/
|
|
static int v9fs_mux_calc_poll_procs(int muxnum)
|
|
{
|
|
int n;
|
|
|
|
if (v9fs_mux_poll_task_num)
|
|
n = muxnum / v9fs_mux_poll_task_num +
|
|
(muxnum % v9fs_mux_poll_task_num ? 1 : 0);
|
|
else
|
|
n = 1;
|
|
|
|
if (n > ARRAY_SIZE(v9fs_mux_poll_tasks))
|
|
n = ARRAY_SIZE(v9fs_mux_poll_tasks);
|
|
|
|
return n;
|
|
}
|
|
|
|
static int v9fs_mux_poll_start(struct v9fs_mux_data *m)
|
|
{
|
|
int i, n;
|
|
struct v9fs_mux_poll_task *vpt, *vptlast;
|
|
struct task_struct *pproc;
|
|
|
|
dprintk(DEBUG_MUX, "mux %p muxnum %d procnum %d\n", m, v9fs_mux_num,
|
|
v9fs_mux_poll_task_num);
|
|
mutex_lock(&v9fs_mux_task_lock);
|
|
|
|
n = v9fs_mux_calc_poll_procs(v9fs_mux_num + 1);
|
|
if (n > v9fs_mux_poll_task_num) {
|
|
for (i = 0; i < ARRAY_SIZE(v9fs_mux_poll_tasks); i++) {
|
|
if (v9fs_mux_poll_tasks[i].task == NULL) {
|
|
vpt = &v9fs_mux_poll_tasks[i];
|
|
dprintk(DEBUG_MUX, "create proc %p\n", vpt);
|
|
pproc = kthread_create(v9fs_poll_proc, vpt,
|
|
"v9fs-poll");
|
|
|
|
if (!IS_ERR(pproc)) {
|
|
vpt->task = pproc;
|
|
INIT_LIST_HEAD(&vpt->mux_list);
|
|
vpt->muxnum = 0;
|
|
v9fs_mux_poll_task_num++;
|
|
wake_up_process(vpt->task);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= ARRAY_SIZE(v9fs_mux_poll_tasks))
|
|
dprintk(DEBUG_ERROR, "warning: no free poll slots\n");
|
|
}
|
|
|
|
n = (v9fs_mux_num + 1) / v9fs_mux_poll_task_num +
|
|
((v9fs_mux_num + 1) % v9fs_mux_poll_task_num ? 1 : 0);
|
|
|
|
vptlast = NULL;
|
|
for (i = 0; i < ARRAY_SIZE(v9fs_mux_poll_tasks); i++) {
|
|
vpt = &v9fs_mux_poll_tasks[i];
|
|
if (vpt->task != NULL) {
|
|
vptlast = vpt;
|
|
if (vpt->muxnum < n) {
|
|
dprintk(DEBUG_MUX, "put in proc %d\n", i);
|
|
list_add(&m->mux_list, &vpt->mux_list);
|
|
vpt->muxnum++;
|
|
m->poll_task = vpt;
|
|
memset(&m->poll_waddr, 0, sizeof(m->poll_waddr));
|
|
init_poll_funcptr(&m->pt, v9fs_pollwait);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i >= ARRAY_SIZE(v9fs_mux_poll_tasks)) {
|
|
if (vptlast == NULL)
|
|
return -ENOMEM;
|
|
|
|
dprintk(DEBUG_MUX, "put in proc %d\n", i);
|
|
list_add(&m->mux_list, &vptlast->mux_list);
|
|
vptlast->muxnum++;
|
|
m->poll_task = vptlast;
|
|
memset(&m->poll_waddr, 0, sizeof(m->poll_waddr));
|
|
init_poll_funcptr(&m->pt, v9fs_pollwait);
|
|
}
|
|
|
|
v9fs_mux_num++;
|
|
mutex_unlock(&v9fs_mux_task_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void v9fs_mux_poll_stop(struct v9fs_mux_data *m)
|
|
{
|
|
int i;
|
|
struct v9fs_mux_poll_task *vpt;
|
|
|
|
mutex_lock(&v9fs_mux_task_lock);
|
|
vpt = m->poll_task;
|
|
list_del(&m->mux_list);
|
|
for(i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) {
|
|
if (m->poll_waddr[i] != NULL) {
|
|
remove_wait_queue(m->poll_waddr[i], &m->poll_wait[i]);
|
|
m->poll_waddr[i] = NULL;
|
|
}
|
|
}
|
|
vpt->muxnum--;
|
|
if (!vpt->muxnum) {
|
|
dprintk(DEBUG_MUX, "destroy proc %p\n", vpt);
|
|
send_sig(SIGKILL, vpt->task, 1);
|
|
vpt->task = NULL;
|
|
v9fs_mux_poll_task_num--;
|
|
}
|
|
v9fs_mux_num--;
|
|
mutex_unlock(&v9fs_mux_task_lock);
|
|
}
|
|
|
|
/**
|
|
* v9fs_mux_init - allocate and initialize the per-session mux data
|
|
* Creates the polling task if this is the first session.
|
|
*
|
|
* @trans - transport structure
|
|
* @msize - maximum message size
|
|
* @extended - pointer to the extended flag
|
|
*/
|
|
struct v9fs_mux_data *v9fs_mux_init(struct v9fs_transport *trans, int msize,
|
|
unsigned char *extended)
|
|
{
|
|
int i, n;
|
|
struct v9fs_mux_data *m, *mtmp;
|
|
|
|
dprintk(DEBUG_MUX, "transport %p msize %d\n", trans, msize);
|
|
m = kmalloc(sizeof(struct v9fs_mux_data), GFP_KERNEL);
|
|
if (!m)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
spin_lock_init(&m->lock);
|
|
INIT_LIST_HEAD(&m->mux_list);
|
|
m->msize = msize;
|
|
m->extended = extended;
|
|
m->trans = trans;
|
|
idr_init(&m->tagpool.pool);
|
|
init_MUTEX(&m->tagpool.lock);
|
|
m->err = 0;
|
|
init_waitqueue_head(&m->equeue);
|
|
INIT_LIST_HEAD(&m->req_list);
|
|
INIT_LIST_HEAD(&m->unsent_req_list);
|
|
m->rcall = NULL;
|
|
m->rpos = 0;
|
|
m->rbuf = NULL;
|
|
m->wpos = m->wsize = 0;
|
|
m->wbuf = NULL;
|
|
INIT_WORK(&m->rq, v9fs_read_work, m);
|
|
INIT_WORK(&m->wq, v9fs_write_work, m);
|
|
m->wsched = 0;
|
|
memset(&m->poll_waddr, 0, sizeof(m->poll_waddr));
|
|
m->poll_task = NULL;
|
|
n = v9fs_mux_poll_start(m);
|
|
if (n)
|
|
return ERR_PTR(n);
|
|
|
|
n = trans->poll(trans, &m->pt);
|
|
if (n & POLLIN) {
|
|
dprintk(DEBUG_MUX, "mux %p can read\n", m);
|
|
set_bit(Rpending, &m->wsched);
|
|
}
|
|
|
|
if (n & POLLOUT) {
|
|
dprintk(DEBUG_MUX, "mux %p can write\n", m);
|
|
set_bit(Wpending, &m->wsched);
|
|
}
|
|
|
|
for(i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) {
|
|
if (IS_ERR(m->poll_waddr[i])) {
|
|
v9fs_mux_poll_stop(m);
|
|
mtmp = (void *)m->poll_waddr; /* the error code */
|
|
kfree(m);
|
|
m = mtmp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/**
|
|
* v9fs_mux_destroy - cancels all pending requests and frees mux resources
|
|
*/
|
|
void v9fs_mux_destroy(struct v9fs_mux_data *m)
|
|
{
|
|
dprintk(DEBUG_MUX, "mux %p prev %p next %p\n", m,
|
|
m->mux_list.prev, m->mux_list.next);
|
|
v9fs_mux_cancel(m, -ECONNRESET);
|
|
|
|
if (!list_empty(&m->req_list)) {
|
|
/* wait until all processes waiting on this session exit */
|
|
dprintk(DEBUG_MUX, "mux %p waiting for empty request queue\n",
|
|
m);
|
|
wait_event_timeout(m->equeue, (list_empty(&m->req_list)), 5000);
|
|
dprintk(DEBUG_MUX, "mux %p request queue empty: %d\n", m,
|
|
list_empty(&m->req_list));
|
|
}
|
|
|
|
v9fs_mux_poll_stop(m);
|
|
m->trans = NULL;
|
|
|
|
kfree(m);
|
|
}
|
|
|
|
/**
|
|
* v9fs_pollwait - called by files poll operation to add v9fs-poll task
|
|
* to files wait queue
|
|
*/
|
|
static void
|
|
v9fs_pollwait(struct file *filp, wait_queue_head_t * wait_address,
|
|
poll_table * p)
|
|
{
|
|
int i;
|
|
struct v9fs_mux_data *m;
|
|
|
|
m = container_of(p, struct v9fs_mux_data, pt);
|
|
for(i = 0; i < ARRAY_SIZE(m->poll_waddr); i++)
|
|
if (m->poll_waddr[i] == NULL)
|
|
break;
|
|
|
|
if (i >= ARRAY_SIZE(m->poll_waddr)) {
|
|
dprintk(DEBUG_ERROR, "not enough wait_address slots\n");
|
|
return;
|
|
}
|
|
|
|
m->poll_waddr[i] = wait_address;
|
|
|
|
if (!wait_address) {
|
|
dprintk(DEBUG_ERROR, "no wait_address\n");
|
|
m->poll_waddr[i] = ERR_PTR(-EIO);
|
|
return;
|
|
}
|
|
|
|
init_waitqueue_entry(&m->poll_wait[i], m->poll_task->task);
|
|
add_wait_queue(wait_address, &m->poll_wait[i]);
|
|
}
|
|
|
|
/**
|
|
* v9fs_poll_mux - polls a mux and schedules read or write works if necessary
|
|
*/
|
|
static void v9fs_poll_mux(struct v9fs_mux_data *m)
|
|
{
|
|
int n;
|
|
|
|
if (m->err < 0)
|
|
return;
|
|
|
|
n = m->trans->poll(m->trans, NULL);
|
|
if (n < 0 || n & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
dprintk(DEBUG_MUX, "error mux %p err %d\n", m, n);
|
|
if (n >= 0)
|
|
n = -ECONNRESET;
|
|
v9fs_mux_cancel(m, n);
|
|
}
|
|
|
|
if (n & POLLIN) {
|
|
set_bit(Rpending, &m->wsched);
|
|
dprintk(DEBUG_MUX, "mux %p can read\n", m);
|
|
if (!test_and_set_bit(Rworksched, &m->wsched)) {
|
|
dprintk(DEBUG_MUX, "schedule read work mux %p\n", m);
|
|
queue_work(v9fs_mux_wq, &m->rq);
|
|
}
|
|
}
|
|
|
|
if (n & POLLOUT) {
|
|
set_bit(Wpending, &m->wsched);
|
|
dprintk(DEBUG_MUX, "mux %p can write\n", m);
|
|
if ((m->wsize || !list_empty(&m->unsent_req_list))
|
|
&& !test_and_set_bit(Wworksched, &m->wsched)) {
|
|
dprintk(DEBUG_MUX, "schedule write work mux %p\n", m);
|
|
queue_work(v9fs_mux_wq, &m->wq);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* v9fs_poll_proc - polls all v9fs transports for new events and queues
|
|
* the appropriate work to the work queue
|
|
*/
|
|
static int v9fs_poll_proc(void *a)
|
|
{
|
|
struct v9fs_mux_data *m, *mtmp;
|
|
struct v9fs_mux_poll_task *vpt;
|
|
|
|
vpt = a;
|
|
dprintk(DEBUG_MUX, "start %p %p\n", current, vpt);
|
|
allow_signal(SIGKILL);
|
|
while (!kthread_should_stop()) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (signal_pending(current))
|
|
break;
|
|
|
|
list_for_each_entry_safe(m, mtmp, &vpt->mux_list, mux_list) {
|
|
v9fs_poll_mux(m);
|
|
}
|
|
|
|
dprintk(DEBUG_MUX, "sleeping...\n");
|
|
schedule_timeout(SCHED_TIMEOUT * HZ);
|
|
}
|
|
|
|
__set_current_state(TASK_RUNNING);
|
|
dprintk(DEBUG_MUX, "finish\n");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* v9fs_write_work - called when a transport can send some data
|
|
*/
|
|
static void v9fs_write_work(void *a)
|
|
{
|
|
int n, err;
|
|
struct v9fs_mux_data *m;
|
|
struct v9fs_req *req;
|
|
|
|
m = a;
|
|
|
|
if (m->err < 0) {
|
|
clear_bit(Wworksched, &m->wsched);
|
|
return;
|
|
}
|
|
|
|
if (!m->wsize) {
|
|
if (list_empty(&m->unsent_req_list)) {
|
|
clear_bit(Wworksched, &m->wsched);
|
|
return;
|
|
}
|
|
|
|
spin_lock(&m->lock);
|
|
again:
|
|
req = list_entry(m->unsent_req_list.next, struct v9fs_req,
|
|
req_list);
|
|
list_move_tail(&req->req_list, &m->req_list);
|
|
if (req->err == ERREQFLUSH)
|
|
goto again;
|
|
|
|
m->wbuf = req->tcall->sdata;
|
|
m->wsize = req->tcall->size;
|
|
m->wpos = 0;
|
|
dump_data(m->wbuf, m->wsize);
|
|
spin_unlock(&m->lock);
|
|
}
|
|
|
|
dprintk(DEBUG_MUX, "mux %p pos %d size %d\n", m, m->wpos, m->wsize);
|
|
clear_bit(Wpending, &m->wsched);
|
|
err = m->trans->write(m->trans, m->wbuf + m->wpos, m->wsize - m->wpos);
|
|
dprintk(DEBUG_MUX, "mux %p sent %d bytes\n", m, err);
|
|
if (err == -EAGAIN) {
|
|
clear_bit(Wworksched, &m->wsched);
|
|
return;
|
|
}
|
|
|
|
if (err <= 0)
|
|
goto error;
|
|
|
|
m->wpos += err;
|
|
if (m->wpos == m->wsize)
|
|
m->wpos = m->wsize = 0;
|
|
|
|
if (m->wsize == 0 && !list_empty(&m->unsent_req_list)) {
|
|
if (test_and_clear_bit(Wpending, &m->wsched))
|
|
n = POLLOUT;
|
|
else
|
|
n = m->trans->poll(m->trans, NULL);
|
|
|
|
if (n & POLLOUT) {
|
|
dprintk(DEBUG_MUX, "schedule write work mux %p\n", m);
|
|
queue_work(v9fs_mux_wq, &m->wq);
|
|
} else
|
|
clear_bit(Wworksched, &m->wsched);
|
|
} else
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
return;
|
|
|
|
error:
|
|
v9fs_mux_cancel(m, err);
|
|
clear_bit(Wworksched, &m->wsched);
|
|
}
|
|
|
|
static void process_request(struct v9fs_mux_data *m, struct v9fs_req *req)
|
|
{
|
|
int ecode, tag;
|
|
struct v9fs_str *ename;
|
|
|
|
tag = req->tag;
|
|
if (!req->err && req->rcall->id == RERROR) {
|
|
ecode = req->rcall->params.rerror.errno;
|
|
ename = &req->rcall->params.rerror.error;
|
|
|
|
dprintk(DEBUG_MUX, "Rerror %.*s\n", ename->len, ename->str);
|
|
|
|
if (*m->extended)
|
|
req->err = -ecode;
|
|
|
|
if (!req->err) {
|
|
req->err = v9fs_errstr2errno(ename->str, ename->len);
|
|
|
|
if (!req->err) { /* string match failed */
|
|
PRINT_FCALL_ERROR("unknown error", req->rcall);
|
|
}
|
|
|
|
if (!req->err)
|
|
req->err = -ESERVERFAULT;
|
|
}
|
|
} else if (req->tcall && req->rcall->id != req->tcall->id + 1) {
|
|
dprintk(DEBUG_ERROR, "fcall mismatch: expected %d, got %d\n",
|
|
req->tcall->id + 1, req->rcall->id);
|
|
if (!req->err)
|
|
req->err = -EIO;
|
|
}
|
|
|
|
if (req->err == ERREQFLUSH)
|
|
return;
|
|
|
|
if (req->cb) {
|
|
dprintk(DEBUG_MUX, "calling callback tcall %p rcall %p\n",
|
|
req->tcall, req->rcall);
|
|
|
|
(*req->cb) (req->cba, req->tcall, req->rcall, req->err);
|
|
req->cb = NULL;
|
|
} else
|
|
kfree(req->rcall);
|
|
|
|
v9fs_mux_put_tag(m, tag);
|
|
|
|
wake_up(&m->equeue);
|
|
kfree(req);
|
|
}
|
|
|
|
/**
|
|
* v9fs_read_work - called when there is some data to be read from a transport
|
|
*/
|
|
static void v9fs_read_work(void *a)
|
|
{
|
|
int n, err;
|
|
struct v9fs_mux_data *m;
|
|
struct v9fs_req *req, *rptr, *rreq;
|
|
struct v9fs_fcall *rcall;
|
|
char *rbuf;
|
|
|
|
m = a;
|
|
|
|
if (m->err < 0)
|
|
return;
|
|
|
|
rcall = NULL;
|
|
dprintk(DEBUG_MUX, "start mux %p pos %d\n", m, m->rpos);
|
|
|
|
if (!m->rcall) {
|
|
m->rcall =
|
|
kmalloc(sizeof(struct v9fs_fcall) + m->msize, GFP_KERNEL);
|
|
if (!m->rcall) {
|
|
err = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
m->rbuf = (char *)m->rcall + sizeof(struct v9fs_fcall);
|
|
m->rpos = 0;
|
|
}
|
|
|
|
clear_bit(Rpending, &m->wsched);
|
|
err = m->trans->read(m->trans, m->rbuf + m->rpos, m->msize - m->rpos);
|
|
dprintk(DEBUG_MUX, "mux %p got %d bytes\n", m, err);
|
|
if (err == -EAGAIN) {
|
|
clear_bit(Rworksched, &m->wsched);
|
|
return;
|
|
}
|
|
|
|
if (err <= 0)
|
|
goto error;
|
|
|
|
m->rpos += err;
|
|
while (m->rpos > 4) {
|
|
n = le32_to_cpu(*(__le32 *) m->rbuf);
|
|
if (n >= m->msize) {
|
|
dprintk(DEBUG_ERROR,
|
|
"requested packet size too big: %d\n", n);
|
|
err = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
if (m->rpos < n)
|
|
break;
|
|
|
|
dump_data(m->rbuf, n);
|
|
err =
|
|
v9fs_deserialize_fcall(m->rbuf, n, m->rcall, *m->extended);
|
|
if (err < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if ((v9fs_debug_level&DEBUG_FCALL) == DEBUG_FCALL) {
|
|
char buf[150];
|
|
|
|
v9fs_printfcall(buf, sizeof(buf), m->rcall,
|
|
*m->extended);
|
|
printk(KERN_NOTICE ">>> %p %s\n", m, buf);
|
|
}
|
|
|
|
rcall = m->rcall;
|
|
rbuf = m->rbuf;
|
|
if (m->rpos > n) {
|
|
m->rcall = kmalloc(sizeof(struct v9fs_fcall) + m->msize,
|
|
GFP_KERNEL);
|
|
if (!m->rcall) {
|
|
err = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
m->rbuf = (char *)m->rcall + sizeof(struct v9fs_fcall);
|
|
memmove(m->rbuf, rbuf + n, m->rpos - n);
|
|
m->rpos -= n;
|
|
} else {
|
|
m->rcall = NULL;
|
|
m->rbuf = NULL;
|
|
m->rpos = 0;
|
|
}
|
|
|
|
dprintk(DEBUG_MUX, "mux %p fcall id %d tag %d\n", m, rcall->id,
|
|
rcall->tag);
|
|
|
|
req = NULL;
|
|
spin_lock(&m->lock);
|
|
list_for_each_entry_safe(rreq, rptr, &m->req_list, req_list) {
|
|
if (rreq->tag == rcall->tag) {
|
|
req = rreq;
|
|
req->rcall = rcall;
|
|
list_del(&req->req_list);
|
|
spin_unlock(&m->lock);
|
|
process_request(m, req);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (!req) {
|
|
spin_unlock(&m->lock);
|
|
if (err >= 0 && rcall->id != RFLUSH)
|
|
dprintk(DEBUG_ERROR,
|
|
"unexpected response mux %p id %d tag %d\n",
|
|
m, rcall->id, rcall->tag);
|
|
kfree(rcall);
|
|
}
|
|
}
|
|
|
|
if (!list_empty(&m->req_list)) {
|
|
if (test_and_clear_bit(Rpending, &m->wsched))
|
|
n = POLLIN;
|
|
else
|
|
n = m->trans->poll(m->trans, NULL);
|
|
|
|
if (n & POLLIN) {
|
|
dprintk(DEBUG_MUX, "schedule read work mux %p\n", m);
|
|
queue_work(v9fs_mux_wq, &m->rq);
|
|
} else
|
|
clear_bit(Rworksched, &m->wsched);
|
|
} else
|
|
clear_bit(Rworksched, &m->wsched);
|
|
|
|
return;
|
|
|
|
error:
|
|
v9fs_mux_cancel(m, err);
|
|
clear_bit(Rworksched, &m->wsched);
|
|
}
|
|
|
|
/**
|
|
* v9fs_send_request - send 9P request
|
|
* The function can sleep until the request is scheduled for sending.
|
|
* The function can be interrupted. Return from the function is not
|
|
* a guarantee that the request is sent succesfully. Can return errors
|
|
* that can be retrieved by PTR_ERR macros.
|
|
*
|
|
* @m: mux data
|
|
* @tc: request to be sent
|
|
* @cb: callback function to call when response is received
|
|
* @cba: parameter to pass to the callback function
|
|
*/
|
|
static struct v9fs_req *v9fs_send_request(struct v9fs_mux_data *m,
|
|
struct v9fs_fcall *tc,
|
|
v9fs_mux_req_callback cb, void *cba)
|
|
{
|
|
int n;
|
|
struct v9fs_req *req;
|
|
|
|
dprintk(DEBUG_MUX, "mux %p task %p tcall %p id %d\n", m, current,
|
|
tc, tc->id);
|
|
if (m->err < 0)
|
|
return ERR_PTR(m->err);
|
|
|
|
req = kmalloc(sizeof(struct v9fs_req), GFP_KERNEL);
|
|
if (!req)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (tc->id == TVERSION)
|
|
n = V9FS_NOTAG;
|
|
else
|
|
n = v9fs_mux_get_tag(m);
|
|
|
|
if (n < 0)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
v9fs_set_tag(tc, n);
|
|
|
|
if ((v9fs_debug_level&DEBUG_FCALL) == DEBUG_FCALL) {
|
|
char buf[150];
|
|
|
|
v9fs_printfcall(buf, sizeof(buf), tc, *m->extended);
|
|
printk(KERN_NOTICE "<<< %p %s\n", m, buf);
|
|
}
|
|
|
|
req->tag = n;
|
|
req->tcall = tc;
|
|
req->rcall = NULL;
|
|
req->err = 0;
|
|
req->cb = cb;
|
|
req->cba = cba;
|
|
|
|
spin_lock(&m->lock);
|
|
list_add_tail(&req->req_list, &m->unsent_req_list);
|
|
spin_unlock(&m->lock);
|
|
|
|
if (test_and_clear_bit(Wpending, &m->wsched))
|
|
n = POLLOUT;
|
|
else
|
|
n = m->trans->poll(m->trans, NULL);
|
|
|
|
if (n & POLLOUT && !test_and_set_bit(Wworksched, &m->wsched))
|
|
queue_work(v9fs_mux_wq, &m->wq);
|
|
|
|
return req;
|
|
}
|
|
|
|
static void v9fs_mux_flush_cb(void *a, struct v9fs_fcall *tc,
|
|
struct v9fs_fcall *rc, int err)
|
|
{
|
|
v9fs_mux_req_callback cb;
|
|
int tag;
|
|
struct v9fs_mux_data *m;
|
|
struct v9fs_req *req, *rptr;
|
|
|
|
m = a;
|
|
dprintk(DEBUG_MUX, "mux %p tc %p rc %p err %d oldtag %d\n", m, tc,
|
|
rc, err, tc->params.tflush.oldtag);
|
|
|
|
spin_lock(&m->lock);
|
|
cb = NULL;
|
|
tag = tc->params.tflush.oldtag;
|
|
list_for_each_entry_safe(req, rptr, &m->req_list, req_list) {
|
|
if (req->tag == tag) {
|
|
list_del(&req->req_list);
|
|
if (req->cb) {
|
|
cb = req->cb;
|
|
req->cb = NULL;
|
|
spin_unlock(&m->lock);
|
|
(*cb) (req->cba, req->tcall, req->rcall,
|
|
req->err);
|
|
}
|
|
kfree(req);
|
|
wake_up(&m->equeue);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!cb)
|
|
spin_unlock(&m->lock);
|
|
|
|
v9fs_mux_put_tag(m, tag);
|
|
kfree(tc);
|
|
kfree(rc);
|
|
}
|
|
|
|
static void
|
|
v9fs_mux_flush_request(struct v9fs_mux_data *m, struct v9fs_req *req)
|
|
{
|
|
struct v9fs_fcall *fc;
|
|
|
|
dprintk(DEBUG_MUX, "mux %p req %p tag %d\n", m, req, req->tag);
|
|
|
|
fc = v9fs_create_tflush(req->tag);
|
|
v9fs_send_request(m, fc, v9fs_mux_flush_cb, m);
|
|
}
|
|
|
|
static void
|
|
v9fs_mux_rpc_cb(void *a, struct v9fs_fcall *tc, struct v9fs_fcall *rc, int err)
|
|
{
|
|
struct v9fs_mux_rpc *r;
|
|
|
|
if (err == ERREQFLUSH) {
|
|
kfree(rc);
|
|
dprintk(DEBUG_MUX, "err req flush\n");
|
|
return;
|
|
}
|
|
|
|
r = a;
|
|
dprintk(DEBUG_MUX, "mux %p req %p tc %p rc %p err %d\n", r->m, r->req,
|
|
tc, rc, err);
|
|
r->rcall = rc;
|
|
r->err = err;
|
|
wake_up(&r->wqueue);
|
|
}
|
|
|
|
/**
|
|
* v9fs_mux_rpc - sends 9P request and waits until a response is available.
|
|
* The function can be interrupted.
|
|
* @m: mux data
|
|
* @tc: request to be sent
|
|
* @rc: pointer where a pointer to the response is stored
|
|
*/
|
|
int
|
|
v9fs_mux_rpc(struct v9fs_mux_data *m, struct v9fs_fcall *tc,
|
|
struct v9fs_fcall **rc)
|
|
{
|
|
int err;
|
|
unsigned long flags;
|
|
struct v9fs_req *req;
|
|
struct v9fs_mux_rpc r;
|
|
|
|
r.err = 0;
|
|
r.rcall = NULL;
|
|
r.m = m;
|
|
init_waitqueue_head(&r.wqueue);
|
|
|
|
if (rc)
|
|
*rc = NULL;
|
|
|
|
req = v9fs_send_request(m, tc, v9fs_mux_rpc_cb, &r);
|
|
if (IS_ERR(req)) {
|
|
err = PTR_ERR(req);
|
|
dprintk(DEBUG_MUX, "error %d\n", err);
|
|
return PTR_ERR(req);
|
|
}
|
|
|
|
r.req = req;
|
|
dprintk(DEBUG_MUX, "mux %p tc %p tag %d rpc %p req %p\n", m, tc,
|
|
req->tag, &r, req);
|
|
err = wait_event_interruptible(r.wqueue, r.rcall != NULL || r.err < 0);
|
|
if (r.err < 0)
|
|
err = r.err;
|
|
|
|
if (err == -ERESTARTSYS && m->trans->status == Connected && m->err == 0) {
|
|
spin_lock(&m->lock);
|
|
req->tcall = NULL;
|
|
req->err = ERREQFLUSH;
|
|
spin_unlock(&m->lock);
|
|
|
|
clear_thread_flag(TIF_SIGPENDING);
|
|
v9fs_mux_flush_request(m, req);
|
|
spin_lock_irqsave(¤t->sighand->siglock, flags);
|
|
recalc_sigpending();
|
|
spin_unlock_irqrestore(¤t->sighand->siglock, flags);
|
|
}
|
|
|
|
if (!err) {
|
|
if (r.rcall)
|
|
dprintk(DEBUG_MUX, "got response id %d tag %d\n",
|
|
r.rcall->id, r.rcall->tag);
|
|
|
|
if (rc)
|
|
*rc = r.rcall;
|
|
else
|
|
kfree(r.rcall);
|
|
} else {
|
|
kfree(r.rcall);
|
|
dprintk(DEBUG_MUX, "got error %d\n", err);
|
|
if (err > 0)
|
|
err = -EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if 0
|
|
/**
|
|
* v9fs_mux_rpcnb - sends 9P request without waiting for response.
|
|
* @m: mux data
|
|
* @tc: request to be sent
|
|
* @cb: callback function to be called when response arrives
|
|
* @cba: value to pass to the callback function
|
|
*/
|
|
int v9fs_mux_rpcnb(struct v9fs_mux_data *m, struct v9fs_fcall *tc,
|
|
v9fs_mux_req_callback cb, void *a)
|
|
{
|
|
int err;
|
|
struct v9fs_req *req;
|
|
|
|
req = v9fs_send_request(m, tc, cb, a);
|
|
if (IS_ERR(req)) {
|
|
err = PTR_ERR(req);
|
|
dprintk(DEBUG_MUX, "error %d\n", err);
|
|
return PTR_ERR(req);
|
|
}
|
|
|
|
dprintk(DEBUG_MUX, "mux %p tc %p tag %d\n", m, tc, req->tag);
|
|
return 0;
|
|
}
|
|
#endif /* 0 */
|
|
|
|
/**
|
|
* v9fs_mux_cancel - cancel all pending requests with error
|
|
* @m: mux data
|
|
* @err: error code
|
|
*/
|
|
void v9fs_mux_cancel(struct v9fs_mux_data *m, int err)
|
|
{
|
|
struct v9fs_req *req, *rtmp;
|
|
LIST_HEAD(cancel_list);
|
|
|
|
dprintk(DEBUG_MUX, "mux %p err %d\n", m, err);
|
|
m->err = err;
|
|
spin_lock(&m->lock);
|
|
list_for_each_entry_safe(req, rtmp, &m->req_list, req_list) {
|
|
list_move(&req->req_list, &cancel_list);
|
|
}
|
|
spin_unlock(&m->lock);
|
|
|
|
list_for_each_entry_safe(req, rtmp, &cancel_list, req_list) {
|
|
list_del(&req->req_list);
|
|
if (!req->err)
|
|
req->err = err;
|
|
|
|
if (req->cb)
|
|
(*req->cb) (req->cba, req->tcall, req->rcall, req->err);
|
|
else
|
|
kfree(req->rcall);
|
|
|
|
kfree(req);
|
|
}
|
|
|
|
wake_up(&m->equeue);
|
|
}
|
|
|
|
static u16 v9fs_mux_get_tag(struct v9fs_mux_data *m)
|
|
{
|
|
int tag;
|
|
|
|
tag = v9fs_get_idpool(&m->tagpool);
|
|
if (tag < 0)
|
|
return V9FS_NOTAG;
|
|
else
|
|
return (u16) tag;
|
|
}
|
|
|
|
static void v9fs_mux_put_tag(struct v9fs_mux_data *m, u16 tag)
|
|
{
|
|
if (tag != V9FS_NOTAG && v9fs_check_idpool(tag, &m->tagpool))
|
|
v9fs_put_idpool(tag, &m->tagpool);
|
|
}
|