| 1 | /* $NetBSD: dtv_scatter.c,v 1.2 2014/08/09 13:34:10 jmcneill Exp $ */ |
| 2 | |
| 3 | /* |
| 4 | * Copyright (c) 2008 Patrick Mahoney <pat@polycrystal.org> |
| 5 | * All rights reserved. |
| 6 | * |
| 7 | * This code was written by Patrick Mahoney (pat@polycrystal.org) as |
| 8 | * part of Google Summer of Code 2008. |
| 9 | * |
| 10 | * Redistribution and use in source and binary forms, with or without |
| 11 | * modification, are permitted provided that the following conditions |
| 12 | * are met: |
| 13 | * 1. Redistributions of source code must retain the above copyright |
| 14 | * notice, this list of conditions and the following disclaimer. |
| 15 | * 2. Redistributions in binary form must reproduce the above copyright |
| 16 | * notice, this list of conditions and the following disclaimer in the |
| 17 | * documentation and/or other materials provided with the distribution. |
| 18 | * |
| 19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
| 20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
| 23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 29 | * POSSIBILITY OF SUCH DAMAGE. |
| 30 | */ |
| 31 | |
| 32 | #include <sys/cdefs.h> |
| 33 | __KERNEL_RCSID(0, "$NetBSD: dtv_scatter.c,v 1.2 2014/08/09 13:34:10 jmcneill Exp $" ); |
| 34 | |
| 35 | #include <sys/param.h> |
| 36 | #include <sys/ioctl.h> |
| 37 | #include <sys/fcntl.h> |
| 38 | #include <sys/vnode.h> |
| 39 | #include <sys/poll.h> |
| 40 | #include <sys/select.h> |
| 41 | #include <sys/kmem.h> |
| 42 | #include <sys/pool.h> |
| 43 | #include <sys/conf.h> |
| 44 | #include <sys/types.h> |
| 45 | #include <sys/device.h> |
| 46 | #include <sys/condvar.h> |
| 47 | #include <sys/queue.h> |
| 48 | |
| 49 | #include <dev/dtv/dtvvar.h> |
| 50 | |
| 51 | void |
| 52 | dtv_scatter_buf_init(struct dtv_scatter_buf *sb) |
| 53 | { |
| 54 | sb->sb_pool = pool_cache_init(PAGE_SIZE, 0, 0, 0, |
| 55 | "dtvscatter" , NULL, IPL_SCHED, |
| 56 | NULL, NULL, NULL); |
| 57 | sb->sb_size = 0; |
| 58 | sb->sb_npages = 0; |
| 59 | sb->sb_page_ary = NULL; |
| 60 | } |
| 61 | |
| 62 | void |
| 63 | dtv_scatter_buf_destroy(struct dtv_scatter_buf *sb) |
| 64 | { |
| 65 | /* Do we need to return everything to the pool first? */ |
| 66 | dtv_scatter_buf_set_size(sb, 0); |
| 67 | pool_cache_destroy(sb->sb_pool); |
| 68 | sb->sb_pool = 0; |
| 69 | sb->sb_npages = 0; |
| 70 | sb->sb_page_ary = NULL; |
| 71 | } |
| 72 | |
| 73 | /* Increase or decrease the size of the buffer */ |
| 74 | int |
| 75 | dtv_scatter_buf_set_size(struct dtv_scatter_buf *sb, size_t sz) |
| 76 | { |
| 77 | unsigned int i; |
| 78 | size_t npages, minpages, oldnpages; |
| 79 | uint8_t **old_ary; |
| 80 | |
| 81 | npages = (sz >> PAGE_SHIFT) + ((sz & PAGE_MASK) > 0); |
| 82 | |
| 83 | if (sb->sb_npages == npages) { |
| 84 | return 0; |
| 85 | } |
| 86 | |
| 87 | oldnpages = sb->sb_npages; |
| 88 | old_ary = sb->sb_page_ary; |
| 89 | |
| 90 | sb->sb_npages = npages; |
| 91 | if (npages > 0) { |
| 92 | sb->sb_page_ary = |
| 93 | kmem_alloc(sizeof(uint8_t *) * npages, KM_SLEEP); |
| 94 | if (sb->sb_page_ary == NULL) { |
| 95 | sb->sb_npages = oldnpages; |
| 96 | sb->sb_page_ary = old_ary; |
| 97 | return ENOMEM; |
| 98 | } |
| 99 | } else { |
| 100 | sb->sb_page_ary = NULL; |
| 101 | } |
| 102 | |
| 103 | minpages = min(npages, oldnpages); |
| 104 | /* copy any pages that will be reused */ |
| 105 | for (i = 0; i < minpages; ++i) |
| 106 | sb->sb_page_ary[i] = old_ary[i]; |
| 107 | /* allocate any new pages */ |
| 108 | for (; i < npages; ++i) { |
| 109 | sb->sb_page_ary[i] = pool_cache_get(sb->sb_pool, 0); |
| 110 | /* TODO: does pool_cache_get return NULL on |
| 111 | * ENOMEM? If so, we need to release or note |
| 112 | * the pages with did allocate |
| 113 | * successfully. */ |
| 114 | if (sb->sb_page_ary[i] == NULL) { |
| 115 | return ENOMEM; |
| 116 | } |
| 117 | } |
| 118 | /* return any pages no longer needed */ |
| 119 | for (; i < oldnpages; ++i) |
| 120 | pool_cache_put(sb->sb_pool, old_ary[i]); |
| 121 | |
| 122 | if (old_ary != NULL) |
| 123 | kmem_free(old_ary, sizeof(uint8_t *) * oldnpages); |
| 124 | |
| 125 | sb->sb_size = sb->sb_npages << PAGE_SHIFT; |
| 126 | |
| 127 | return 0; |
| 128 | } |
| 129 | |
| 130 | |
| 131 | paddr_t |
| 132 | dtv_scatter_buf_map(struct dtv_scatter_buf *sb, off_t off) |
| 133 | { |
| 134 | size_t pg; |
| 135 | paddr_t pa; |
| 136 | |
| 137 | pg = off >> PAGE_SHIFT; |
| 138 | |
| 139 | if (pg >= sb->sb_npages) |
| 140 | return -1; |
| 141 | else if (!pmap_extract(pmap_kernel(), (vaddr_t)sb->sb_page_ary[pg], &pa)) |
| 142 | return -1; |
| 143 | |
| 144 | return atop(pa); |
| 145 | } |
| 146 | |
| 147 | /* Initialize data for an io operation on a scatter buffer. Returns |
| 148 | * true if the transfer is valid, or false if out of range. */ |
| 149 | bool |
| 150 | dtv_scatter_io_init(struct dtv_scatter_buf *sb, |
| 151 | off_t off, size_t len, |
| 152 | struct dtv_scatter_io *sio) |
| 153 | { |
| 154 | if ((off + len) > sb->sb_size) { |
| 155 | printf("dtv: %s failed: off=%" PRId64 |
| 156 | " len=%zu sb->sb_size=%zu\n" , |
| 157 | __func__, off, len, sb->sb_size); |
| 158 | return false; |
| 159 | } |
| 160 | |
| 161 | sio->sio_buf = sb; |
| 162 | sio->sio_offset = off; |
| 163 | sio->sio_resid = len; |
| 164 | |
| 165 | return true; |
| 166 | } |
| 167 | |
| 168 | /* Store the pointer and size of the next contiguous segment. Returns |
| 169 | * true if the segment is valid, or false if all has been transfered. |
| 170 | * Does not check for overflow. */ |
| 171 | bool |
| 172 | dtv_scatter_io_next(struct dtv_scatter_io *sio, void **p, size_t *sz) |
| 173 | { |
| 174 | size_t pg, pgo; |
| 175 | |
| 176 | if (sio->sio_resid == 0) |
| 177 | return false; |
| 178 | |
| 179 | pg = sio->sio_offset >> PAGE_SHIFT; |
| 180 | pgo = sio->sio_offset & PAGE_MASK; |
| 181 | |
| 182 | *sz = min(PAGE_SIZE - pgo, sio->sio_resid); |
| 183 | *p = sio->sio_buf->sb_page_ary[pg] + pgo; |
| 184 | |
| 185 | sio->sio_offset += *sz; |
| 186 | sio->sio_resid -= *sz; |
| 187 | |
| 188 | return true; |
| 189 | } |
| 190 | |
| 191 | /* Semi-undo of a failed segment copy. Updates the scatter_io |
| 192 | * struct to the previous values prior to a failed segment copy. */ |
| 193 | void |
| 194 | dtv_scatter_io_undo(struct dtv_scatter_io *sio, size_t sz) |
| 195 | { |
| 196 | sio->sio_offset -= sz; |
| 197 | sio->sio_resid += sz; |
| 198 | } |
| 199 | |
| 200 | /* Copy data from src into the scatter_buf as described by io. */ |
| 201 | void |
| 202 | dtv_scatter_io_copyin(struct dtv_scatter_io *sio, const void *p) |
| 203 | { |
| 204 | void *dst; |
| 205 | const uint8_t *src = p; |
| 206 | size_t sz; |
| 207 | |
| 208 | while (dtv_scatter_io_next(sio, &dst, &sz)) { |
| 209 | memcpy(dst, src, sz); |
| 210 | src += sz; |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | /* --not used; commented to avoid compiler warnings-- |
| 215 | void |
| 216 | dtv_scatter_io_copyout(struct dtv_scatter_io *sio, void *p) |
| 217 | { |
| 218 | void *src; |
| 219 | uint8_t *dst = p; |
| 220 | size_t sz; |
| 221 | |
| 222 | while (dtv_scatter_io_next(sio, &src, &sz)) { |
| 223 | memcpy(dst, src, sz); |
| 224 | dst += sz; |
| 225 | } |
| 226 | } |
| 227 | */ |
| 228 | |
| 229 | /* Performat a series of uiomove calls on a scatter buf. Returns |
| 230 | * EFAULT if uiomove EFAULTs on the first segment. Otherwise, returns |
| 231 | * an incomplete transfer but with no error. */ |
| 232 | int |
| 233 | dtv_scatter_io_uiomove(struct dtv_scatter_io *sio, struct uio *uio) |
| 234 | { |
| 235 | void *p; |
| 236 | size_t sz; |
| 237 | bool first = true; |
| 238 | int err; |
| 239 | |
| 240 | while (dtv_scatter_io_next(sio, &p, &sz)) { |
| 241 | err = uiomove(p, sz, uio); |
| 242 | if (err == EFAULT) { |
| 243 | dtv_scatter_io_undo(sio, sz); |
| 244 | if (first) |
| 245 | return EFAULT; |
| 246 | else |
| 247 | return 0; |
| 248 | } |
| 249 | first = false; |
| 250 | } |
| 251 | |
| 252 | return 0; |
| 253 | } |
| 254 | |