CIFS: Use multicredits for SMB 2.1/3 writes
authorPavel Shilovsky <pshilovsky@samba.org>
Thu, 5 Jun 2014 15:03:27 +0000 (19:03 +0400)
committerSteve French <smfrench@gmail.com>
Sat, 2 Aug 2014 06:23:03 +0000 (01:23 -0500)
If we negotiate SMB 2.1 and higher version of the protocol and
a server supports large write buffer size, we need to consume 1
credit per 65536 bytes. So, we need to know how many credits
we have and obtain the required number of them before constructing
a writedata structure in writepages and iovec write.

Reviewed-by: Shirish Pargaonkar <spargaonkar@suse.com>
Signed-off-by: Pavel Shilovsky <pshilovsky@samba.org>
Signed-off-by: Steve French <smfrench@gmail.com>
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/file.c
fs/cifs/smb1ops.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2transport.c
fs/cifs/transport.c

index 8c53f209315217025846f79064084c0d22991f43..54ca2b985a0dc179fc6e6a6c6ad4ca892bc3cfb3 100644 (file)
@@ -406,6 +406,9 @@ struct smb_version_operations {
                        int);
        /* writepages retry size */
        unsigned int (*wp_retry_size)(struct inode *);
+       /* get mtu credits */
+       int (*wait_mtu_credits)(struct TCP_Server_Info *, unsigned int,
+                               unsigned int *, unsigned int *);
 };
 
 struct smb_version_values {
@@ -641,6 +644,16 @@ add_credits(struct TCP_Server_Info *server, const unsigned int add,
        server->ops->add_credits(server, add, optype);
 }
 
+static inline void
+add_credits_and_wake_if(struct TCP_Server_Info *server, const unsigned int add,
+                       const int optype)
+{
+       if (add) {
+               server->ops->add_credits(server, add, optype);
+               wake_up(&server->request_q);
+       }
+}
+
 static inline void
 set_credits(struct TCP_Server_Info *server, const int val)
 {
@@ -1075,6 +1088,7 @@ struct cifs_writedata {
        int                             result;
        unsigned int                    pagesz;
        unsigned int                    tailsz;
+       unsigned int                    credits;
        unsigned int                    nr_pages;
        struct page                     *pages[];
 };
@@ -1400,6 +1414,7 @@ static inline void free_dfs_info_array(struct dfs_info3_param *param,
 #define   CIFS_OBREAK_OP   0x0100    /* oplock break request */
 #define   CIFS_NEG_OP      0x0200    /* negotiate request */
 #define   CIFS_OP_MASK     0x0380    /* mask request type */
+#define   CIFS_HAS_CREDITS 0x0400    /* already has credits */
 
 /* Security Flags: indicate type of session setup needed */
 #define   CIFSSEC_MAY_SIGN     0x00001
index de49d7a37b00334700a1ff5acd3245757bf966ee..c31ce98c1704a32b998f993d9a26613dc1342e29 100644 (file)
@@ -90,6 +90,9 @@ extern struct mid_q_entry *cifs_setup_async_request(struct TCP_Server_Info *,
                                                struct smb_rqst *);
 extern int cifs_check_receive(struct mid_q_entry *mid,
                        struct TCP_Server_Info *server, bool log_error);
+extern int cifs_wait_mtu_credits(struct TCP_Server_Info *server,
+                                unsigned int size, unsigned int *num,
+                                unsigned int *credits);
 extern int SendReceive2(const unsigned int /* xid */ , struct cifs_ses *,
                        struct kvec *, int /* nvec to send */,
                        int * /* type of buf returned */ , const int flags);
index c9c4f5ac3c78b21c3f8f4045de2d651de5c2c62e..c79bdf3e6f515a1cabe031e7a7fc2ee1852ec97e 100644 (file)
@@ -1670,8 +1670,8 @@ cifs_write(struct cifsFileInfo *open_file, __u32 pid, const char *write_data,
                                        break;
                        }
 
-                       len = min((size_t)cifs_sb->wsize,
-                                 write_size - total_written);
+                       len = min(server->ops->wp_retry_size(dentry->d_inode),
+                                 (unsigned int)write_size - total_written);
                        /* iov[0] is reserved for smb header */
                        iov[1].iov_base = (char *)write_data + total_written;
                        iov[1].iov_len = len;
@@ -2031,6 +2031,7 @@ static int cifs_writepages(struct address_space *mapping,
                           struct writeback_control *wbc)
 {
        struct cifs_sb_info *cifs_sb = CIFS_SB(mapping->host->i_sb);
+       struct TCP_Server_Info *server;
        bool done = false, scanned = false, range_whole = false;
        pgoff_t end, index;
        struct cifs_writedata *wdata;
@@ -2053,23 +2054,30 @@ static int cifs_writepages(struct address_space *mapping,
                        range_whole = true;
                scanned = true;
        }
+       server = cifs_sb_master_tcon(cifs_sb)->ses->server;
 retry:
        while (!done && index <= end) {
-               unsigned int i, nr_pages, found_pages;
+               unsigned int i, nr_pages, found_pages, wsize, credits;
                pgoff_t next = 0, tofind, saved_index = index;
 
-               tofind = min((cifs_sb->wsize / PAGE_CACHE_SIZE) - 1,
-                               end - index) + 1;
+               rc = server->ops->wait_mtu_credits(server, cifs_sb->wsize,
+                                                  &wsize, &credits);
+               if (rc)
+                       break;
+
+               tofind = min((wsize / PAGE_CACHE_SIZE) - 1, end - index) + 1;
 
                wdata = wdata_alloc_and_fillpages(tofind, mapping, end, &index,
                                                  &found_pages);
                if (!wdata) {
                        rc = -ENOMEM;
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
                if (found_pages == 0) {
                        kref_put(&wdata->refcount, cifs_writedata_release);
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
@@ -2079,13 +2087,17 @@ retry:
                /* nothing to write? */
                if (nr_pages == 0) {
                        kref_put(&wdata->refcount, cifs_writedata_release);
+                       add_credits_and_wake_if(server, credits, 0);
                        continue;
                }
 
+               wdata->credits = credits;
+
                rc = wdata_send_pages(wdata, nr_pages, mapping, wbc);
 
                /* send failure -- clean up the mess */
                if (rc != 0) {
+                       add_credits_and_wake_if(server, wdata->credits, 0);
                        for (i = 0; i < nr_pages; ++i) {
                                if (rc == -EAGAIN)
                                        redirty_page_for_writepage(wbc,
@@ -2466,17 +2478,26 @@ cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from,
        memcpy(&saved_from, from, sizeof(struct iov_iter));
 
        do {
-               nr_pages = get_numpages(cifs_sb->wsize, len, &cur_len);
+               unsigned int wsize, credits;
+
+               rc = server->ops->wait_mtu_credits(server, cifs_sb->wsize,
+                                                  &wsize, &credits);
+               if (rc)
+                       break;
+
+               nr_pages = get_numpages(wsize, len, &cur_len);
                wdata = cifs_writedata_alloc(nr_pages,
                                             cifs_uncached_writev_complete);
                if (!wdata) {
                        rc = -ENOMEM;
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
                rc = cifs_write_allocate_pages(wdata->pages, nr_pages);
                if (rc) {
                        kfree(wdata);
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
@@ -2486,6 +2507,7 @@ cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from,
                        for (i = 0; i < nr_pages; i++)
                                put_page(wdata->pages[i]);
                        kfree(wdata);
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
@@ -2504,12 +2526,14 @@ cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from,
                wdata->bytes = cur_len;
                wdata->pagesz = PAGE_SIZE;
                wdata->tailsz = cur_len - ((nr_pages - 1) * PAGE_SIZE);
+               wdata->credits = credits;
 
                if (!wdata->cfile->invalidHandle ||
                    !cifs_reopen_file(wdata->cfile, false))
                        rc = server->ops->async_writev(wdata,
                                        cifs_uncached_writedata_release);
                if (rc) {
+                       add_credits_and_wake_if(server, wdata->credits, 0);
                        kref_put(&wdata->refcount,
                                 cifs_uncached_writedata_release);
                        if (rc == -EAGAIN) {
index 8a963426d8105621922ab33df9be2786302a5e91..5e8c22d6c7b9dc96f4079a9579fd8b5c68143af8 100644 (file)
@@ -1025,6 +1025,7 @@ struct smb_version_operations smb1_operations = {
        .set_credits = cifs_set_credits,
        .get_credits_field = cifs_get_credits_field,
        .get_credits = cifs_get_credits,
+       .wait_mtu_credits = cifs_wait_mtu_credits,
        .get_next_mid = cifs_get_next_mid,
        .read_data_offset = cifs_read_data_offset,
        .read_data_length = cifs_read_data_length,
index e35ce5b3d88f238aba992ffbc37a35d481008e33..3427c1fa38066b858976c0615f90cc027622fa71 100644 (file)
@@ -112,6 +112,53 @@ smb2_get_credits(struct mid_q_entry *mid)
        return le16_to_cpu(((struct smb2_hdr *)mid->resp_buf)->CreditRequest);
 }
 
+static int
+smb2_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size,
+                     unsigned int *num, unsigned int *credits)
+{
+       int rc = 0;
+       unsigned int scredits;
+
+       spin_lock(&server->req_lock);
+       while (1) {
+               if (server->credits <= 0) {
+                       spin_unlock(&server->req_lock);
+                       cifs_num_waiters_inc(server);
+                       rc = wait_event_killable(server->request_q,
+                                       has_credits(server, &server->credits));
+                       cifs_num_waiters_dec(server);
+                       if (rc)
+                               return rc;
+                       spin_lock(&server->req_lock);
+               } else {
+                       if (server->tcpStatus == CifsExiting) {
+                               spin_unlock(&server->req_lock);
+                               return -ENOENT;
+                       }
+
+                       scredits = server->credits;
+                       /* can deadlock with reopen */
+                       if (scredits == 1) {
+                               *num = SMB2_MAX_BUFFER_SIZE;
+                               *credits = 0;
+                               break;
+                       }
+
+                       /* leave one credit for a possible reopen */
+                       scredits--;
+                       *num = min_t(unsigned int, size,
+                                    scredits * SMB2_MAX_BUFFER_SIZE);
+
+                       *credits = DIV_ROUND_UP(*num, SMB2_MAX_BUFFER_SIZE);
+                       server->credits -= *credits;
+                       server->in_flight++;
+                       break;
+               }
+       }
+       spin_unlock(&server->req_lock);
+       return rc;
+}
+
 static __u64
 smb2_get_next_mid(struct TCP_Server_Info *server)
 {
@@ -182,8 +229,9 @@ smb2_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *volume_info)
        /* start with specified wsize, or default */
        wsize = volume_info->wsize ? volume_info->wsize : CIFS_DEFAULT_IOSIZE;
        wsize = min_t(unsigned int, wsize, server->max_write);
-       /* set it to the maximum buffer size value we can send with 1 credit */
-       wsize = min_t(unsigned int, wsize, SMB2_MAX_BUFFER_SIZE);
+
+       if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
+               wsize = min_t(unsigned int, wsize, SMB2_MAX_BUFFER_SIZE);
 
        return wsize;
 }
@@ -1120,6 +1168,7 @@ struct smb_version_operations smb20_operations = {
        .set_credits = smb2_set_credits,
        .get_credits_field = smb2_get_credits_field,
        .get_credits = smb2_get_credits,
+       .wait_mtu_credits = cifs_wait_mtu_credits,
        .get_next_mid = smb2_get_next_mid,
        .read_data_offset = smb2_read_data_offset,
        .read_data_length = smb2_read_data_length,
@@ -1196,6 +1245,7 @@ struct smb_version_operations smb21_operations = {
        .set_credits = smb2_set_credits,
        .get_credits_field = smb2_get_credits_field,
        .get_credits = smb2_get_credits,
+       .wait_mtu_credits = smb2_wait_mtu_credits,
        .get_next_mid = smb2_get_next_mid,
        .read_data_offset = smb2_read_data_offset,
        .read_data_length = smb2_read_data_length,
@@ -1272,6 +1322,7 @@ struct smb_version_operations smb30_operations = {
        .set_credits = smb2_set_credits,
        .get_credits_field = smb2_get_credits_field,
        .get_credits = smb2_get_credits,
+       .wait_mtu_credits = smb2_wait_mtu_credits,
        .get_next_mid = smb2_get_next_mid,
        .read_data_offset = smb2_read_data_offset,
        .read_data_length = smb2_read_data_length,
index da7aa62bf0618d827122b780fd45e1c68224ad63..be7b2eb55134d9aff70c3a08697f44891fd44e56 100644 (file)
@@ -108,7 +108,6 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ ,
        if (!tcon)
                goto out;
 
-       /* BB FIXME when we do write > 64K add +1 for every 64K in req or rsp */
        /* GLOBAL_CAP_LARGE_MTU will only be set if dialect > SMB2.02 */
        /* See sections 2.2.4 and 3.2.4.1.5 of MS-SMB2 */
        if ((tcon->ses) &&
@@ -1892,15 +1891,25 @@ int
 smb2_async_writev(struct cifs_writedata *wdata,
                  void (*release)(struct kref *kref))
 {
-       int rc = -EACCES;
+       int rc = -EACCES, flags = 0;
        struct smb2_write_req *req = NULL;
        struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink);
+       struct TCP_Server_Info *server = tcon->ses->server;
        struct kvec iov;
        struct smb_rqst rqst;
 
        rc = small_smb2_init(SMB2_WRITE, tcon, (void **) &req);
-       if (rc)
+       if (rc) {
+               if (rc == -EAGAIN && wdata->credits) {
+                       /* credits was reset by reconnect */
+                       wdata->credits = 0;
+                       /* reduce in_flight value since we won't send the req */
+                       spin_lock(&server->req_lock);
+                       server->in_flight--;
+                       spin_unlock(&server->req_lock);
+               }
                goto async_writev_out;
+       }
 
        req->hdr.ProcessId = cpu_to_le32(wdata->cfile->pid);
 
@@ -1933,9 +1942,20 @@ smb2_async_writev(struct cifs_writedata *wdata,
 
        inc_rfc1001_len(&req->hdr, wdata->bytes - 1 /* Buffer */);
 
+       if (wdata->credits) {
+               req->hdr.CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes,
+                                                   SMB2_MAX_BUFFER_SIZE));
+               spin_lock(&server->req_lock);
+               server->credits += wdata->credits -
+                                       le16_to_cpu(req->hdr.CreditCharge);
+               spin_unlock(&server->req_lock);
+               wake_up(&server->request_q);
+               flags = CIFS_HAS_CREDITS;
+       }
+
        kref_get(&wdata->refcount);
-       rc = cifs_call_async(tcon->ses->server, &rqst, NULL,
-                               smb2_writev_callback, wdata, 0);
+       rc = cifs_call_async(server, &rqst, NULL, smb2_writev_callback, wdata,
+                            flags);
 
        if (rc) {
                kref_put(&wdata->refcount, release);
index 59c748ce872f9bb190bf683c8fd49a38926e3b4d..5111e7272db62e718fcb3d968af48083d598bccf 100644 (file)
@@ -466,7 +466,12 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
 static inline void
 smb2_seq_num_into_buf(struct TCP_Server_Info *server, struct smb2_hdr *hdr)
 {
+       unsigned int i, num = le16_to_cpu(hdr->CreditCharge);
+
        hdr->MessageId = get_next_mid64(server);
+       /* skip message numbers according to CreditCharge field */
+       for (i = 1; i < num; i++)
+               get_next_mid(server);
 }
 
 static struct mid_q_entry *
index 18cd5650a5fc6106394691f6ee5baf37f5d8d49b..9d087f4e7d4e3de1df68ad2f2d6e57e79d767b70 100644 (file)
@@ -448,6 +448,15 @@ wait_for_free_request(struct TCP_Server_Info *server, const int timeout,
        return wait_for_free_credits(server, timeout, val);
 }
 
+int
+cifs_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size,
+                     unsigned int *num, unsigned int *credits)
+{
+       *num = size;
+       *credits = 0;
+       return 0;
+}
+
 static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf,
                        struct mid_q_entry **ppmidQ)
 {
@@ -531,20 +540,23 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst,
 {
        int rc, timeout, optype;
        struct mid_q_entry *mid;
+       unsigned int credits = 0;
 
        timeout = flags & CIFS_TIMEOUT_MASK;
        optype = flags & CIFS_OP_MASK;
 
-       rc = wait_for_free_request(server, timeout, optype);
-       if (rc)
-               return rc;
+       if ((flags & CIFS_HAS_CREDITS) == 0) {
+               rc = wait_for_free_request(server, timeout, optype);
+               if (rc)
+                       return rc;
+               credits = 1;
+       }
 
        mutex_lock(&server->srv_mutex);
        mid = server->ops->setup_async_request(server, rqst);
        if (IS_ERR(mid)) {
                mutex_unlock(&server->srv_mutex);
-               add_credits(server, 1, optype);
-               wake_up(&server->request_q);
+               add_credits_and_wake_if(server, credits, optype);
                return PTR_ERR(mid);
        }
 
@@ -572,8 +584,7 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst,
                return 0;
 
        cifs_delete_mid(mid);
-       add_credits(server, 1, optype);
-       wake_up(&server->request_q);
+       add_credits_and_wake_if(server, credits, optype);
        return rc;
 }