Author: adrian
Date: Thu May 26 07:02:20 2016
New Revision: 300713
URL: https://svnweb.freebsd.org/changeset/base/300713

Log:
  [spigen] add initial spigen driver from green@
  
  This is a simple ioctl and mmap API to issue SPI transactions from
  userland.  It's useful for simple devices (eg spi temperature sensors,
  etc) for experimentation.
  
  TODO:
  
  * Write some documentation!
  
  Submitted by: green

Added:
  head/sys/dev/spibus/spigen.c   (contents, props changed)
Modified:
  head/sys/conf/files
  head/sys/dev/spibus/spi.h

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files Thu May 26 06:37:33 2016        (r300712)
+++ head/sys/conf/files Thu May 26 07:02:20 2016        (r300713)
@@ -2553,6 +2553,7 @@ dev/sound/midi/synth_if.m optional sound
 dev/spibus/ofw_spibus.c                optional fdt spibus
 dev/spibus/spibus.c            optional spibus                         \
        dependency      "spibus_if.h"
+dev/spibus/spigen.c            optional spigen
 dev/spibus/spibus_if.m         optional spibus
 dev/ste/if_ste.c               optional ste pci
 dev/stg/tmc18c30.c             optional stg

Modified: head/sys/dev/spibus/spi.h
==============================================================================
--- head/sys/dev/spibus/spi.h   Thu May 26 06:37:33 2016        (r300712)
+++ head/sys/dev/spibus/spi.h   Thu May 26 07:02:20 2016        (r300713)
@@ -37,4 +37,6 @@ struct spi_command {
        uint32_t rx_data_sz;
 };
 
+#define        SPI_COMMAND_INITIALIZER { 0 }
+
 #define        SPI_CHIP_SELECT_HIGH    0x1             /* Chip select high 
(else low) */

