/*- * */ #include __FBSDID("$FreeBSD$"); /* * Example code showing how to use d_mmap_single() to map buffers with * non-default cache modes in userland. * * This is a really gross hack. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LAPIC_LEN PAGE_SIZE struct patdev_softc { struct cdev *cdev; vm_object_t mem; struct sglist *sg; vm_object_t sgobj; void *sgptr; struct sx lock; }; static d_mmap_single_t pat_mmap_single; static d_read_t pat_read; static struct patdev_softc pat_sc; static struct cdevsw pat_devsw = { .d_version = D_VERSION, .d_flags = D_MEM, .d_read = pat_read, .d_mmap_single = pat_mmap_single, .d_name = "pat" }; static int pat_read(struct cdev *dev, struct uio *uio, int ioflag) { struct patdev_softc *sc; size_t len; int error; sc = dev->si_drv1; sx_slock(&sc->lock); if (uio->uio_offset < LAPIC_LEN) { len = MIN(uio->uio_resid, LAPIC_LEN - uio->uio_offset); error = uiomove((char *)sc->sgptr + uio->uio_offset, len, uio); } else error = 0; sx_sunlock(&sc->lock); return (error); } static int pat_mmap_single(struct cdev *dev, vm_ooffset_t *offset, vm_size_t size, vm_object_t *object, int nprot) { struct patdev_softc *sc; int error; sc = dev->si_drv1; error = 0; sx_xlock(&sc->lock); switch (*offset) { case 0: /* * The first mmap() attempt with an offset of 0 creates * a new memory object with the requested size. Subsequent * mmap()'s map the same object. */ if (sc->mem == NULL) { /* * Note that this does not wire any backing * pages. I could do that later before a DMA * was started by wiring pages (even just * using the userland mapping to do that) * while the DMA was in-progress and unwiring * them later. */ sc->mem = vm_pager_allocate(OBJT_DEFAULT, NULL, size, VM_PROT_DEFAULT, 0); VM_OBJECT_LOCK(sc->mem); vm_object_clear_flag(sc->mem, OBJ_ONEMAPPING); vm_object_set_flag(sc->mem, OBJ_NOSPLIT); vm_object_set_cache_mode(sc->mem, VM_CACHE_WRITE_COMBINING); VM_OBJECT_UNLOCK(sc->mem); } vm_object_reference(sc->mem); *object = sc->mem; break; case PAGE_SIZE: /* Map the local APIC. */ vm_object_reference(sc->sgobj); *object = sc->sgobj; *offset = 0; break; default: /* Use ENODEV to fallback to d_mmap(). */ error = EINVAL; break; } sx_xunlock(&sc->lock); return (error); } static int pat_attach(struct patdev_softc *sc) { vm_offset_t va; int rv; bzero(sc, sizeof(*sc)); sx_init(&sc->lock, "patdev"); sc->cdev = make_dev(&pat_devsw, 0, UID_ROOT, GID_WHEEL, 0640, "pat"); sc->cdev->si_drv1 = sc; /* Create a scatter/gather list that maps the local APIC. */ sc->sg = sglist_alloc(1, M_WAITOK); sglist_append_phys(sc->sg, lapic_paddr, LAPIC_LEN); /* Create a VM object that is backed by the scatter/gather list. */ sc->sgobj = vm_pager_allocate(OBJT_SG, sc->sg, LAPIC_LEN, VM_PROT_READ, 0); VM_OBJECT_LOCK(sc->sgobj); vm_object_set_cache_mode(sc->sgobj, VM_CACHE_UNCACHEABLE); VM_OBJECT_UNLOCK(sc->sgobj); /* * Wire this into KVA for pat_read(). * * XXX: VM_MAP_WIRE_SYSTEM implies VM_PROT_WRITE during the faults * to wire the pages, so we have to map this read/write even though * I only want to allow read access. */ vm_object_reference(sc->sgobj); va = vm_map_min(kernel_map); rv = vm_map_find(kernel_map, sc->sgobj, 0, &va, LAPIC_LEN, TRUE, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE, 0); if (rv != KERN_SUCCESS) vm_object_deallocate(sc->sgobj); else { rv = vm_map_wire(kernel_map, va, va + LAPIC_LEN, VM_MAP_WIRE_SYSTEM | VM_MAP_WIRE_NOHOLES); if (rv != KERN_SUCCESS) vm_map_remove(kernel_map, va, va + LAPIC_LEN); else sc->sgptr = (void *)va; } return (0); } static int pat_detach(struct patdev_softc *sc) { vm_offset_t va; destroy_dev(sc->cdev); sx_xlock(&sc->lock); vm_object_deallocate(sc->mem); sx_xunlock(&sc->lock); vm_object_deallocate(sc->sgobj); if (sc->sgptr != NULL) { va = (vm_offset_t)sc->sgptr; vm_map_remove(kernel_map, va, va + LAPIC_LEN); } sglist_free(sc->sg); sx_destroy(&sc->lock); return (0); } static int mod_event(struct module *module, int cmd, void *arg) { int error = 0; switch (cmd) { case MOD_LOAD: error = pat_attach(&pat_sc); break; case MOD_UNLOAD: error = pat_detach(&pat_sc); break; default: error = EINVAL; break; } return (error); } DEV_MODULE(patdev, mod_event, NULL); MODULE_VERSION(patdev, 1);