aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/storage/protocol.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/storage/protocol.c')
-rw-r--r--drivers/usb/storage/protocol.c81
1 files changed, 27 insertions, 54 deletions
diff --git a/drivers/usb/storage/protocol.c b/drivers/usb/storage/protocol.c
index 5dfb4c36a1b0..12e3c2fac642 100644
--- a/drivers/usb/storage/protocol.c
+++ b/drivers/usb/storage/protocol.c
@@ -135,69 +135,42 @@ unsigned int usb_stor_access_xfer_buf(unsigned char *buffer,
135 unsigned int buflen, struct scsi_cmnd *srb, struct scatterlist **sgptr, 135 unsigned int buflen, struct scsi_cmnd *srb, struct scatterlist **sgptr,
136 unsigned int *offset, enum xfer_buf_dir dir) 136 unsigned int *offset, enum xfer_buf_dir dir)
137{ 137{
138 unsigned int cnt; 138 unsigned int cnt = 0;
139 struct scatterlist *sg = *sgptr; 139 struct scatterlist *sg = *sgptr;
140 struct sg_mapping_iter miter;
141 unsigned int nents = scsi_sg_count(srb);
140 142
141 /* We have to go through the list one entry 143 if (sg)
142 * at a time. Each s-g entry contains some number of pages, and 144 nents = sg_nents(sg);
143 * each page has to be kmap()'ed separately. If the page is already 145 else
144 * in kernel-addressable memory then kmap() will return its address.
145 * If the page is not directly accessible -- such as a user buffer
146 * located in high memory -- then kmap() will map it to a temporary
147 * position in the kernel's virtual address space.
148 */
149
150 if (!sg)
151 sg = scsi_sglist(srb); 146 sg = scsi_sglist(srb);
152 147
153 /* This loop handles a single s-g list entry, which may 148 sg_miter_start(&miter, sg, nents, dir == FROM_XFER_BUF ?
154 * include multiple pages. Find the initial page structure 149 SG_MITER_FROM_SG: SG_MITER_TO_SG);
155 * and the starting offset within the page, and update
156 * the *offset and **sgptr values for the next loop.
157 */
158 cnt = 0;
159 while (cnt < buflen && sg) {
160 struct page *page = sg_page(sg) +
161 ((sg->offset + *offset) >> PAGE_SHIFT);
162 unsigned int poff = (sg->offset + *offset) & (PAGE_SIZE-1);
163 unsigned int sglen = sg->length - *offset;
164
165 if (sglen > buflen - cnt) {
166
167 /* Transfer ends within this s-g entry */
168 sglen = buflen - cnt;
169 *offset += sglen;
170 } else {
171 150
172 /* Transfer continues to next s-g entry */ 151 if (!sg_miter_skip(&miter, *offset))
173 *offset = 0; 152 return cnt;
174 sg = sg_next(sg); 153
175 } 154 while (sg_miter_next(&miter) && cnt < buflen) {
155 unsigned int len = min_t(unsigned int, miter.length,
156 buflen - cnt);
157
158 if (dir == FROM_XFER_BUF)
159 memcpy(buffer + cnt, miter.addr, len);
160 else
161 memcpy(miter.addr, buffer + cnt, len);
176 162
177 /* Transfer the data for all the pages in this 163 if (*offset + len < miter.piter.sg->length) {
178 * s-g entry. For each page: call kmap(), do the 164 *offset += len;
179 * transfer, and call kunmap() immediately after. */ 165 *sgptr = miter.piter.sg;
180 while (sglen > 0) { 166 } else {
181 unsigned int plen = min(sglen, (unsigned int) 167 *offset = 0;
182 PAGE_SIZE - poff); 168 *sgptr = sg_next(miter.piter.sg);
183 unsigned char *ptr = kmap(page);
184
185 if (dir == TO_XFER_BUF)
186 memcpy(ptr + poff, buffer + cnt, plen);
187 else
188 memcpy(buffer + cnt, ptr + poff, plen);
189 kunmap(page);
190
191 /* Start at the beginning of the next page */
192 poff = 0;
193 ++page;
194 cnt += plen;
195 sglen -= plen;
196 } 169 }
170 cnt += len;
197 } 171 }
198 *sgptr = sg; 172 sg_miter_stop(&miter);
199 173
200 /* Return the amount actually transferred */
201 return cnt; 174 return cnt;
202} 175}
203EXPORT_SYMBOL_GPL(usb_stor_access_xfer_buf); 176EXPORT_SYMBOL_GPL(usb_stor_access_xfer_buf);