1 /*- 2 * Copyright (c) 2015 Brian Fundakowski Feldman. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 */ 24 25 #include <sys/cdefs.h> 26 __FBSDID("$FreeBSD$"); 27 28 #include <sys/param.h> 29 #include <sys/systm.h> 30 #include <sys/bus.h> 31 #include <sys/conf.h> 32 #include <sys/kernel.h> 33 #include <sys/lock.h> 34 #include <sys/malloc.h> 35 #include <sys/mman.h> 36 #include <sys/mutex.h> 37 #include <sys/module.h> 38 #include <sys/proc.h> 39 #include <sys/rwlock.h> 40 #include <sys/spigenio.h> 41 #include <sys/sysctl.h> 42 #include <sys/types.h> 43 44 #include <vm/vm.h> 45 #include <vm/vm_extern.h> 46 #include <vm/vm_object.h> 47 #include <vm/vm_page.h> 48 #include <vm/vm_pager.h> 49 50 #include <dev/spibus/spi.h> 51 52 #include "spibus_if.h" 53 54 struct spigen_softc { 55 device_t sc_dev; 56 struct cdev *sc_cdev; 57 struct mtx sc_mtx; 58 uint32_t sc_clock_speed; 59 uint32_t sc_command_length_max; /* cannot change while mmapped */ 60 uint32_t sc_data_length_max; /* cannot change while mmapped */ 61 vm_object_t sc_mmap_buffer; /* command, then data */ 62 vm_offset_t sc_mmap_kvaddr; 63 size_t sc_mmap_buffer_size; 64 int sc_mmap_busy; 65 int sc_debug; 66 }; 67 68 static int 69 spigen_probe(device_t dev) 70 { 71 device_set_desc(dev, "SPI Generic IO"); 72 return (0); 73 } 74 75 static int spigen_open(struct cdev *, int, int, struct thread *); 76 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *); 77 static int spigen_close(struct cdev *, int, int, struct thread *); 78 static d_mmap_single_t spigen_mmap_single; 79 80 static struct cdevsw spigen_cdevsw = { 81 .d_version = D_VERSION, 82 .d_name = "spigen", 83 .d_open = spigen_open, 84 .d_ioctl = spigen_ioctl, 85 .d_mmap_single = spigen_mmap_single, 86 .d_close = spigen_close 87 }; 88 89 static int 90 spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS) 91 { 92 struct spigen_softc *sc = (struct spigen_softc *)arg1; 93 uint32_t command_length_max; 94 int error; 95 96 mtx_lock(&sc->sc_mtx); 97 command_length_max = sc->sc_command_length_max; 98 mtx_unlock(&sc->sc_mtx); 99 error = sysctl_handle_int(oidp, &command_length_max, 100 sizeof(command_length_max), req); 101 if (error == 0 && req->newptr != NULL) { 102 mtx_lock(&sc->sc_mtx); 103 if (sc->sc_mmap_buffer != NULL) 104 error = EBUSY; 105 else 106 sc->sc_command_length_max = command_length_max; 107 mtx_unlock(&sc->sc_mtx); 108 } 109 return (error); 110 } 111 112 static int 113 spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS) 114 { 115 struct spigen_softc *sc = (struct spigen_softc *)arg1; 116 uint32_t data_length_max; 117 int error; 118 119 mtx_lock(&sc->sc_mtx); 120 data_length_max = sc->sc_data_length_max; 121 mtx_unlock(&sc->sc_mtx); 122 error = sysctl_handle_int(oidp, &data_length_max, 123 sizeof(data_length_max), req); 124 if (error == 0 && req->newptr != NULL) { 125 mtx_lock(&sc->sc_mtx); 126 if (sc->sc_mmap_buffer != NULL) 127 error = EBUSY; 128 else 129 sc->sc_data_length_max = data_length_max; 130 mtx_unlock(&sc->sc_mtx); 131 } 132 return (error); 133 } 134 135 static void 136 spigen_sysctl_init(struct spigen_softc *sc) 137 { 138 struct sysctl_ctx_list *ctx; 139 struct sysctl_oid *tree_node; 140 struct sysctl_oid_list *tree; 141 142 /* 143 * Add system sysctl tree/handlers. 144 */ 145 ctx = device_get_sysctl_ctx(sc->sc_dev); 146 tree_node = device_get_sysctl_tree(sc->sc_dev); 147 tree = SYSCTL_CHILDREN(tree_node); 148 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max", 149 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), 150 spigen_command_length_max_proc, "IU", "SPI command header portion (octets)"); 151 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max", 152 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), 153 spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)"); 154 SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW, 155 &sc->sc_debug, 0, "debug flags"); 156 157 } 158 159 static int 160 spigen_attach(device_t dev) 161 { 162 struct spigen_softc *sc; 163 const int unit = device_get_unit(dev); 164 165 sc = device_get_softc(dev); 166 sc->sc_dev = dev; 167 sc->sc_cdev = make_dev(&spigen_cdevsw, unit, 168 UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit); 169 sc->sc_cdev->si_drv1 = dev; 170 sc->sc_command_length_max = PAGE_SIZE; 171 sc->sc_data_length_max = PAGE_SIZE; 172 mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); 173 spigen_sysctl_init(sc); 174 175 return (0); 176 } 177 178 static int 179 spigen_open(struct cdev *dev, int oflags, int devtype, struct thread *td) 180 { 181 182 return (0); 183 } 184 185 static int 186 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st) 187 { 188 struct spi_command transfer = SPI_COMMAND_INITIALIZER; 189 device_t dev = cdev->si_drv1; 190 struct spigen_softc *sc = device_get_softc(dev); 191 int error = 0; 192 193 mtx_lock(&sc->sc_mtx); 194 if (st->st_command.iov_len == 0 || st->st_data.iov_len == 0) 195 error = EINVAL; 196 else if (st->st_command.iov_len > sc->sc_command_length_max || 197 st->st_data.iov_len > sc->sc_data_length_max) 198 error = ENOMEM; 199 mtx_unlock(&sc->sc_mtx); 200 if (error) 201 return (error); 202 203 #if 0 204 device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base, 205 st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len); 206 #endif 207 transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len, 208 M_DEVBUF, M_WAITOK); 209 if (transfer.tx_cmd == NULL) 210 return (ENOMEM); 211 transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len, 212 M_DEVBUF, M_WAITOK); 213 if (transfer.tx_data == NULL) { 214 free(transfer.tx_cmd, M_DEVBUF); 215 return (ENOMEM); 216 } 217 218 error = copyin(st->st_command.iov_base, transfer.tx_cmd, 219 transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len); 220 if (error == 0) 221 error = copyin(st->st_data.iov_base, transfer.tx_data, 222 transfer.tx_data_sz = transfer.rx_data_sz = 223 st->st_data.iov_len); 224 if (error == 0) 225 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 226 if (error == 0) { 227 error = copyout(transfer.rx_cmd, st->st_command.iov_base, 228 transfer.rx_cmd_sz); 229 if (error == 0) 230 error = copyout(transfer.rx_data, st->st_data.iov_base, 231 transfer.rx_data_sz); 232 } 233 234 free(transfer.tx_cmd, M_DEVBUF); 235 free(transfer.tx_data, M_DEVBUF); 236 return (error); 237 } 238 239 static int 240 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm) 241 { 242 struct spi_command transfer = SPI_COMMAND_INITIALIZER; 243 device_t dev = cdev->si_drv1; 244 struct spigen_softc *sc = device_get_softc(dev); 245 int error = 0; 246 247 mtx_lock(&sc->sc_mtx); 248 if (sc->sc_mmap_busy) 249 error = EBUSY; 250 else if (stm->stm_command_length > sc->sc_command_length_max || 251 stm->stm_data_length > sc->sc_data_length_max) 252 error = E2BIG; 253 else if (sc->sc_mmap_buffer == NULL) 254 error = EINVAL; 255 else if (sc->sc_mmap_buffer_size < 256 stm->stm_command_length + stm->stm_data_length) 257 error = ENOMEM; 258 if (error == 0) 259 sc->sc_mmap_busy = 1; 260 mtx_unlock(&sc->sc_mtx); 261 if (error) 262 return (error); 263 264 transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr; 265 transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length; 266 transfer.tx_data = transfer.rx_data = 267 (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length); 268 transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length; 269 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 270 271 mtx_lock(&sc->sc_mtx); 272 KASSERT(sc->sc_mmap_busy, ("mmap no longer marked busy")); 273 sc->sc_mmap_busy = 0; 274 mtx_unlock(&sc->sc_mtx); 275 return (error); 276 } 277 278 static int 279 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, 280 struct thread *td) 281 { 282 device_t dev = cdev->si_drv1; 283 struct spigen_softc *sc = device_get_softc(dev); 284 int error; 285 286 switch (cmd) { 287 case SPIGENIOC_TRANSFER: 288 error = spigen_transfer(cdev, (struct spigen_transfer *)data); 289 break; 290 case SPIGENIOC_TRANSFER_MMAPPED: 291 error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data); 292 break; 293 case SPIGENIOC_GET_CLOCK_SPEED: 294 mtx_lock(&sc->sc_mtx); 295 *(uint32_t *)data = sc->sc_clock_speed; 296 /* XXX TODO: implement spibus ivar call */ 297 mtx_unlock(&sc->sc_mtx); 298 error = 0; 299 break; 300 case SPIGENIOC_SET_CLOCK_SPEED: 301 mtx_lock(&sc->sc_mtx); 302 sc->sc_clock_speed = *(uint32_t *)data; 303 mtx_unlock(&sc->sc_mtx); 304 error = 0; 305 break; 306 default: 307 error = EOPNOTSUPP; 308 } 309 return (error); 310 } 311 312 static int 313 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, 314 vm_size_t size, struct vm_object **object, int nprot) 315 { 316 device_t dev = cdev->si_drv1; 317 struct spigen_softc *sc = device_get_softc(dev); 318 vm_page_t *m; 319 size_t n, pages; 320 321 if (size == 0 || 322 (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE)) 323 != (PROT_READ | PROT_WRITE)) 324 return (EINVAL); 325 size = roundup2(size, PAGE_SIZE); 326 pages = size / PAGE_SIZE; 327 328 mtx_lock(&sc->sc_mtx); 329 if (sc->sc_mmap_buffer != NULL) { 330 mtx_unlock(&sc->sc_mtx); 331 return (EBUSY); 332 } else if (size > sc->sc_command_length_max + sc->sc_data_length_max) { 333 mtx_unlock(&sc->sc_mtx); 334 return (E2BIG); 335 } 336 sc->sc_mmap_buffer_size = size; 337 *offset = 0; 338 sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size, 339 nprot, *offset, curthread->td_ucred); 340 m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK); 341 VM_OBJECT_WLOCK(*object); 342 vm_object_reference_locked(*object); // kernel and userland both 343 for (n = 0; n < pages; n++) { 344 m[n] = vm_page_grab(*object, n, 345 VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED); 346 m[n]->valid = VM_PAGE_BITS_ALL; 347 } 348 VM_OBJECT_WUNLOCK(*object); 349 sc->sc_mmap_kvaddr = kva_alloc(size); 350 pmap_qenter(sc->sc_mmap_kvaddr, m, pages); 351 free(m, M_TEMP); 352 mtx_unlock(&sc->sc_mtx); 353 354 if (*object == NULL) 355 return (EINVAL); 356 return (0); 357 } 358 359 static int 360 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td) 361 { 362 device_t dev = cdev->si_drv1; 363 struct spigen_softc *sc = device_get_softc(dev); 364 365 mtx_lock(&sc->sc_mtx); 366 if (sc->sc_mmap_buffer != NULL) { 367 pmap_qremove(sc->sc_mmap_kvaddr, 368 sc->sc_mmap_buffer_size / PAGE_SIZE); 369 kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size); 370 sc->sc_mmap_kvaddr = 0; 371 vm_object_deallocate(sc->sc_mmap_buffer); 372 sc->sc_mmap_buffer = NULL; 373 sc->sc_mmap_buffer_size = 0; 374 } 375 mtx_unlock(&sc->sc_mtx); 376 return (0); 377 } 378 379 static int 380 spigen_detach(device_t dev) 381 { 382 383 return (EIO); 384 } 385 386 static devclass_t spigen_devclass; 387 388 static device_method_t spigen_methods[] = { 389 /* Device interface */ 390 DEVMETHOD(device_probe, spigen_probe), 391 DEVMETHOD(device_attach, spigen_attach), 392 DEVMETHOD(device_detach, spigen_detach), 393 394 { 0, 0 } 395 }; 396 397 static driver_t spigen_driver = { 398 "spigen", 399 spigen_methods, 400 sizeof(struct spigen_softc), 401 }; 402 403 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0); 404