nfs41: Implement NFSv4.1 callback service process.
[pandora-kernel.git] / fs / nfs / callback.c
index 4b1313e..4709288 100644 (file)
@@ -17,6 +17,9 @@
 #include <linux/freezer.h>
 #include <linux/kthread.h>
 #include <linux/sunrpc/svcauth_gss.h>
+#if defined(CONFIG_NFS_V4_1)
+#include <linux/sunrpc/bc_xprt.h>
+#endif
 
 #include <net/inet_sock.h>
 
@@ -28,6 +31,7 @@
 
 struct nfs_callback_data {
        unsigned int users;
+       struct svc_serv *serv;
        struct svc_rqst *rqst;
        struct task_struct *task;
 };
@@ -131,6 +135,99 @@ out_err:
        return ERR_PTR(ret);
 }
 
+#if defined(CONFIG_NFS_V4_1)
+/*
+ * The callback service for NFSv4.1 callbacks
+ */
+static int
+nfs41_callback_svc(void *vrqstp)
+{
+       struct svc_rqst *rqstp = vrqstp;
+       struct svc_serv *serv = rqstp->rq_server;
+       struct rpc_rqst *req;
+       int error;
+       DEFINE_WAIT(wq);
+
+       set_freezable();
+
+       /*
+        * FIXME: do we really need to run this under the BKL? If so, please
+        * add a comment about what it's intended to protect.
+        */
+       lock_kernel();
+       while (!kthread_should_stop()) {
+               prepare_to_wait(&serv->sv_cb_waitq, &wq, TASK_INTERRUPTIBLE);
+               spin_lock_bh(&serv->sv_cb_lock);
+               if (!list_empty(&serv->sv_cb_list)) {
+                       req = list_first_entry(&serv->sv_cb_list,
+                                       struct rpc_rqst, rq_bc_list);
+                       list_del(&req->rq_bc_list);
+                       spin_unlock_bh(&serv->sv_cb_lock);
+                       dprintk("Invoking bc_svc_process()\n");
+                       error = bc_svc_process(serv, req, rqstp);
+                       dprintk("bc_svc_process() returned w/ error code= %d\n",
+                               error);
+               } else {
+                       spin_unlock_bh(&serv->sv_cb_lock);
+                       schedule();
+               }
+               finish_wait(&serv->sv_cb_waitq, &wq);
+       }
+       unlock_kernel();
+       nfs_callback_info.task = NULL;
+       svc_exit_thread(rqstp);
+       return 0;
+}
+
+/*
+ * Bring up the NFSv4.1 callback service
+ */
+struct svc_rqst *
+nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt)
+{
+       /*
+        * Save the svc_serv in the transport so that it can
+        * be referenced when the session backchannel is initialized
+        */
+       xprt->bc_serv = serv;
+
+       INIT_LIST_HEAD(&serv->sv_cb_list);
+       spin_lock_init(&serv->sv_cb_lock);
+       init_waitqueue_head(&serv->sv_cb_waitq);
+       return svc_prepare_thread(serv, &serv->sv_pools[0]);
+}
+
+static inline int nfs_minorversion_callback_svc_setup(u32 minorversion,
+               struct svc_serv *serv, struct rpc_xprt *xprt,
+               struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
+{
+       if (minorversion) {
+               *rqstpp = nfs41_callback_up(serv, xprt);
+               *callback_svc = nfs41_callback_svc;
+       }
+       return minorversion;
+}
+
+static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,
+               struct nfs_callback_data *cb_info)
+{
+       if (minorversion)
+               xprt->bc_serv = cb_info->serv;
+}
+#else
+static inline int nfs_minorversion_callback_svc_setup(u32 minorversion,
+               struct svc_serv *serv, struct rpc_xprt *xprt,
+               struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
+{
+       return 0;
+}
+
+static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,
+               struct nfs_callback_data *cb_info)
+{
+}
+#endif /* CONFIG_NFS_V4_1 */
+
 /*
  * Bring up the callback thread if it is not already up.
  */
@@ -141,21 +238,25 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
        int (*callback_svc)(void *vrqstp);
        char svc_name[12];
        int ret = 0;
+       int minorversion_setup;
 
        mutex_lock(&nfs_callback_mutex);
-       if (nfs_callback_info.users++ || nfs_callback_info.task != NULL)
+       if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) {
+               nfs_callback_bc_serv(minorversion, xprt, &nfs_callback_info);
                goto out;
+       }
        serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
        if (!serv) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       if (!minorversion) {
+       minorversion_setup =  nfs_minorversion_callback_svc_setup(minorversion,
+                                       serv, xprt, &rqstp, &callback_svc);
+       if (!minorversion_setup) {
+               /* v4.0 callback setup */
                rqstp = nfs4_callback_up(serv);
                callback_svc = nfs4_callback_svc;
-       } else {
-               BUG();  /* for now */
        }
 
        if (IS_ERR(rqstp)) {
@@ -166,6 +267,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
        svc_sock_update_bufs(serv);
 
        sprintf(svc_name, "nfsv4.%u-svc", minorversion);
+       nfs_callback_info.serv = serv;
        nfs_callback_info.rqst = rqstp;
        nfs_callback_info.task = kthread_run(callback_svc,
                                             nfs_callback_info.rqst,
@@ -173,6 +275,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
        if (IS_ERR(nfs_callback_info.task)) {
                ret = PTR_ERR(nfs_callback_info.task);
                svc_exit_thread(nfs_callback_info.rqst);
+               nfs_callback_info.serv = NULL;
                nfs_callback_info.rqst = NULL;
                nfs_callback_info.task = NULL;
                goto out_err;
@@ -205,6 +308,7 @@ void nfs_callback_down(void)
        if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) {
                kthread_stop(nfs_callback_info.task);
                svc_exit_thread(nfs_callback_info.rqst);
+               nfs_callback_info.serv = NULL;
                nfs_callback_info.rqst = NULL;
                nfs_callback_info.task = NULL;
        }