return idx;
 }
 
+static void
+encode_referring_call4(struct xdr_stream *xdr,
+                      const struct nfsd4_referring_call *rc)
+{
+       encode_uint32(xdr, rc->rc_sequenceid);
+       encode_uint32(xdr, rc->rc_slotid);
+}
+
+static void
+encode_referring_call_list4(struct xdr_stream *xdr,
+                           const struct nfsd4_referring_call_list *rcl)
+{
+       struct nfsd4_referring_call *rc;
+       __be32 *p;
+
+       p = xdr_reserve_space(xdr, NFS4_MAX_SESSIONID_LEN);
+       xdr_encode_opaque_fixed(p, rcl->rcl_sessionid.data,
+                                       NFS4_MAX_SESSIONID_LEN);
+       encode_uint32(xdr, rcl->__nr_referring_calls);
+       list_for_each_entry(rc, &rcl->rcl_referring_calls, __list)
+               encode_referring_call4(xdr, rc);
+}
+
 /*
  * CB_SEQUENCE4args
  *
                                    struct nfs4_cb_compound_hdr *hdr)
 {
        struct nfsd4_session *session = cb->cb_clp->cl_cb_session;
+       struct nfsd4_referring_call_list *rcl;
        __be32 *p;
 
        if (hdr->minorversion == 0)
        encode_nfs_cb_opnum4(xdr, OP_CB_SEQUENCE);
        encode_sessionid4(xdr, session);
 
-       p = xdr_reserve_space(xdr, 4 + 4 + 4 + 4 + 4);
+       p = xdr_reserve_space(xdr, XDR_UNIT * 4);
        *p++ = cpu_to_be32(session->se_cb_seq_nr[cb->cb_held_slot]);    /* csa_sequenceid */
        *p++ = cpu_to_be32(cb->cb_held_slot);           /* csa_slotid */
        *p++ = cpu_to_be32(highest_slotid(session)); /* csa_highest_slotid */
        *p++ = xdr_zero;                        /* csa_cachethis */
-       xdr_encode_empty_array(p);              /* csa_referring_call_lists */
+
+       /* csa_referring_call_lists */
+       encode_uint32(xdr, cb->cb_nr_referring_call_list);
+       list_for_each_entry(rcl, &cb->cb_referring_call_list, __list)
+               encode_referring_call_list4(xdr, rcl);
 
        hdr->nops++;
 }
        nfsd41_cb_inflight_end(clp);
 }
 
-/*
- * TODO: cb_sequence should support referring call lists, cachethis,
- * and mark callback channel down on communication errors.
+/**
+ * nfsd41_cb_referring_call - add a referring call to a callback operation
+ * @cb: context of callback to add the rc to
+ * @sessionid: referring call's session ID
+ * @slotid: referring call's session slot index
+ * @seqno: referring call's slot sequence number
+ *
+ * Caller serializes access to @cb.
+ *
+ * NB: If memory allocation fails, the referring call is not added.
  */
