diff options
Diffstat (limited to 'drivers/scsi/sg.c')
-rw-r--r-- | drivers/scsi/sg.c | 589 |
1 files changed, 427 insertions, 162 deletions
diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index 05fc98833a37..9360e57b0468 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -47,6 +47,7 @@ static int sg_version_num = 30534; /* 2 digits for each component */ #include <linux/seq_file.h> #include <linux/blkdev.h> #include <linux/delay.h> +#include <linux/scatterlist.h> #include <linux/blktrace_api.h> #include <linux/smp_lock.h> @@ -118,8 +119,7 @@ typedef struct sg_scatter_hold { /* holding area for scsi scatter gather info */ unsigned sglist_len; /* size of malloc'd scatter-gather list ++ */ unsigned bufflen; /* Size of (aggregate) data buffer */ unsigned b_malloc_len; /* actual len malloc'ed in buffer */ - struct page **pages; - int page_order; + struct scatterlist *buffer;/* scatter list */ char dio_in_use; /* 0->indirect IO (or mmap), 1->dio */ unsigned char cmd_opcode; /* first byte of command */ } Sg_scatter_hold; @@ -137,8 +137,6 @@ typedef struct sg_request { /* SG_MAX_QUEUE requests outstanding per file */ char orphan; /* 1 -> drop on sight, 0 -> normal */ char sg_io_owned; /* 1 -> packet belongs to SG_IO */ volatile char done; /* 0->before bh, 1->before read, 2->read */ - struct request *rq; - struct bio *bio; } Sg_request; typedef struct sg_fd { /* holds the state of a file descriptor */ @@ -178,7 +176,7 @@ typedef struct sg_device { /* holds the state of each scsi generic device */ static int sg_fasync(int fd, struct file *filp, int mode); /* tasklet or soft irq callback */ static void sg_cmd_done(void *data, char *sense, int result, int resid); -static int sg_start_req(Sg_request *srp, unsigned char *cmd); +static int sg_start_req(Sg_request * srp); static void sg_finish_rem_req(Sg_request * srp); static int sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size); static int sg_build_sgat(Sg_scatter_hold * schp, const Sg_fd * sfp, @@ -190,6 +188,8 @@ static ssize_t sg_new_write(Sg_fd *sfp, struct file *file, int read_only, Sg_request **o_srp); static int sg_common_write(Sg_fd * sfp, Sg_request * srp, unsigned char *cmnd, int timeout, int blocking); +static int sg_u_iovec(sg_io_hdr_t * hp, int sg_num, int ind, + int wr_xf, int *countp, unsigned char __user **up); static int sg_write_xfer(Sg_request * srp); static int sg_read_xfer(Sg_request * srp); static int sg_read_oxfer(Sg_request * srp, char __user *outp, int num_read_xfer); @@ -197,6 +197,8 @@ static void sg_remove_scat(Sg_scatter_hold * schp); static void sg_build_reserve(Sg_fd * sfp, int req_size); static void sg_link_reserve(Sg_fd * sfp, Sg_request * srp, int size); static void sg_unlink_reserve(Sg_fd * sfp, Sg_request * srp); +static struct page *sg_page_malloc(int rqSz, int lowDma, int *retSzp); +static void sg_page_free(struct page *page, int size); static Sg_fd *sg_add_sfp(Sg_device * sdp, int dev); static int sg_remove_sfp(Sg_device * sdp, Sg_fd * sfp); static void __sg_remove_sfp(Sg_device * sdp, Sg_fd * sfp); @@ -227,11 +229,6 @@ static int sg_allow_access(struct file *filp, unsigned char *cmd) cmd, filp->f_mode & FMODE_WRITE); } -static void sg_rq_end_io(struct request *rq, int uptodate) -{ - sg_cmd_done(rq->end_io_data, rq->sense, rq->errors, rq->data_len); -} - static int sg_open(struct inode *inode, struct file *filp) { @@ -735,8 +732,7 @@ sg_common_write(Sg_fd * sfp, Sg_request * srp, SCSI_LOG_TIMEOUT(4, printk("sg_common_write: scsi opcode=0x%02x, cmd_size=%d\n", (int) cmnd[0], (int) hp->cmd_len)); - k = sg_start_req(srp, cmnd); - if (k) { + if ((k = sg_start_req(srp))) { SCSI_LOG_TIMEOUT(1, printk("sg_common_write: start_req err=%d\n", k)); sg_finish_rem_req(srp); return k; /* probably out of space --> ENOMEM */ @@ -767,11 +763,20 @@ sg_common_write(Sg_fd * sfp, Sg_request * srp, break; } hp->duration = jiffies_to_msecs(jiffies); - - srp->rq->timeout = timeout; - blk_execute_rq_nowait(sdp->device->request_queue, sdp->disk, - srp->rq, 1, sg_rq_end_io); - return 0; +/* Now send everything of to mid-level. The next time we hear about this + packet is when sg_cmd_done() is called (i.e. a callback). */ + if (scsi_execute_async(sdp->device, cmnd, hp->cmd_len, data_dir, srp->data.buffer, + hp->dxfer_len, srp->data.k_use_sg, timeout, + SG_DEFAULT_RETRIES, srp, sg_cmd_done, + GFP_ATOMIC)) { + SCSI_LOG_TIMEOUT(1, printk("sg_common_write: scsi_execute_async failed\n")); + /* + * most likely out of mem, but could also be a bad map + */ + sg_finish_rem_req(srp); + return -ENOMEM; + } else + return 0; } static int @@ -1187,7 +1192,8 @@ sg_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) Sg_fd *sfp; unsigned long offset, len, sa; Sg_scatter_hold *rsv_schp; - int k, length; + struct scatterlist *sg; + int k; if ((NULL == vma) || (!(sfp = (Sg_fd *) vma->vm_private_data))) return VM_FAULT_SIGBUS; @@ -1197,14 +1203,15 @@ sg_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) return VM_FAULT_SIGBUS; SCSI_LOG_TIMEOUT(3, printk("sg_vma_fault: offset=%lu, scatg=%d\n", offset, rsv_schp->k_use_sg)); + sg = rsv_schp->buffer; sa = vma->vm_start; - length = 1 << (PAGE_SHIFT + rsv_schp->page_order); - for (k = 0; k < rsv_schp->k_use_sg && sa < vma->vm_end; k++) { + for (k = 0; (k < rsv_schp->k_use_sg) && (sa < vma->vm_end); + ++k, sg = sg_next(sg)) { len = vma->vm_end - sa; - len = (len < length) ? len : length; + len = (len < sg->length) ? len : sg->length; if (offset < len) { - struct page *page = nth_page(rsv_schp->pages[k], - offset >> PAGE_SHIFT); + struct page *page; + page = virt_to_page(page_address(sg_page(sg)) + offset); get_page(page); /* increment page count */ vmf->page = page; return 0; /* success */ @@ -1226,7 +1233,8 @@ sg_mmap(struct file *filp, struct vm_area_struct *vma) Sg_fd *sfp; unsigned long req_sz, len, sa; Sg_scatter_hold *rsv_schp; - int k, length; + int k; + struct scatterlist *sg; if ((!filp) || (!vma) || (!(sfp = (Sg_fd *) filp->private_data))) return -ENXIO; @@ -1240,10 +1248,11 @@ sg_mmap(struct file *filp, struct vm_area_struct *vma) return -ENOMEM; /* cannot map more than reserved buffer */ sa = vma->vm_start; - length = 1 << (PAGE_SHIFT + rsv_schp->page_order); - for (k = 0; k < rsv_schp->k_use_sg && sa < vma->vm_end; k++) { + sg = rsv_schp->buffer; + for (k = 0; (k < rsv_schp->k_use_sg) && (sa < vma->vm_end); + ++k, sg = sg_next(sg)) { len = vma->vm_end - sa; - len = (len < length) ? len : length; + len = (len < sg->length) ? len : sg->length; sa += len; } @@ -1287,6 +1296,7 @@ sg_cmd_done(void *data, char *sense, int result, int resid) if (0 != result) { struct scsi_sense_hdr sshdr; + memcpy(srp->sense_b, sense, sizeof (srp->sense_b)); srp->header.status = 0xff & result; srp->header.masked_status = status_byte(result); srp->header.msg_status = msg_byte(result); @@ -1622,32 +1632,8 @@ exit_sg(void) idr_destroy(&sg_index_idr); } -static int __sg_start_req(struct sg_request *srp, struct sg_io_hdr *hp, - unsigned char *cmd) -{ - struct sg_fd *sfp = srp->parentfp; - struct request_queue *q = sfp->parentdp->device->request_queue; - struct request *rq; - int rw = hp->dxfer_direction == SG_DXFER_TO_DEV ? WRITE : READ; - - rq = blk_get_request(q, rw, GFP_ATOMIC); - if (!rq) - return -ENOMEM; - - memcpy(rq->cmd, cmd, hp->cmd_len); - - rq->cmd_len = hp->cmd_len; - rq->cmd_type = REQ_TYPE_BLOCK_PC; - - srp->rq = rq; - rq->end_io_data = srp; - rq->sense = srp->sense_b; - rq->retries = SG_DEFAULT_RETRIES; - - return 0; -} - -static int sg_start_req(Sg_request *srp, unsigned char *cmd) +static int +sg_start_req(Sg_request * srp) { int res; Sg_fd *sfp = srp->parentfp; @@ -1656,52 +1642,27 @@ static int sg_start_req(Sg_request *srp, unsigned char *cmd) int dxfer_dir = hp->dxfer_direction; Sg_scatter_hold *req_schp = &srp->data; Sg_scatter_hold *rsv_schp = &sfp->reserve; - struct request_queue *q = sfp->parentdp->device->request_queue; - struct rq_map_data map_data; SCSI_LOG_TIMEOUT(4, printk("sg_start_req: dxfer_len=%d\n", dxfer_len)); - - res = __sg_start_req(srp, hp, cmd); - if (res) - return res; - if ((dxfer_len <= 0) || (dxfer_dir == SG_DXFER_NONE)) return 0; - -#ifdef SG_ALLOW_DIO_CODE if (sg_allow_dio && (hp->flags & SG_FLAG_DIRECT_IO) && (dxfer_dir != SG_DXFER_UNKNOWN) && (0 == hp->iovec_count) && - (!sfp->parentdp->device->host->unchecked_isa_dma) && - blk_rq_aligned(q, hp->dxferp, dxfer_len)) - return sg_build_direct(srp, sfp, dxfer_len); -#endif + (!sfp->parentdp->device->host->unchecked_isa_dma)) { + res = sg_build_direct(srp, sfp, dxfer_len); + if (res <= 0) /* -ve -> error, 0 -> done, 1 -> try indirect */ + return res; + } if ((!sg_res_in_use(sfp)) && (dxfer_len <= rsv_schp->bufflen)) sg_link_reserve(sfp, srp, dxfer_len); - else + else { res = sg_build_indirect(req_schp, sfp, dxfer_len); - - if (!res) { - struct request *rq = srp->rq; - Sg_scatter_hold *schp = &srp->data; - int iovec_count = (int) hp->iovec_count; - - map_data.pages = schp->pages; - map_data.page_order = schp->page_order; - map_data.nr_entries = schp->k_use_sg; - - if (iovec_count) - res = blk_rq_map_user_iov(q, rq, &map_data, hp->dxferp, - iovec_count, - hp->dxfer_len, GFP_ATOMIC); - else - res = blk_rq_map_user(q, rq, &map_data, hp->dxferp, - hp->dxfer_len, GFP_ATOMIC); - - if (!res) - srp->bio = rq->bio; + if (res) { + sg_remove_scat(req_schp); + return res; + } } - - return res; + return 0; } static void @@ -1715,56 +1676,186 @@ sg_finish_rem_req(Sg_request * srp) sg_unlink_reserve(sfp, srp); else sg_remove_scat(req_schp); - - if (srp->rq) { - if (srp->bio) - blk_rq_unmap_user(srp->bio); - - blk_put_request(srp->rq); - } - sg_remove_request(sfp, srp); } static int sg_build_sgat(Sg_scatter_hold * schp, const Sg_fd * sfp, int tablesize) { - int sg_bufflen = tablesize * sizeof(struct page *); + int sg_bufflen = tablesize * sizeof(struct scatterlist); gfp_t gfp_flags = GFP_ATOMIC | __GFP_NOWARN; - schp->pages = kzalloc(sg_bufflen, gfp_flags); - if (!schp->pages) + /* + * TODO: test without low_dma, we should not need it since + * the block layer will bounce the buffer for us + * + * XXX(hch): we shouldn't need GFP_DMA for the actual S/G list. + */ + if (sfp->low_dma) + gfp_flags |= GFP_DMA; + schp->buffer = kzalloc(sg_bufflen, gfp_flags); + if (!schp->buffer) return -ENOMEM; + sg_init_table(schp->buffer, tablesize); schp->sglist_len = sg_bufflen; return tablesize; /* number of scat_gath elements allocated */ } +#ifdef SG_ALLOW_DIO_CODE +/* vvvvvvvv following code borrowed from st driver's direct IO vvvvvvvvv */ + /* TODO: hopefully we can use the generic block layer code */ + +/* Pin down user pages and put them into a scatter gather list. Returns <= 0 if + - mapping of all pages not successful + (i.e., either completely successful or fails) +*/ +static int +st_map_user_pages(struct scatterlist *sgl, const unsigned int max_pages, + unsigned long uaddr, size_t count, int rw) +{ + unsigned long end = (uaddr + count + PAGE_SIZE - 1) >> PAGE_SHIFT; + unsigned long start = uaddr >> PAGE_SHIFT; + const int nr_pages = end - start; + int res, i, j; + struct page **pages; + + /* User attempted Overflow! */ + if ((uaddr + count) < uaddr) + return -EINVAL; + + /* Too big */ + if (nr_pages > max_pages) + return -ENOMEM; + + /* Hmm? */ + if (count == 0) + return 0; + + if ((pages = kmalloc(max_pages * sizeof(*pages), GFP_ATOMIC)) == NULL) + return -ENOMEM; + + /* Try to fault in all of the necessary pages */ + down_read(¤t->mm->mmap_sem); + /* rw==READ means read from drive, write into memory area */ + res = get_user_pages( + current, + current->mm, + uaddr, + nr_pages, + rw == READ, + 0, /* don't force */ + pages, + NULL); + up_read(¤t->mm->mmap_sem); + + /* Errors and no page mapped should return here */ + if (res < nr_pages) + goto out_unmap; + + for (i=0; i < nr_pages; i++) { + /* FIXME: flush superflous for rw==READ, + * probably wrong function for rw==WRITE + */ + flush_dcache_page(pages[i]); + /* ?? Is locking needed? I don't think so */ + /* if (!trylock_page(pages[i])) + goto out_unlock; */ + } + + sg_set_page(sgl, pages[0], 0, uaddr & ~PAGE_MASK); + if (nr_pages > 1) { + sgl[0].length = PAGE_SIZE - sgl[0].offset; + count -= sgl[0].length; + for (i=1; i < nr_pages ; i++) + sg_set_page(&sgl[i], pages[i], count < PAGE_SIZE ? count : PAGE_SIZE, 0); + } + else { + sgl[0].length = count; + } + + kfree(pages); + return nr_pages; + + out_unmap: + if (res > 0) { + for (j=0; j < res; j++) + page_cache_release(pages[j]); + res = 0; + } + kfree(pages); + return res; +} + + +/* And unmap them... */ +static int +st_unmap_user_pages(struct scatterlist *sgl, const unsigned int nr_pages, + int dirtied) +{ + int i; + + for (i=0; i < nr_pages; i++) { + struct page *page = sg_page(&sgl[i]); + + if (dirtied) + SetPageDirty(page); + /* unlock_page(page); */ + /* FIXME: cache flush missing for rw==READ + * FIXME: call the correct reference counting function + */ + page_cache_release(page); + } + + return 0; +} + +/* ^^^^^^^^ above code borrowed from st driver's direct IO ^^^^^^^^^ */ +#endif + + /* Returns: -ve -> error, 0 -> done, 1 -> try indirect */ static int sg_build_direct(Sg_request * srp, Sg_fd * sfp, int dxfer_len) { +#ifdef SG_ALLOW_DIO_CODE sg_io_hdr_t *hp = &srp->header; Sg_scatter_hold *schp = &srp->data; - int res; - struct request *rq = srp->rq; - struct request_queue *q = sfp->parentdp->device->request_queue; + int sg_tablesize = sfp->parentdp->sg_tablesize; + int mx_sc_elems, res; + struct scsi_device *sdev = sfp->parentdp->device; - res = blk_rq_map_user(q, rq, NULL, hp->dxferp, dxfer_len, GFP_ATOMIC); - if (res) - return res; - srp->bio = rq->bio; + if (((unsigned long)hp->dxferp & + queue_dma_alignment(sdev->request_queue)) != 0) + return 1; + + mx_sc_elems = sg_build_sgat(schp, sfp, sg_tablesize); + if (mx_sc_elems <= 0) { + return 1; + } + res = st_map_user_pages(schp->buffer, mx_sc_elems, + (unsigned long)hp->dxferp, dxfer_len, + (SG_DXFER_TO_DEV == hp->dxfer_direction) ? 1 : 0); + if (res <= 0) { + sg_remove_scat(schp); + return 1; + } + schp->k_use_sg = res; schp->dio_in_use = 1; hp->info |= SG_INFO_DIRECT_IO; return 0; +#else + return 1; +#endif } static int sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size) { - int ret_sz = 0, i, k, rem_sz, num, mx_sc_elems; + struct scatterlist *sg; + int ret_sz = 0, k, rem_sz, num, mx_sc_elems; int sg_tablesize = sfp->parentdp->sg_tablesize; - int blk_size = buff_size, order; - gfp_t gfp_mask = GFP_ATOMIC | __GFP_COMP | __GFP_NOWARN; + int blk_size = buff_size; + struct page *p = NULL; if (blk_size < 0) return -EFAULT; @@ -1788,26 +1879,15 @@ sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size) } else scatter_elem_sz_prev = num; } - - if (sfp->low_dma) - gfp_mask |= GFP_DMA; - - if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO)) - gfp_mask |= __GFP_ZERO; - - order = get_order(num); -retry: - ret_sz = 1 << (PAGE_SHIFT + order); - - for (k = 0, rem_sz = blk_size; rem_sz > 0 && k < mx_sc_elems; - k++, rem_sz -= ret_sz) { - + for (k = 0, sg = schp->buffer, rem_sz = blk_size; + (rem_sz > 0) && (k < mx_sc_elems); + ++k, rem_sz -= ret_sz, sg = sg_next(sg)) { + num = (rem_sz > scatter_elem_sz_prev) ? - scatter_elem_sz_prev : rem_sz; - - schp->pages[k] = alloc_pages(gfp_mask, order); - if (!schp->pages[k]) - goto out; + scatter_elem_sz_prev : rem_sz; + p = sg_page_malloc(num, sfp->low_dma, &ret_sz); + if (!p) + return -ENOMEM; if (num == scatter_elem_sz_prev) { if (unlikely(ret_sz > scatter_elem_sz_prev)) { @@ -1815,12 +1895,12 @@ retry: scatter_elem_sz_prev = ret_sz; } } + sg_set_page(sg, p, (ret_sz > num) ? num : ret_sz, 0); SCSI_LOG_TIMEOUT(5, printk("sg_build_indirect: k=%d, num=%d, " "ret_sz=%d\n", k, num, ret_sz)); } /* end of for loop */ - schp->page_order = order; schp->k_use_sg = k; SCSI_LOG_TIMEOUT(5, printk("sg_build_indirect: k_use_sg=%d, " "rem_sz=%d\n", k, rem_sz)); @@ -1828,15 +1908,8 @@ retry: schp->bufflen = blk_size; if (rem_sz > 0) /* must have failed */ return -ENOMEM; - return 0; -out: - for (i = 0; i < k; i++) - __free_pages(schp->pages[k], order); - - if (--order >= 0) - goto retry; - return -ENOMEM; + return 0; } static int @@ -1844,8 +1917,13 @@ sg_write_xfer(Sg_request * srp) { sg_io_hdr_t *hp = &srp->header; Sg_scatter_hold *schp = &srp->data; + struct scatterlist *sg = schp->buffer; int num_xfer = 0; + int j, k, onum, usglen, ksglen, res; + int iovec_count = (int) hp->iovec_count; int dxfer_dir = hp->dxfer_direction; + unsigned char *p; + unsigned char __user *up; int new_interface = ('\0' == hp->interface_id) ? 0 : 1; if ((SG_DXFER_UNKNOWN == dxfer_dir) || (SG_DXFER_TO_DEV == dxfer_dir) || @@ -1859,9 +1937,83 @@ sg_write_xfer(Sg_request * srp) && ((SG_FLAG_NO_DXFER | SG_FLAG_MMAP_IO) & hp->flags))) return 0; - SCSI_LOG_TIMEOUT(4, printk("sg_write_xfer: num_xfer=%d, k_use_sg=%d\n", - num_xfer, schp->k_use_sg)); + SCSI_LOG_TIMEOUT(4, printk("sg_write_xfer: num_xfer=%d, iovec_count=%d, k_use_sg=%d\n", + num_xfer, iovec_count, schp->k_use_sg)); + if (iovec_count) { + onum = iovec_count; + if (!access_ok(VERIFY_READ, hp->dxferp, SZ_SG_IOVEC * onum)) + return -EFAULT; + } else + onum = 1; + + ksglen = sg->length; + p = page_address(sg_page(sg)); + for (j = 0, k = 0; j < onum; ++j) { + res = sg_u_iovec(hp, iovec_count, j, 1, &usglen, &up); + if (res) + return res; + + for (; p; sg = sg_next(sg), ksglen = sg->length, + p = page_address(sg_page(sg))) { + if (usglen <= 0) + break; + if (ksglen > usglen) { + if (usglen >= num_xfer) { + if (__copy_from_user(p, up, num_xfer)) + return -EFAULT; + return 0; + } + if (__copy_from_user(p, up, usglen)) + return -EFAULT; + p += usglen; + ksglen -= usglen; + break; + } else { + if (ksglen >= num_xfer) { + if (__copy_from_user(p, up, num_xfer)) + return -EFAULT; + return 0; + } + if (__copy_from_user(p, up, ksglen)) + return -EFAULT; + up += ksglen; + usglen -= ksglen; + } + ++k; + if (k >= schp->k_use_sg) + return 0; + } + } + + return 0; +} +static int +sg_u_iovec(sg_io_hdr_t * hp, int sg_num, int ind, + int wr_xf, int *countp, unsigned char __user **up) +{ + int num_xfer = (int) hp->dxfer_len; + unsigned char __user *p = hp->dxferp; + int count; + + if (0 == sg_num) { + if (wr_xf && ('\0' == hp->interface_id)) + count = (int) hp->flags; /* holds "old" input_size */ + else + count = num_xfer; + } else { + sg_iovec_t iovec; + if (__copy_from_user(&iovec, p + ind*SZ_SG_IOVEC, SZ_SG_IOVEC)) + return -EFAULT; + p = iovec.iov_base; + count = (int) iovec.iov_len; + } + if (!access_ok(wr_xf ? VERIFY_READ : VERIFY_WRITE, p, count)) + return -EFAULT; + if (up) + *up = p; + if (countp) + *countp = count; return 0; } @@ -1869,19 +2021,25 @@ static void sg_remove_scat(Sg_scatter_hold * schp) { SCSI_LOG_TIMEOUT(4, printk("sg_remove_scat: k_use_sg=%d\n", schp->k_use_sg)); - if (schp->pages && schp->sglist_len > 0) { - if (!schp->dio_in_use) { + if (schp->buffer && (schp->sglist_len > 0)) { + struct scatterlist *sg = schp->buffer; + + if (schp->dio_in_use) { +#ifdef SG_ALLOW_DIO_CODE + st_unmap_user_pages(sg, schp->k_use_sg, TRUE); +#endif + } else { int k; - for (k = 0; k < schp->k_use_sg && schp->pages[k]; k++) { + for (k = 0; (k < schp->k_use_sg) && sg_page(sg); + ++k, sg = sg_next(sg)) { SCSI_LOG_TIMEOUT(5, printk( - "sg_remove_scat: k=%d, pg=0x%p\n", - k, schp->pages[k])); - __free_pages(schp->pages[k], schp->page_order); + "sg_remove_scat: k=%d, pg=0x%p, len=%d\n", + k, sg_page(sg), sg->length)); + sg_page_free(sg_page(sg), sg->length); } - - kfree(schp->pages); } + kfree(schp->buffer); } memset(schp, 0, sizeof (*schp)); } @@ -1891,8 +2049,13 @@ sg_read_xfer(Sg_request * srp) { sg_io_hdr_t *hp = &srp->header; Sg_scatter_hold *schp = &srp->data; + struct scatterlist *sg = schp->buffer; int num_xfer = 0; + int j, k, onum, usglen, ksglen, res; + int iovec_count = (int) hp->iovec_count; int dxfer_dir = hp->dxfer_direction; + unsigned char *p; + unsigned char __user *up; int new_interface = ('\0' == hp->interface_id) ? 0 : 1; if ((SG_DXFER_UNKNOWN == dxfer_dir) || (SG_DXFER_FROM_DEV == dxfer_dir) @@ -1907,7 +2070,53 @@ sg_read_xfer(Sg_request * srp) return 0; SCSI_LOG_TIMEOUT(4, printk("sg_read_xfer: num_xfer=%d, iovec_count=%d, k_use_sg=%d\n", - num_xfer, (int)hp->iovec_count, schp->k_use_sg)); + num_xfer, iovec_count, schp->k_use_sg)); + if (iovec_count) { + onum = iovec_count; + if (!access_ok(VERIFY_READ, hp->dxferp, SZ_SG_IOVEC * onum)) + return -EFAULT; + } else + onum = 1; + + p = page_address(sg_page(sg)); + ksglen = sg->length; + for (j = 0, k = 0; j < onum; ++j) { + res = sg_u_iovec(hp, iovec_count, j, 0, &usglen, &up); + if (res) + return res; + + for (; p; sg = sg_next(sg), ksglen = sg->length, + p = page_address(sg_page(sg))) { + if (usglen <= 0) + break; + if (ksglen > usglen) { + if (usglen >= num_xfer) { + if (__copy_to_user(up, p, num_xfer)) + return -EFAULT; + return 0; + } + if (__copy_to_user(up, p, usglen)) + return -EFAULT; + p += usglen; + ksglen -= usglen; + break; + } else { + if (ksglen >= num_xfer) { + if (__copy_to_user(up, p, num_xfer)) + return -EFAULT; + return 0; + } + if (__copy_to_user(up, p, ksglen)) + return -EFAULT; + up += ksglen; + usglen -= ksglen; + } + ++k; + if (k >= schp->k_use_sg) + return 0; + } + } + return 0; } @@ -1915,6 +2124,7 @@ static int sg_read_oxfer(Sg_request * srp, char __user *outp, int num_read_xfer) { Sg_scatter_hold *schp = &srp->data; + struct scatterlist *sg = schp->buffer; int k, num; SCSI_LOG_TIMEOUT(4, printk("sg_read_oxfer: num_read_xfer=%d\n", @@ -1922,18 +2132,15 @@ sg_read_oxfer(Sg_request * srp, char __user *outp, int num_read_xfer) if ((!outp) || (num_read_xfer <= 0)) return 0; - blk_rq_unmap_user(srp->bio); - srp->bio = NULL; - - num = 1 << (PAGE_SHIFT + schp->page_order); - for (k = 0; k < schp->k_use_sg && schp->pages[k]; k++) { + for (k = 0; (k < schp->k_use_sg) && sg_page(sg); ++k, sg = sg_next(sg)) { + num = sg->length; if (num > num_read_xfer) { - if (__copy_to_user(outp, page_address(schp->pages[k]), + if (__copy_to_user(outp, page_address(sg_page(sg)), num_read_xfer)) return -EFAULT; break; } else { - if (__copy_to_user(outp, page_address(schp->pages[k]), + if (__copy_to_user(outp, page_address(sg_page(sg)), num)) return -EFAULT; num_read_xfer -= num; @@ -1968,22 +2175,24 @@ sg_link_reserve(Sg_fd * sfp, Sg_request * srp, int size) { Sg_scatter_hold *req_schp = &srp->data; Sg_scatter_hold *rsv_schp = &sfp->reserve; + struct scatterlist *sg = rsv_schp->buffer; int k, num, rem; srp->res_used = 1; SCSI_LOG_TIMEOUT(4, printk("sg_link_reserve: size=%d\n", size)); rem = size; - num = 1 << (PAGE_SHIFT + rsv_schp->page_order); - for (k = 0; k < rsv_schp->k_use_sg; k++) { + for (k = 0; k < rsv_schp->k_use_sg; ++k, sg = sg_next(sg)) { + num = sg->length; if (rem <= num) { + sfp->save_scat_len = num; + sg->length = rem; req_schp->k_use_sg = k + 1; req_schp->sglist_len = rsv_schp->sglist_len; - req_schp->pages = rsv_schp->pages; + req_schp->buffer = rsv_schp->buffer; req_schp->bufflen = size; req_schp->b_malloc_len = rsv_schp->b_malloc_len; - req_schp->page_order = rsv_schp->page_order; break; } else rem -= num; @@ -1997,13 +2206,22 @@ static void sg_unlink_reserve(Sg_fd * sfp, Sg_request * srp) { Sg_scatter_hold *req_schp = &srp->data; + Sg_scatter_hold *rsv_schp = &sfp->reserve; SCSI_LOG_TIMEOUT(4, printk("sg_unlink_reserve: req->k_use_sg=%d\n", (int) req_schp->k_use_sg)); + if ((rsv_schp->k_use_sg > 0) && (req_schp->k_use_sg > 0)) { + struct scatterlist *sg = rsv_schp->buffer; + + if (sfp->save_scat_len > 0) + (sg + (req_schp->k_use_sg - 1))->length = + (unsigned) sfp->save_scat_len; + else + SCSI_LOG_TIMEOUT(1, printk ("sg_unlink_reserve: BAD save_scat_len\n")); + } req_schp->k_use_sg = 0; req_schp->bufflen = 0; - req_schp->pages = NULL; - req_schp->page_order = 0; + req_schp->buffer = NULL; req_schp->sglist_len = 0; sfp->save_scat_len = 0; srp->res_used = 0; @@ -2261,6 +2479,53 @@ sg_res_in_use(Sg_fd * sfp) return srp ? 1 : 0; } +/* The size fetched (value output via retSzp) set when non-NULL return */ +static struct page * +sg_page_malloc(int rqSz, int lowDma, int *retSzp) +{ + struct page *resp = NULL; + gfp_t page_mask; + int order, a_size; + int resSz; + + if ((rqSz <= 0) || (NULL == retSzp)) + return resp; + + if (lowDma) + page_mask = GFP_ATOMIC | GFP_DMA | __GFP_COMP | __GFP_NOWARN; + else + page_mask = GFP_ATOMIC | __GFP_COMP | __GFP_NOWARN; + + for (order = 0, a_size = PAGE_SIZE; a_size < rqSz; + order++, a_size <<= 1) ; + resSz = a_size; /* rounded up if necessary */ + resp = alloc_pages(page_mask, order); + while ((!resp) && order) { + --order; + a_size >>= 1; /* divide by 2, until PAGE_SIZE */ + resp = alloc_pages(page_mask, order); /* try half */ + resSz = a_size; + } + if (resp) { + if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO)) + memset(page_address(resp), 0, resSz); + *retSzp = resSz; + } + return resp; +} + +static void +sg_page_free(struct page *page, int size) +{ + int order, a_size; + + if (!page) + return; + for (order = 0, a_size = PAGE_SIZE; a_size < size; + order++, a_size <<= 1) ; + __free_pages(page, order); +} + #ifdef CONFIG_SCSI_PROC_FS static int sg_idr_max_id(int id, void *p, void *data) |