Commit cb7e9eab authored by Pavel Shilovsky's avatar Pavel Shilovsky Committed by Steve French
Browse files

CIFS: Use multicredits for SMB 2.1/3 writes



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: default avatarShirish Pargaonkar <spargaonkar@suse.com>
Signed-off-by: default avatarPavel Shilovsky <pshilovsky@samba.org>
Signed-off-by: default avatarSteve French <smfrench@gmail.com>
parent 6ec0b01b
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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
+3 −0
Original line number Diff line number Diff line
@@ -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);
+30 −6
Original line number Diff line number Diff line
@@ -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) {
+1 −0
Original line number Diff line number Diff line
@@ -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,
+53 −2
Original line number Diff line number Diff line
@@ -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,7 +229,8 @@ 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 */

	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,
Loading