Added: head/sys/dev/spibus/spigen.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/dev/spibus/spigen.c        Thu May 26 07:02:20 2016        
(r300713)
@@ -0,0 +1,403 @@
+/*-
+ * Copyright (c) 2015 Brian Fundakowski Feldman.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mman.h>
+#include <sys/mutex.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/rwlock.h>
+#include <sys/spigenio.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+ 
+#include <vm/vm.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_object.h>
+#include <vm/vm_page.h>
+#include <vm/vm_pager.h>
+
+#include <dev/spibus/spi.h>
+
+#include "spibus_if.h"
+
+struct spigen_softc {
+       device_t sc_dev;
+       struct cdev *sc_cdev;
+       struct mtx sc_mtx;
+       uint32_t sc_clock_speed;
+       uint32_t sc_command_length_max; /* cannot change while mmapped */
+       uint32_t sc_data_length_max;    /* cannot change while mmapped */
+       vm_object_t sc_mmap_buffer;     /* command, then data */
+       vm_offset_t sc_mmap_kvaddr;
+       size_t sc_mmap_buffer_size;
+       int sc_mmap_busy;
+       int sc_debug;
+};
+
+static int
+spigen_probe(device_t dev)
+{
+       device_set_desc(dev, "SPI Generic IO");
+       return (0);
+}
+
+static int spigen_open(struct cdev *, int, int, struct thread *);
+static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
+static int spigen_close(struct cdev *, int, int, struct thread *);
+static d_mmap_single_t spigen_mmap_single;
+
+static struct cdevsw spigen_cdevsw = {
+       .d_version =     D_VERSION,
+       .d_name =        "spigen",
+       .d_open =        spigen_open,
+       .d_ioctl =       spigen_ioctl,
+       .d_mmap_single = spigen_mmap_single,
+       .d_close =       spigen_close
+};
+
+static int
+spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
+{
+       struct spigen_softc *sc = (struct spigen_softc *)arg1;
+       uint32_t command_length_max;
+       int error;
+
+       mtx_lock(&sc->sc_mtx);
+       command_length_max = sc->sc_command_length_max;
+       mtx_unlock(&sc->sc_mtx);
+       error = sysctl_handle_int(oidp, &command_length_max,
+           sizeof(command_length_max), req);
+       if (error == 0 && req->newptr != NULL) {
+               mtx_lock(&sc->sc_mtx);
+               if (sc->sc_mmap_buffer != NULL)
+                       error = EBUSY;
+               else
+                       sc->sc_command_length_max = command_length_max;
+               mtx_unlock(&sc->sc_mtx);
+       }
+       return (error);
+}
+
+static int
+spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
+{
+       struct spigen_softc *sc = (struct spigen_softc *)arg1;
+       uint32_t data_length_max;
+       int error;
+
+       mtx_lock(&sc->sc_mtx);
+       data_length_max = sc->sc_data_length_max;
+       mtx_unlock(&sc->sc_mtx);
+       error = sysctl_handle_int(oidp, &data_length_max,
+           sizeof(data_length_max), req);
+       if (error == 0 && req->newptr != NULL) {
+               mtx_lock(&sc->sc_mtx);
+               if (sc->sc_mmap_buffer != NULL)
+                       error = EBUSY;
+               else
+                       sc->sc_data_length_max = data_length_max;
+               mtx_unlock(&sc->sc_mtx);
+       }
+       return (error);
+}
+
+static void
+spigen_sysctl_init(struct spigen_softc *sc)
+{
+       struct sysctl_ctx_list *ctx;
+       struct sysctl_oid *tree_node;
+       struct sysctl_oid_list *tree;
+
+       /*
+        * Add system sysctl tree/handlers.
+        */
+       ctx = device_get_sysctl_ctx(sc->sc_dev);
+       tree_node = device_get_sysctl_tree(sc->sc_dev);
+       tree = SYSCTL_CHILDREN(tree_node);
+       SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
+           CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
+           spigen_command_length_max_proc, "IU", "SPI command header portion 
(octets)");
+       SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
+           CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
+           spigen_data_length_max_proc, "IU", "SPI data trailer portion 
(octets)");
+       SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
+           &sc->sc_debug, 0, "debug flags");
+
+}
+
+static int
+spigen_attach(device_t dev)
+{
+       struct spigen_softc *sc;
+       const int unit = device_get_unit(dev);
+
+       sc = device_get_softc(dev);
+       sc->sc_dev = dev;
+       sc->sc_cdev = make_dev(&spigen_cdevsw, unit,
+           UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit);
+       sc->sc_cdev->si_drv1 = dev;
+       sc->sc_command_length_max = PAGE_SIZE;
+       sc->sc_data_length_max = PAGE_SIZE;
+       mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+       spigen_sysctl_init(sc);
+
+       return (0);
+}
+
+static int 
+spigen_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+
+       return (0);
+}
+
+static int
+spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
+{
+       struct spi_command transfer = SPI_COMMAND_INITIALIZER;
+       device_t dev = cdev->si_drv1;
+       struct spigen_softc *sc = device_get_softc(dev);
+       int error = 0;
+
+       mtx_lock(&sc->sc_mtx);
+       if (st->st_command.iov_len == 0 || st->st_data.iov_len == 0)
+               error = EINVAL;
+       else if (st->st_command.iov_len > sc->sc_command_length_max ||
+           st->st_data.iov_len > sc->sc_data_length_max)
+               error = ENOMEM;
+       mtx_unlock(&sc->sc_mtx);
+       if (error)
+               return (error);
+       
+#if 0
+       device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
+           st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
+#endif
+       transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
+           M_DEVBUF, M_WAITOK);
+       if (transfer.tx_cmd == NULL)
+               return (ENOMEM);
+       transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
+           M_DEVBUF, M_WAITOK);
+       if (transfer.tx_data == NULL) {
+               free(transfer.tx_cmd, M_DEVBUF);
+               return (ENOMEM);
+       }
+
+       error = copyin(st->st_command.iov_base, transfer.tx_cmd,
+           transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);  
+       if (error == 0)
+               error = copyin(st->st_data.iov_base, transfer.tx_data,
+                   transfer.tx_data_sz = transfer.rx_data_sz =
+                                         st->st_data.iov_len); 
+       if (error == 0)
+               error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
+       if (error == 0) {
+               error = copyout(transfer.rx_cmd, st->st_command.iov_base,
+                   transfer.rx_cmd_sz);
+               if (error == 0)
+                       error = copyout(transfer.rx_data, st->st_data.iov_base,
+                           transfer.rx_data_sz);
+       }
+
+       free(transfer.tx_cmd, M_DEVBUF);
+       free(transfer.tx_data, M_DEVBUF);
+       return (error);
+}
+
+static int
+spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
+{
+       struct spi_command transfer = SPI_COMMAND_INITIALIZER;
+       device_t dev = cdev->si_drv1;
+       struct spigen_softc *sc = device_get_softc(dev);
+       int error = 0;
+
+       mtx_lock(&sc->sc_mtx);
+       if (sc->sc_mmap_busy)
+               error = EBUSY;
+       else if (stm->stm_command_length > sc->sc_command_length_max ||
+           stm->stm_data_length > sc->sc_data_length_max)
+               error = E2BIG;
+       else if (sc->sc_mmap_buffer == NULL)
+               error = EINVAL;
+       else if (sc->sc_mmap_buffer_size <
+           stm->stm_command_length + stm->stm_data_length)
+               error = ENOMEM;
+       if (error == 0)
+               sc->sc_mmap_busy = 1;
+       mtx_unlock(&sc->sc_mtx);
+       if (error)
+               return (error);
+       
+       transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
+       transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
+       transfer.tx_data = transfer.rx_data =
+           (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
+       transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
+       error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
+
+       mtx_lock(&sc->sc_mtx);
+       KASSERT(sc->sc_mmap_busy, ("mmap no longer marked busy"));
+       sc->sc_mmap_busy = 0;
+       mtx_unlock(&sc->sc_mtx);
+       return (error);
+}
+
+static int
+spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
+    struct thread *td)
+{
+       device_t dev = cdev->si_drv1;
+       struct spigen_softc *sc = device_get_softc(dev);
+       int error;
+
+       switch (cmd) {
+       case SPIGENIOC_TRANSFER:
+               error = spigen_transfer(cdev, (struct spigen_transfer *)data);
+               break;
+       case SPIGENIOC_TRANSFER_MMAPPED:
+               error = spigen_transfer_mmapped(cdev, (struct 
spigen_transfer_mmapped *)data);
+               break;
+       case SPIGENIOC_GET_CLOCK_SPEED:
+               mtx_lock(&sc->sc_mtx);
+               *(uint32_t *)data = sc->sc_clock_speed;
+               /* XXX TODO: implement spibus ivar call */
+               mtx_unlock(&sc->sc_mtx);
+               error = 0;
+               break;
+       case SPIGENIOC_SET_CLOCK_SPEED:
+               mtx_lock(&sc->sc_mtx);
+               sc->sc_clock_speed = *(uint32_t *)data;
+               mtx_unlock(&sc->sc_mtx);
+               error = 0;
+               break;
+       default:
+               error = EOPNOTSUPP;
+       }
+       return (error);
+}
+
+static int
+spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
+    vm_size_t size, struct vm_object **object, int nprot)
+{
+       device_t dev = cdev->si_drv1;
+       struct spigen_softc *sc = device_get_softc(dev);
+       vm_page_t *m;
+       size_t n, pages;
+
+       if (size == 0 ||
+           (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
+           != (PROT_READ | PROT_WRITE))
+               return (EINVAL);
+       size = roundup2(size, PAGE_SIZE);
+       pages = size / PAGE_SIZE;
+
+       mtx_lock(&sc->sc_mtx);
+       if (sc->sc_mmap_buffer != NULL) {
+               mtx_unlock(&sc->sc_mtx);
+               return (EBUSY);
+       } else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
+               mtx_unlock(&sc->sc_mtx);
+               return (E2BIG);
+       }
+       sc->sc_mmap_buffer_size = size;
+       *offset = 0;
+       sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
+           nprot, *offset, curthread->td_ucred);
+       m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
+       VM_OBJECT_WLOCK(*object);
+       vm_object_reference_locked(*object); // kernel and userland both
+       for (n = 0; n < pages; n++) {
+               m[n] = vm_page_grab(*object, n,
+                   VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
+               m[n]->valid = VM_PAGE_BITS_ALL;
+       }
+       VM_OBJECT_WUNLOCK(*object);
+       sc->sc_mmap_kvaddr = kva_alloc(size);
+       pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
+       free(m, M_TEMP);
+       mtx_unlock(&sc->sc_mtx);
+
+       if (*object == NULL)
+                return (EINVAL);
+       return (0);
+}
+
+static int 
+spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
+{
+       device_t dev = cdev->si_drv1;
+       struct spigen_softc *sc = device_get_softc(dev);
+
+       mtx_lock(&sc->sc_mtx);
+       if (sc->sc_mmap_buffer != NULL) {
+               pmap_qremove(sc->sc_mmap_kvaddr,
+                   sc->sc_mmap_buffer_size / PAGE_SIZE);
+               kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
+               sc->sc_mmap_kvaddr = 0;
+               vm_object_deallocate(sc->sc_mmap_buffer);
+               sc->sc_mmap_buffer = NULL;
+               sc->sc_mmap_buffer_size = 0;
+       }
+       mtx_unlock(&sc->sc_mtx);
+       return (0);
+}
+
+static int
+spigen_detach(device_t dev)
+{
+
+       return (EIO);
+}
+
+static devclass_t spigen_devclass;
+
+static device_method_t spigen_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_probe,         spigen_probe),
+       DEVMETHOD(device_attach,        spigen_attach),
+       DEVMETHOD(device_detach,        spigen_detach),
+
+       { 0, 0 }
+};
+
+static driver_t spigen_driver = {
+       "spigen",
+       spigen_methods,
+       sizeof(struct spigen_softc),
+};
+
+DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to