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 "opt_platform.h" 29 30 #include <sys/param.h> 31 #include <sys/systm.h> 32 #include <sys/bus.h> 33 #include <sys/conf.h> 34 #include <sys/kernel.h> 35 #include <sys/lock.h> 36 #include <sys/malloc.h> 37 #include <sys/mman.h> 38 #include <sys/mutex.h> 39 #include <sys/module.h> 40 #include <sys/proc.h> 41 #include <sys/rwlock.h> 42 #include <sys/spigenio.h> 43 #include <sys/sysctl.h> 44 #include <sys/types.h> 45 46 #include <vm/vm.h> 47 #include <vm/vm_extern.h> 48 #include <vm/vm_object.h> 49 #include <vm/vm_page.h> 50 #include <vm/vm_pager.h> 51 52 #include <dev/spibus/spi.h> 53 54 #include "spibus_if.h" 55 56 #define SPIGEN_OPEN (1 << 0) 57 #define SPIGEN_MMAP_BUSY (1 << 1) 58 59 struct spigen_softc { 60 device_t sc_dev; 61 struct cdev *sc_cdev; 62 struct mtx sc_mtx; 63 uint32_t sc_clock_speed; 64 uint32_t sc_command_length_max; /* cannot change while mmapped */ 65 uint32_t sc_data_length_max; /* cannot change while mmapped */ 66 vm_object_t sc_mmap_buffer; /* command, then data */ 67 vm_offset_t sc_mmap_kvaddr; 68 size_t sc_mmap_buffer_size; 69 int sc_debug; 70 int sc_flags; 71 }; 72 73 #ifdef FDT 74 static void 75 spigen_identify(driver_t *driver, device_t parent) 76 { 77 if (device_find_child(parent, "spigen", -1) != NULL) 78 return; 79 if (BUS_ADD_CHILD(parent, 0, "spigen", -1) == NULL) 80 device_printf(parent, "add child failed\n"); 81 } 82 #endif 83 84 static int 85 spigen_probe(device_t dev) 86 { 87 88 device_set_desc(dev, "SPI Generic IO"); 89 90 return (BUS_PROBE_NOWILDCARD); 91 } 92 93 static int spigen_open(struct cdev *, int, int, struct thread *); 94 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *); 95 static int spigen_close(struct cdev *, int, int, struct thread *); 96 static d_mmap_single_t spigen_mmap_single; 97 98 static struct cdevsw spigen_cdevsw = { 99 .d_version = D_VERSION, 100 .d_name = "spigen", 101 .d_open = spigen_open, 102 .d_ioctl = spigen_ioctl, 103 .d_mmap_single = spigen_mmap_single, 104 .d_close = spigen_close 105 }; 106 107 static int 108 spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS) 109 { 110 struct spigen_softc *sc = (struct spigen_softc *)arg1; 111 uint32_t command_length_max; 112 int error; 113 114 mtx_lock(&sc->sc_mtx); 115 command_length_max = sc->sc_command_length_max; 116 mtx_unlock(&sc->sc_mtx); 117 error = sysctl_handle_int(oidp, &command_length_max, 118 sizeof(command_length_max), req); 119 if (error == 0 && req->newptr != NULL) { 120 mtx_lock(&sc->sc_mtx); 121 if (sc->sc_mmap_buffer != NULL) 122 error = EBUSY; 123 else 124 sc->sc_command_length_max = command_length_max; 125 mtx_unlock(&sc->sc_mtx); 126 } 127 return (error); 128 } 129 130 static int 131 spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS) 132 { 133 struct spigen_softc *sc = (struct spigen_softc *)arg1; 134 uint32_t data_length_max; 135 int error; 136 137 mtx_lock(&sc->sc_mtx); 138 data_length_max = sc->sc_data_length_max; 139 mtx_unlock(&sc->sc_mtx); 140 error = sysctl_handle_int(oidp, &data_length_max, 141 sizeof(data_length_max), req); 142 if (error == 0 && req->newptr != NULL) { 143 mtx_lock(&sc->sc_mtx); 144 if (sc->sc_mmap_buffer != NULL) 145 error = EBUSY; 146 else 147 sc->sc_data_length_max = data_length_max; 148 mtx_unlock(&sc->sc_mtx); 149 } 150 return (error); 151 } 152 153 static void 154 spigen_sysctl_init(struct spigen_softc *sc) 155 { 156 struct sysctl_ctx_list *ctx; 157 struct sysctl_oid *tree_node; 158 struct sysctl_oid_list *tree; 159 160 /* 161 * Add system sysctl tree/handlers. 162 */ 163 ctx = device_get_sysctl_ctx(sc->sc_dev); 164 tree_node = device_get_sysctl_tree(sc->sc_dev); 165 tree = SYSCTL_CHILDREN(tree_node); 166 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max", 167 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), 168 spigen_command_length_max_proc, "IU", "SPI command header portion (octets)"); 169 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max", 170 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), 171 spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)"); 172 SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW, 173 &sc->sc_debug, 0, "debug flags"); 174 175 } 176 177 static int 178 spigen_attach(device_t dev) 179 { 180 struct spigen_softc *sc; 181 const int unit = device_get_unit(dev); 182 183 sc = device_get_softc(dev); 184 sc->sc_dev = dev; 185 sc->sc_cdev = make_dev(&spigen_cdevsw, unit, 186 UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit); 187 sc->sc_cdev->si_drv1 = dev; 188 sc->sc_command_length_max = PAGE_SIZE; 189 sc->sc_data_length_max = PAGE_SIZE; 190 mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); 191 spigen_sysctl_init(sc); 192 193 return (0); 194 } 195 196 static int 197 spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td) 198 { 199 int error; 200 device_t dev; 201 struct spigen_softc *sc; 202 203 error = 0; 204 dev = cdev->si_drv1; 205 sc = device_get_softc(dev); 206 207 mtx_lock(&sc->sc_mtx); 208 if (sc->sc_flags & SPIGEN_OPEN) 209 error = EBUSY; 210 else 211 sc->sc_flags |= SPIGEN_OPEN; 212 mtx_unlock(&sc->sc_mtx); 213 214 return (error); 215 } 216 217 static int 218 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st) 219 { 220 struct spi_command transfer = SPI_COMMAND_INITIALIZER; 221 device_t dev = cdev->si_drv1; 222 struct spigen_softc *sc = device_get_softc(dev); 223 int error = 0; 224 225 mtx_lock(&sc->sc_mtx); 226 if (st->st_command.iov_len == 0) 227 error = EINVAL; 228 else if (st->st_command.iov_len > sc->sc_command_length_max || 229 st->st_data.iov_len > sc->sc_data_length_max) 230 error = ENOMEM; 231 mtx_unlock(&sc->sc_mtx); 232 if (error) 233 return (error); 234 235 #if 0 236 device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base, 237 st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len); 238 #endif 239 transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len, 240 M_DEVBUF, M_WAITOK); 241 if (transfer.tx_cmd == NULL) 242 return (ENOMEM); 243 if (st->st_data.iov_len > 0) { 244 transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len, 245 M_DEVBUF, M_WAITOK); 246 if (transfer.tx_data == NULL) { 247 free(transfer.tx_cmd, M_DEVBUF); 248 return (ENOMEM); 249 } 250 } 251 else 252 transfer.tx_data = transfer.rx_data = NULL; 253 254 error = copyin(st->st_command.iov_base, transfer.tx_cmd, 255 transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len); 256 if ((error == 0) && (st->st_data.iov_len > 0)) 257 error = copyin(st->st_data.iov_base, transfer.tx_data, 258 transfer.tx_data_sz = transfer.rx_data_sz = 259 st->st_data.iov_len); 260 if (error == 0) 261 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 262 if (error == 0) { 263 error = copyout(transfer.rx_cmd, st->st_command.iov_base, 264 transfer.rx_cmd_sz); 265 if ((error == 0) && (st->st_data.iov_len > 0)) 266 error = copyout(transfer.rx_data, st->st_data.iov_base, 267 transfer.rx_data_sz); 268 } 269 270 free(transfer.tx_cmd, M_DEVBUF); 271 free(transfer.tx_data, M_DEVBUF); 272 return (error); 273 } 274 275 static int 276 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm) 277 { 278 struct spi_command transfer = SPI_COMMAND_INITIALIZER; 279 device_t dev = cdev->si_drv1; 280 struct spigen_softc *sc = device_get_softc(dev); 281 int error = 0; 282 283 mtx_lock(&sc->sc_mtx); 284 if (sc->sc_flags & SPIGEN_MMAP_BUSY) 285 error = EBUSY; 286 else if (stm->stm_command_length > sc->sc_command_length_max || 287 stm->stm_data_length > sc->sc_data_length_max) 288 error = E2BIG; 289 else if (sc->sc_mmap_buffer == NULL) 290 error = EINVAL; 291 else if (sc->sc_mmap_buffer_size < 292 stm->stm_command_length + stm->stm_data_length) 293 error = ENOMEM; 294 if (error == 0) 295 sc->sc_flags |= SPIGEN_MMAP_BUSY; 296 mtx_unlock(&sc->sc_mtx); 297 if (error) 298 return (error); 299 300 transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr; 301 transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length; 302 transfer.tx_data = transfer.rx_data = 303 (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length); 304 transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length; 305 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 306 307 mtx_lock(&sc->sc_mtx); 308 KASSERT((sc->sc_flags & SPIGEN_MMAP_BUSY), ("mmap no longer marked busy")); 309 sc->sc_flags &= ~(SPIGEN_MMAP_BUSY); 310 mtx_unlock(&sc->sc_mtx); 311 return (error); 312 } 313 314 static int 315 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, 316 struct thread *td) 317 { 318 device_t dev = cdev->si_drv1; 319 struct spigen_softc *sc = device_get_softc(dev); 320 int error; 321 322 switch (cmd) { 323 case SPIGENIOC_TRANSFER: 324 error = spigen_transfer(cdev, (struct spigen_transfer *)data); 325 break; 326 case SPIGENIOC_TRANSFER_MMAPPED: 327 error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data); 328 break; 329 case SPIGENIOC_GET_CLOCK_SPEED: 330 mtx_lock(&sc->sc_mtx); 331 *(uint32_t *)data = sc->sc_clock_speed; 332 /* XXX TODO: implement spibus ivar call */ 333 mtx_unlock(&sc->sc_mtx); 334 error = 0; 335 break; 336 case SPIGENIOC_SET_CLOCK_SPEED: 337 mtx_lock(&sc->sc_mtx); 338 sc->sc_clock_speed = *(uint32_t *)data; 339 mtx_unlock(&sc->sc_mtx); 340 error = 0; 341 break; 342 default: 343 error = EOPNOTSUPP; 344 } 345 return (error); 346 } 347 348 static int 349 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, 350 vm_size_t size, struct vm_object **object, int nprot) 351 { 352 device_t dev = cdev->si_drv1; 353 struct spigen_softc *sc = device_get_softc(dev); 354 vm_page_t *m; 355 size_t n, pages; 356 357 if (size == 0 || 358 (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE)) 359 != (PROT_READ | PROT_WRITE)) 360 return (EINVAL); 361 size = roundup2(size, PAGE_SIZE); 362 pages = size / PAGE_SIZE; 363 364 mtx_lock(&sc->sc_mtx); 365 if (sc->sc_mmap_buffer != NULL) { 366 mtx_unlock(&sc->sc_mtx); 367 return (EBUSY); 368 } else if (size > sc->sc_command_length_max + sc->sc_data_length_max) { 369 mtx_unlock(&sc->sc_mtx); 370 return (E2BIG); 371 } 372 sc->sc_mmap_buffer_size = size; 373 *offset = 0; 374 sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size, 375 nprot, *offset, curthread->td_ucred); 376 m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK); 377 VM_OBJECT_WLOCK(*object); 378 vm_object_reference_locked(*object); // kernel and userland both 379 for (n = 0; n < pages; n++) { 380 m[n] = vm_page_grab(*object, n, 381 VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED); 382 m[n]->valid = VM_PAGE_BITS_ALL; 383 } 384 VM_OBJECT_WUNLOCK(*object); 385 sc->sc_mmap_kvaddr = kva_alloc(size); 386 pmap_qenter(sc->sc_mmap_kvaddr, m, pages); 387 free(m, M_TEMP); 388 mtx_unlock(&sc->sc_mtx); 389 390 if (*object == NULL) 391 return (EINVAL); 392 return (0); 393 } 394 395 static int 396 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td) 397 { 398 device_t dev = cdev->si_drv1; 399 struct spigen_softc *sc = device_get_softc(dev); 400 401 mtx_lock(&sc->sc_mtx); 402 if (sc->sc_mmap_buffer != NULL) { 403 pmap_qremove(sc->sc_mmap_kvaddr, 404 sc->sc_mmap_buffer_size / PAGE_SIZE); 405 kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size); 406 sc->sc_mmap_kvaddr = 0; 407 vm_object_deallocate(sc->sc_mmap_buffer); 408 sc->sc_mmap_buffer = NULL; 409 sc->sc_mmap_buffer_size = 0; 410 } 411 sc->sc_flags &= ~(SPIGEN_OPEN); 412 mtx_unlock(&sc->sc_mtx); 413 return (0); 414 } 415 416 static int 417 spigen_detach(device_t dev) 418 { 419 struct spigen_softc *sc; 420 421 sc = device_get_softc(dev); 422 423 mtx_lock(&sc->sc_mtx); 424 if (sc->sc_flags & SPIGEN_OPEN) { 425 mtx_unlock(&sc->sc_mtx); 426 return (EBUSY); 427 } 428 mtx_unlock(&sc->sc_mtx); 429 430 mtx_destroy(&sc->sc_mtx); 431 432 if (sc->sc_cdev) 433 destroy_dev(sc->sc_cdev); 434 435 return (0); 436 } 437 438 static devclass_t spigen_devclass; 439 440 static device_method_t spigen_methods[] = { 441 /* Device interface */ 442 #ifdef FDT 443 DEVMETHOD(device_identify, spigen_identify), 444 #endif 445 DEVMETHOD(device_probe, spigen_probe), 446 DEVMETHOD(device_attach, spigen_attach), 447 DEVMETHOD(device_detach, spigen_detach), 448 449 { 0, 0 } 450 }; 451 452 static driver_t spigen_driver = { 453 "spigen", 454 spigen_methods, 455 sizeof(struct spigen_softc), 456 }; 457 458 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0); 459