/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2022 Juniper Networks Inc. * * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * tlbdemo: A dummy driver which reserves contiguous physical memory at boot * time and allows it to be mapped into userspace using largepage shared memory * objects. * * The driver creates one global shared memory object for each page size index. * A page size index is a valid index into the pagesizes[] array, which records * the valid superpage sizes for the system. During boot, the shared memory * objects are initialized and populated with contiguous memory. Then, the * driver creates one /dev node per page size, so /dev/tlbdemo1 is used to map * 2MB pages, /dev/tlbdemo2 is used to map 8MB pages, and so on. * * When a tlbdemo device node is opened, the driver returns a descriptor for the * corresponding shared memory object. Userspace can then use mmap() to map the * shared memory object. * * In the kernel, the shared memory currently must be accessed via the direct * map. The tlbdemo_map() function returns a pointer to the requested region. * In particular, a reservation of multiple large pages is not physically * contiguous. In the kernel each large page is accessed through the direct * map, and so the driver has to manage a pointer to each large page. With some * additional work it would be possible to map large pages contiguously into the * kernel map. */ static struct sx tlbdemo_mtx; struct tlbdemo_store { struct shmfd *shmfd; vm_size_t size; int count; }; /* * Number of pages of each size to reserve at boot time. */ static struct tlbdemo_store tlbdemo_reservations[MAXPAGESIZES] = { [1] = { .count = 1 }, /* 2MB */ [2] = { .count = 1 }, /* 8MB */ [3] = { .count = 1 }, /* 32MB */ [4] = { .count = 1 }, /* 128MB */ [5] = { .count = 1 }, /* 512MB */ }; /* * Get a pointer to the large page of size pagesizes[psind] at offset * offset * pagesizes[psind]. */ static void * __unused tlbdemo_map(int psind, u_int offset) { struct tlbdemo_store *s; vm_page_t m; vm_pindex_t pi; if (psind < 1 || psind >= MAXPAGESIZES) return (NULL); s = &tlbdemo_reservations[psind]; if (s->shmfd == NULL || offset >= s->count) return (NULL); pi = atop(offset * s->size); VM_OBJECT_RLOCK(s->shmfd->shm_object); m = vm_page_lookup(s->shmfd->shm_object, pi); KASSERT(m != NULL, ("%s: no page found, psind %d pi %#lx", __func__, psind, pi)); VM_OBJECT_RUNLOCK(s->shmfd->shm_object); return ((void *)MIPS_PHYS_TO_DIRECT(VM_PAGE_TO_PHYS(m))); } #if 0 /* * Populate the shared memory with 0xa, just to verify that userspace is mapping * the same physical pages. */ static void tlbdemo_write(void) { struct tlbdemo_store *s; for (int i = 1; i < MAXPAGESIZES; i++) { s = &tlbdemo_reservations[i]; if (s->shmfd == NULL) continue; for (int j = 0; j < s->count; j++) { void *addr = tlbdemo_map(i, j); memset(addr, 0xa, s->size); } } } SYSINIT(tlbdemo_write, SI_SUB_SYSV_SHM + 2, SI_ORDER_ANY, tlbdemo_write, NULL); #endif static void tlbdemo_store(void *arg __unused) { struct shm_largepage_conf conf; struct shmfd *shmfd; struct tlbdemo_store *s; int error; for (int i = 1; i < MAXPAGESIZES; i++) { s = &tlbdemo_reservations[i]; s->size = pagesizes[i]; shmfd = shm_alloc(curthread->td_ucred, 0600, true); if (shmfd == NULL) { printf("%s:%d\n", __func__, __LINE__); break; } conf.psind = i; conf.alloc_policy = SHM_LARGEPAGE_ALLOC_NOWAIT; error = shm_largepage_setconf(shmfd, &conf); if (error != 0) { printf("%s: failed to set largepage conf: %d\n", __func__, error); shm_drop(shmfd); continue; } error = shm_dotruncate(shmfd, s->count * s->size); if (error != 0) { printf("%s: failed to truncate largepage obj: %d\n", __func__, error); shm_drop(shmfd); continue; } s->shmfd = shmfd; } } SYSINIT(tlbdemo_store, SI_SUB_INTRINSIC, SI_ORDER_ANY, tlbdemo_store, NULL); static int tlbdemo_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *fp) { struct tlbdemo_store *s; int error; error = 0; sx_xlock(&tlbdemo_mtx); s = dev->si_drv1; if (s->shmfd != NULL) { shm_hold(s->shmfd); shm_finit(s->shmfd, fflags & (FREAD | FWRITE), fp); } else { error = ENOENT; } sx_xunlock(&tlbdemo_mtx); return (0); } static struct cdevsw tlbdemo_cdevsw = { .d_name = "tlbdemo", .d_version = D_VERSION, .d_fdopen = tlbdemo_fdopen, }; static int tlbdemo_modevent(module_t mod __unused, int type, void *data __unused) { static struct cdev *tlbdemo_dev[MAXPAGESIZES]; struct make_dev_args args; int error; switch (type) { case MOD_LOAD: sx_init(&tlbdemo_mtx, "tlbdemo"); for (int i = 1; i < MAXPAGESIZES; i++) { make_dev_args_init(&args); args.mda_devsw = &tlbdemo_cdevsw; args.mda_uid = UID_ROOT; args.mda_gid = GID_WHEEL; args.mda_mode = 0600; args.mda_si_drv1 = &tlbdemo_reservations[i]; args.mda_si_drv2 = NULL; error = make_dev_s(&args, &tlbdemo_dev[i], "tlbdemo%d", i); if (error != 0) break; } break; case MOD_UNLOAD: for (int i = 1; i < MAXPAGESIZES; i++) { if (tlbdemo_dev[i] != NULL) { destroy_dev(tlbdemo_dev[i]); tlbdemo_dev[i] = NULL; } } sx_destroy(&tlbdemo_mtx); break; } return (0); } DEV_MODULE(tlbdemo, tlbdemo_modevent, NULL); MODULE_VERSION(tlbdemo, 1);