/*- * Copyright (c) 2011 Advanced Computing Technologies LLC * Written by: John H. Baldwin * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 __FBSDID("$FreeBSD$"); /* * Simple character devices to demonstrate the read, write, ioctl, * poll, and kqfilter cdevsw methods. * * The first device (/dev/echobuf) just creates a fixed size buffer * that data can be read and written into. It's size can be adjusted * via an ioctl. It is prefilled with zero's and is always readable * and writable as long as the buffer has non-zero size. * * The second device (/dev/echostream) creates a stream device which * stores data written (with each write always being an append) and * supplies that stream to readers. You can think of it as a very * naive global pipe. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "echodev.h" struct echodev_softc { struct cdev *cdev; char *buffer; size_t length, valid; struct sx lock; struct selinfo rsel; struct selinfo wsel; }; static MALLOC_DEFINE(M_ECHODEV, "echodev", "Demo echo character devices"); static d_ioctl_t echobuf_ioctl; static d_read_t echobuf_rdwr; static d_poll_t echobuf_poll; static d_kqfilter_t echobuf_kqfilter; static int echobuf_kqevent(struct knote *, long); static void echobuf_kqdetach(struct knote *); static struct filterops echobuf_filterops = { .f_isfd = 1, .f_attach = NULL, .f_detach = echobuf_kqdetach, .f_event = echobuf_kqevent, }; static struct cdevsw echobuf_cdevsw = { .d_version = D_VERSION, .d_read = echobuf_rdwr, .d_write = echobuf_rdwr, .d_ioctl = echobuf_ioctl, .d_poll = echobuf_poll, .d_kqfilter = echobuf_kqfilter, .d_name = "echobuf", }; static int echobuf_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct echodev_softc *sc; char *newbuf; size_t newlen; int error; error = 0; sc = dev->si_drv1; switch (cmd) { case ECHODEV_GBUFSIZE: *(size_t *)data = sc->length; break; case ECHODEV_SBUFSIZE: /* Require a file descriptor open for writes. */ if (!(fflag & FWRITE)) { error = EPERM; break; } newlen = *(size_t *)data; if (newlen == 0) newbuf = NULL; else newbuf = malloc(newlen, M_ECHODEV, M_WAITOK | M_ZERO); sx_xlock(&sc->lock); if (sc->buffer != NULL && newbuf != NULL) bcopy(sc->buffer, newbuf, MIN(sc->length, newlen)); free(sc->buffer, M_ECHODEV); sc->buffer = newbuf; sc->length = newlen; if (newlen != 0) { selwakeup(&sc->rsel); KNOTE_UNLOCKED(&sc->rsel.si_note, 0); } sx_xunlock(&sc->lock); break; case ECHODEV_CLEAR: sx_xlock(&sc->lock); if (sc->buffer != NULL) bzero(sc->buffer, sc->length); sx_xunlock(&sc->lock); break; default: error = ENOIOCTL; } return (error); } static int echobuf_rdwr(struct cdev *dev, struct uio *uio, int ioflag) { struct echodev_softc *sc; size_t tomove; int error; sc = dev->si_drv1; error = 0; sx_xlock(&sc->lock); if (uio->uio_offset < sc->length) { tomove = MIN(uio->uio_resid, sc->length - uio->uio_offset); error = uiomove(sc->buffer + uio->uio_offset, tomove, uio); } sx_xunlock(&sc->lock); return (error); } static int echobuf_poll(struct cdev *dev, int events, struct thread *td) { struct echodev_softc *sc; int revents; sc = dev->si_drv1; sx_slock(&sc->lock); if (sc->length != 0) revents = events & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM); else { revents = 0; if (events & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)) selrecord(td, &sc->rsel); } sx_sunlock(&sc->lock); return (revents); } static int echobuf_kqfilter(struct cdev *dev, struct knote *kn) { struct echodev_softc *sc; sc = dev->si_drv1; switch (kn->kn_filter) { case EVFILT_READ: case EVFILT_WRITE: kn->kn_fop = &echobuf_filterops; kn->kn_hook = sc; knlist_add(&sc->rsel.si_note, kn, 0); return (0); default: return (EINVAL); } } static int echobuf_kqevent(struct knote *kn, long hint) { struct echodev_softc *sc; sc = kn->kn_hook; kn->kn_data = sc->length; return (kn->kn_data > 0); } static void echobuf_kqdetach(struct knote *kn) { struct echodev_softc *sc; sc = kn->kn_hook; knlist_remove(&sc->rsel.si_note, kn, 0); } static d_ioctl_t echostream_ioctl; static d_read_t echostream_read; static d_write_t echostream_write; static d_poll_t echostream_poll; static d_kqfilter_t echostream_kqfilter; static int echostream_kqread_event(struct knote *, long); static void echostream_kqread_detach(struct knote *); static int echostream_kqwrite_event(struct knote *, long); static void echostream_kqwrite_detach(struct knote *); static struct filterops echostream_read_filterops = { .f_isfd = 1, .f_attach = NULL, .f_detach = echostream_kqread_detach, .f_event = echostream_kqread_event, }; static struct filterops echostream_write_filterops = { .f_isfd = 1, .f_attach = NULL, .f_detach = echostream_kqwrite_detach, .f_event = echostream_kqwrite_event, }; static struct cdevsw echostream_cdevsw = { .d_version = D_VERSION, .d_read = echostream_read, .d_write = echostream_write, .d_ioctl = echostream_ioctl, .d_poll = echostream_poll, .d_kqfilter = echostream_kqfilter, .d_name = "echostream", }; static int echostream_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct echodev_softc *sc; int error; error = 0; sc = dev->si_drv1; switch (cmd) { case ECHODEV_GBUFSIZE: sx_slock(&sc->lock); *(size_t *)data = sc->length; sx_sunlock(&sc->lock); break; case ECHODEV_CLEAR: sx_xlock(&sc->lock); if (sc->valid == sc->length) wakeup(sc); sc->valid = 0; selwakeup(&sc->wsel); KNOTE_UNLOCKED(&sc->wsel.si_note, 0); sx_xunlock(&sc->lock); break; case FIONBIO: /* IO_NDELAY is supported. */ break; case FIOASYNC: /* IO_ASYNC is not. */ if (*(int *)data != 0) error = EINVAL; break; case FIONREAD: sx_slock(&sc->lock); *(int *)data = MIN(INT_MAX, sc->valid); sx_sunlock(&sc->lock); break; case FIONWRITE: sx_slock(&sc->lock); *(int *)data = MIN(INT_MAX, sc->length - sc->valid); sx_sunlock(&sc->lock); break; default: error = ENOIOCTL; } return (error); } /* * Rather than deal with a ring buffer, just copy the remaining data * to the front of the buffer after each read. This simplifies the * logic even if it is less optimal. */ static int echostream_read(struct cdev *dev, struct uio *uio, int ioflag) { struct echodev_softc *sc; size_t toread, resid, nread; int error; if (uio->uio_resid == 0) return (0); sc = dev->si_drv1; sx_xlock(&sc->lock); /* Block waiting for bytes to read if needed. */ while (sc->valid == 0) { if (ioflag & IO_NDELAY) error = EWOULDBLOCK; else error = sx_sleep(sc, &sc->lock, PCATCH, "echord", 0); if (error) { sx_xunlock(&sc->lock); return (error); } } resid = uio->uio_resid; toread = MIN(resid, sc->valid); KASSERT(toread != 0, ("no data to read")); error = uiomove(sc->buffer, toread, uio); nread = resid - uio->uio_resid; if (nread != 0) { if (sc->valid == sc->length) wakeup(sc); sc->valid -= nread; bcopy(sc->buffer + nread, sc->buffer, sc->valid); selwakeup(&sc->wsel); KNOTE_UNLOCKED(&sc->wsel.si_note, 0); } sx_xunlock(&sc->lock); return (error); } static int echostream_write(struct cdev *dev, struct uio *uio, int ioflag) { struct echodev_softc *sc; size_t towrite, resid, nwritten; int error; if (uio->uio_resid == 0) return (0); sc = dev->si_drv1; sx_xlock(&sc->lock); /* Block waiting for space to write if needed. */ while (sc->valid == sc->length) { if (ioflag & IO_NDELAY) error = EWOULDBLOCK; else error = sx_sleep(sc, &sc->lock, PCATCH, "echowr", 0); if (error) { sx_xunlock(&sc->lock); return (error); } } resid = uio->uio_resid; towrite = MIN(resid, sc->length - sc->valid); KASSERT(towrite != 0, ("no space to write")); error = uiomove(sc->buffer + sc->valid, towrite, uio); nwritten = resid - uio->uio_resid; if (nwritten != 0) { if (sc->valid == 0) wakeup(sc); sc->valid += nwritten; selwakeup(&sc->rsel); KNOTE_UNLOCKED(&sc->rsel.si_note, 0); } sx_xunlock(&sc->lock); return (error); } static int echostream_poll(struct cdev *dev, int events, struct thread *td) { struct echodev_softc *sc; int revents; revents = 0; sc = dev->si_drv1; sx_slock(&sc->lock); if (sc->valid != 0) revents |= events & (POLLIN | POLLRDNORM); if (sc->valid < sc->length) revents |= events & (POLLOUT | POLLWRNORM); if (revents == 0) { if (events & (POLLIN | POLLRDNORM)) selrecord(td, &sc->rsel); if (events & (POLLOUT | POLLWRNORM)) selrecord(td, &sc->wsel); } sx_sunlock(&sc->lock); return (revents); } static int echostream_kqfilter(struct cdev *dev, struct knote *kn) { struct echodev_softc *sc; sc = dev->si_drv1; switch (kn->kn_filter) { case EVFILT_READ: kn->kn_fop = &echostream_read_filterops; kn->kn_hook = sc; knlist_add(&sc->rsel.si_note, kn, 0); return (0); case EVFILT_WRITE: kn->kn_fop = &echostream_write_filterops; kn->kn_hook = sc; knlist_add(&sc->wsel.si_note, kn, 0); return (0); default: return (EINVAL); } } static int echostream_kqread_event(struct knote *kn, long hint) { struct echodev_softc *sc; sc = kn->kn_hook; kn->kn_data = sc->valid; return (kn->kn_data > 0); } static void echostream_kqread_detach(struct knote *kn) { struct echodev_softc *sc; sc = kn->kn_hook; knlist_remove(&sc->rsel.si_note, kn, 0); } static int echostream_kqwrite_event(struct knote *kn, long hint) { struct echodev_softc *sc; sc = kn->kn_hook; kn->kn_data = (sc->length - sc->valid); return (kn->kn_data > 0); } static void echostream_kqwrite_detach(struct knote *kn) { struct echodev_softc *sc; sc = kn->kn_hook; knlist_remove(&sc->wsel.si_note, kn, 0); } static struct echodev_softc * echodev_create(struct cdevsw *cdevsw, const char *name, size_t size) { struct echodev_softc *sc; sc = malloc(sizeof(*sc), M_ECHODEV, M_WAITOK | M_ZERO); sx_init(&sc->lock, name); knlist_init_mtx(&sc->rsel.si_note, NULL); knlist_init_mtx(&sc->wsel.si_note, NULL); if (size != 0) { sc->buffer = malloc(size, M_ECHODEV, M_WAITOK | M_ZERO); sc->length = size; } sc->cdev = make_dev(cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "%s", name); if (sc->cdev == NULL) { knlist_destroy(&sc->rsel.si_note); knlist_destroy(&sc->wsel.si_note); sx_destroy(&sc->lock); free(sc->buffer, M_ECHODEV); free(sc, M_ECHODEV); printf("echodev: Failed to create /dev/%s\n", name); return (NULL); } sc->cdev->si_drv1 = sc; return (sc); } static void echodev_destroy(struct echodev_softc *sc) { destroy_dev(sc->cdev); knlist_destroy(&sc->rsel.si_note); knlist_destroy(&sc->wsel.si_note); seldrain(&sc->rsel); seldrain(&sc->wsel); sx_destroy(&sc->lock); free(sc->buffer, M_ECHODEV); free(sc, M_ECHODEV); } static int echodev_modevent(module_t mod, int type, void *arg) { static struct echodev_softc *echobuf, *echostream; switch (type) { case MOD_LOAD: echobuf = echodev_create(&echobuf_cdevsw, "echobuf", 0); echostream = echodev_create(&echostream_cdevsw, "echostream", 64 * 1024); break; case MOD_UNLOAD: if (echobuf != NULL) echodev_destroy(echobuf); if (echostream != NULL) echodev_destroy(echostream); break; case MOD_SHUTDOWN: break; default: return (EOPNOTSUPP); } return (0); } DEV_MODULE(echodev, echodev_modevent, NULL);