+void nfsd41_cb_referring_call(struct nfsd4_callback *cb,
+                             struct nfs4_sessionid *sessionid,
+                             u32 slotid, u32 seqno)
+{
+       struct nfsd4_referring_call_list *rcl;
+       struct nfsd4_referring_call *rc;
+       bool found;
+
+       might_sleep();
+
+       found = false;
+       list_for_each_entry(rcl, &cb->cb_referring_call_list, __list) {
+               if (!memcmp(rcl->rcl_sessionid.data, sessionid->data,
+                          NFS4_MAX_SESSIONID_LEN)) {
+                       found = true;
+                       break;
+               }
+       }
+       if (!found) {
+               rcl = kmalloc(sizeof(*rcl), GFP_KERNEL);
+               if (!rcl)
+                       return;
+               memcpy(rcl->rcl_sessionid.data, sessionid->data,
+                      NFS4_MAX_SESSIONID_LEN);
+               rcl->__nr_referring_calls = 0;
+               INIT_LIST_HEAD(&rcl->rcl_referring_calls);
+               list_add(&rcl->__list, &cb->cb_referring_call_list);
+               cb->cb_nr_referring_call_list++;
+       }
+
+       found = false;
+       list_for_each_entry(rc, &rcl->rcl_referring_calls, __list) {
+               if (rc->rc_sequenceid == seqno && rc->rc_slotid == slotid) {
+                       found = true;
+                       break;
+               }
+       }
+       if (!found) {
+               rc = kmalloc(sizeof(*rc), GFP_KERNEL);
+               if (!rc)
+                       goto out;
+               rc->rc_sequenceid = seqno;
+               rc->rc_slotid = slotid;
+               rcl->__nr_referring_calls++;
+               list_add(&rc->__list, &rcl->rcl_referring_calls);
+       }
+
+out:
+       if (!rcl->__nr_referring_calls) {
+               cb->cb_nr_referring_call_list--;
+               kfree(rcl);
+       }
+}
+
+/**
+ * nfsd41_cb_destroy_referring_call_list - release referring call info
+ * @cb: context of a callback that has completed
+ *
+ * Callers who allocate referring calls using nfsd41_cb_referring_call() must
+ * release those resources by calling nfsd41_cb_destroy_referring_call_list.
+ *
+ * Caller serializes access to @cb.
+ */
+void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb)
+{
+       struct nfsd4_referring_call_list *rcl;
+       struct nfsd4_referring_call *rc;
+
+       while (!list_empty(&cb->cb_referring_call_list)) {
+               rcl = list_first_entry(&cb->cb_referring_call_list,
+                                      struct nfsd4_referring_call_list,
+                                      __list);
+
+               while (!list_empty(&rcl->rcl_referring_calls)) {
+                       rc = list_first_entry(&rcl->rcl_referring_calls,
+                                             struct nfsd4_referring_call,
+                                             __list);
+                       list_del(&rc->__list);
+                       kfree(rc);
+               }
+               list_del(&rcl->__list);
+               kfree(rcl);
+       }
+}
+
 static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)
 {
        struct nfsd4_callback *cb = calldata;
        INIT_WORK(&cb->cb_work, nfsd4_run_cb_work);
        cb->cb_status = 0;
        cb->cb_held_slot = -1;
+       cb->cb_nr_referring_call_list = 0;
+       INIT_LIST_HEAD(&cb->cb_referring_call_list);
 }
 
 /**
 
        refcount_t              cs_count;
 } copy_stateid_t;
 
+struct nfsd4_referring_call {
+       struct list_head        __list;
+
+       u32                     rc_sequenceid;
+       u32                     rc_slotid;
+};
+
+struct nfsd4_referring_call_list {
+       struct list_head        __list;
+
+       struct nfs4_sessionid   rcl_sessionid;
+       int                     __nr_referring_calls;
+       struct list_head        rcl_referring_calls;
+};
+
 struct nfsd4_callback {
        struct nfs4_client *cb_clp;
        struct rpc_message cb_msg;
        int cb_seq_status;
        int cb_status;
        int cb_held_slot;
+
+       int cb_nr_referring_call_list;
+       struct list_head cb_referring_call_list;
 };
 
 struct nfsd4_callback_ops {
 extern void nfsd4_probe_callback(struct nfs4_client *clp);
 extern void nfsd4_probe_callback_sync(struct nfs4_client *clp);
 extern void nfsd4_change_callback(struct nfs4_client *clp, struct nfs4_cb_conn *);
+extern void nfsd41_cb_referring_call(struct nfsd4_callback *cb,
+                                    struct nfs4_sessionid *sessionid,
+                                    u32 slotid, u32 seqno);
+extern void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb);
 extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
                const struct nfsd4_callback_ops *ops, enum nfsd4_cb_op op);
 extern bool nfsd4_run_cb(struct nfsd4_callback *cb);