25b17c09d6
- Resources used by spigen_mmap_single() are now tracked using devfs_set_cdevpriv() rather than in the softc. - Since resources are now tracked per-open-fd, there is no need to try to impose any exclusive-open logic, so flags related to that are removed. - Flags used to track open status to prevent detach() when the device is open are replaced with calls to device_busy()/device_unbusy(). That extends the protection up the hierarchy so that the spibus and hardware controller drivers also can't be detached while the device is open/in use. - Arbitrary limits on the maximum size of a transfer are removed, along with the sysctl variables that allowed the limits to be changed. There is just no reason to limit the size of a spi transfer to the machine's page size. Or to any other arbitrary value, really. - Most of the locking is removed. It was mostly protecting access to flags and fields in the softc that no longer exist. The locking that remains is just to prevent concurrent calls to device_[un]busy(). - The code was calling malloc() with M_WAITOK while holding a mutex in several places. Since most of the locking is gone, that's fixed.
400 lines
10 KiB
C
400 lines
10 KiB
C
/*-
|
|
* 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 "opt_platform.h"
|
|
#include "opt_spi.h"
|
|
|
|
#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/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 <dev/spibus/spibusvar.h>
|
|
|
|
#ifdef FDT
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
|
|
static struct ofw_compat_data compat_data[] = {
|
|
{"freebsd,spigen", true},
|
|
{NULL, false}
|
|
};
|
|
|
|
#endif
|
|
|
|
#include "spibus_if.h"
|
|
|
|
struct spigen_softc {
|
|
device_t sc_dev;
|
|
struct cdev *sc_cdev;
|
|
#ifdef SPIGEN_LEGACY_CDEVNAME
|
|
struct cdev *sc_adev; /* alias device */
|
|
#endif
|
|
struct mtx sc_mtx;
|
|
};
|
|
|
|
struct spigen_mmap {
|
|
vm_object_t bufobj;
|
|
vm_offset_t kvaddr;
|
|
size_t bufsize;
|
|
};
|
|
|
|
static int
|
|
spigen_probe(device_t dev)
|
|
{
|
|
int rv;
|
|
|
|
/*
|
|
* By default we only bid to attach if specifically added by our parent
|
|
* (usually via hint.spigen.#.at=busname). On FDT systems we bid as the
|
|
* default driver based on being configured in the FDT data.
|
|
*/
|
|
rv = BUS_PROBE_NOWILDCARD;
|
|
|
|
#ifdef FDT
|
|
if (ofw_bus_status_okay(dev) &&
|
|
ofw_bus_search_compatible(dev, compat_data)->ocd_data)
|
|
rv = BUS_PROBE_DEFAULT;
|
|
#endif
|
|
|
|
device_set_desc(dev, "SPI Generic IO");
|
|
|
|
return (rv);
|
|
}
|
|
|
|
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_attach(device_t dev)
|
|
{
|
|
struct spigen_softc *sc;
|
|
const int unit = device_get_unit(dev);
|
|
int cs, res;
|
|
struct make_dev_args mda;
|
|
|
|
spibus_get_cs(dev, &cs);
|
|
cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->sc_dev = dev;
|
|
|
|
mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
|
|
|
|
make_dev_args_init(&mda);
|
|
mda.mda_flags = MAKEDEV_WAITOK;
|
|
mda.mda_devsw = &spigen_cdevsw;
|
|
mda.mda_cr = NULL;
|
|
mda.mda_uid = UID_ROOT;
|
|
mda.mda_gid = GID_OPERATOR;
|
|
mda.mda_mode = 0660;
|
|
mda.mda_unit = unit;
|
|
mda.mda_si_drv1 = dev;
|
|
|
|
res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d",
|
|
device_get_unit(device_get_parent(dev)), cs);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
#ifdef SPIGEN_LEGACY_CDEVNAME
|
|
res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit);
|
|
if (res) {
|
|
if (sc->sc_cdev) {
|
|
destroy_dev(sc->sc_cdev);
|
|
sc->sc_cdev = NULL;
|
|
}
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
|
|
{
|
|
device_t dev;
|
|
struct spigen_softc *sc;
|
|
|
|
dev = cdev->si_drv1;
|
|
sc = device_get_softc(dev);
|
|
|
|
mtx_lock(&sc->sc_mtx);
|
|
device_busy(sc->sc_dev);
|
|
mtx_unlock(&sc->sc_mtx);
|
|
|
|
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;
|
|
int error = 0;
|
|
|
|
#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
|
|
|
|
if (st->st_command.iov_len == 0)
|
|
return (EINVAL);
|
|
|
|
transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
|
|
M_DEVBUF, M_WAITOK);
|
|
if (st->st_data.iov_len > 0) {
|
|
transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
|
|
M_DEVBUF, M_WAITOK);
|
|
}
|
|
else
|
|
transfer.tx_data = transfer.rx_data = NULL;
|
|
|
|
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) && (st->st_data.iov_len > 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) && (st->st_data.iov_len > 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_mmap *mmap;
|
|
int error;
|
|
|
|
if ((error = devfs_get_cdevpriv((void **)&mmap)) != 0)
|
|
return (error);
|
|
|
|
if (mmap->bufsize < stm->stm_command_length + stm->stm_data_length)
|
|
return (E2BIG);
|
|
|
|
transfer.tx_cmd = transfer.rx_cmd = (void *)((uintptr_t)mmap->kvaddr);
|
|
transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
|
|
transfer.tx_data = transfer.rx_data =
|
|
(void *)((uintptr_t)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);
|
|
|
|
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;
|
|
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:
|
|
error = spibus_get_clock(dev, (uint32_t *)data);
|
|
break;
|
|
case SPIGENIOC_SET_CLOCK_SPEED:
|
|
error = spibus_set_clock(dev, *(uint32_t *)data);
|
|
break;
|
|
case SPIGENIOC_GET_SPI_MODE:
|
|
error = spibus_get_mode(dev, (uint32_t *)data);
|
|
break;
|
|
case SPIGENIOC_SET_SPI_MODE:
|
|
error = spibus_set_mode(dev, *(uint32_t *)data);
|
|
break;
|
|
default:
|
|
error = ENOTTY;
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
spigen_mmap_cleanup(void *arg)
|
|
{
|
|
struct spigen_mmap *mmap = arg;
|
|
|
|
if (mmap->kvaddr != 0)
|
|
pmap_qremove(mmap->kvaddr, mmap->bufsize / PAGE_SIZE);
|
|
if (mmap->bufobj != NULL)
|
|
vm_object_deallocate(mmap->bufobj);
|
|
free(mmap, M_DEVBUF);
|
|
}
|
|
|
|
static int
|
|
spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
|
|
vm_size_t size, struct vm_object **object, int nprot)
|
|
{
|
|
struct spigen_mmap *mmap;
|
|
vm_page_t *m;
|
|
size_t n, pages;
|
|
int error;
|
|
|
|
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;
|
|
|
|
if (devfs_get_cdevpriv((void **)&mmap) == 0)
|
|
return (EBUSY);
|
|
|
|
mmap = malloc(sizeof(*mmap), M_DEVBUF, M_ZERO | M_WAITOK);
|
|
if ((mmap->kvaddr = kva_alloc(size)) == 0) {
|
|
spigen_mmap_cleanup(mmap);
|
|
return (ENOMEM);
|
|
}
|
|
mmap->bufsize = size;
|
|
mmap->bufobj = vm_pager_allocate(OBJT_PHYS, 0, size, nprot, 0,
|
|
curthread->td_ucred);
|
|
|
|
m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
|
|
VM_OBJECT_WLOCK(mmap->bufobj);
|
|
vm_object_reference_locked(mmap->bufobj); // kernel and userland both
|
|
for (n = 0; n < pages; n++) {
|
|
m[n] = vm_page_grab(mmap->bufobj, n,
|
|
VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
|
|
m[n]->valid = VM_PAGE_BITS_ALL;
|
|
}
|
|
VM_OBJECT_WUNLOCK(mmap->bufobj);
|
|
pmap_qenter(mmap->kvaddr, m, pages);
|
|
free(m, M_TEMP);
|
|
|
|
if ((error = devfs_set_cdevpriv(mmap, spigen_mmap_cleanup)) != 0) {
|
|
/* Two threads were racing through this code; we lost. */
|
|
spigen_mmap_cleanup(mmap);
|
|
return (error);
|
|
}
|
|
*offset = 0;
|
|
*object = mmap->bufobj;
|
|
|
|
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);
|
|
device_unbusy(sc->sc_dev);
|
|
mtx_unlock(&sc->sc_mtx);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
spigen_detach(device_t dev)
|
|
{
|
|
struct spigen_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
#ifdef SPIGEN_LEGACY_CDEVNAME
|
|
if (sc->sc_adev)
|
|
destroy_dev(sc->sc_adev);
|
|
#endif
|
|
|
|
if (sc->sc_cdev)
|
|
destroy_dev(sc->sc_cdev);
|
|
|
|
mtx_destroy(&sc->sc_mtx);
|
|
|
|
return (0);
|
|
}
|
|
|
|
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);
|
|
MODULE_DEPEND(spigen, spibus, 1, 1, 1);
|
|
#ifdef FDT
|
|
SIMPLEBUS_PNP_INFO(compat_data);
|
|
#endif
|