Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/mmio/mmio_emul.c
- This file was added.
| #include <sys/cdefs.h> | |||||
| __FBSDID("$FreeBSD$"); | |||||
| #include <sys/linker_set.h> | |||||
| #include <sys/param.h> | |||||
| #include <sys/types.h> | |||||
| #include <errno.h> | |||||
| #include <pthread.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #include "arm64/mem.h" | |||||
| #include "mmio_emul.h" | |||||
| #include "mmio_irq.h" | |||||
| #define DEVEMU_MEMLIMIT 0xFD00000000UL | |||||
| #define DEVEMU_MEMBASE 0xD000000000UL | |||||
| #define MEM_ROUNDUP (1 << 20) | |||||
| #ifndef max | |||||
| # define max(A, B) ((A) > (B) ? (A) : (B)) | |||||
| #endif | |||||
| static uint64_t mmio_membase; | |||||
| SET_DECLARE(mmio_set, struct mmio_devemu); | |||||
| static struct mmio_devemu *mmio_finddef(const char *name); | |||||
| static void mmio_lintr_route(struct mmio_devinst *di); | |||||
| static void mmio_lintr_update(struct mmio_devinst *di); | |||||
| static struct mmio_emul_info { | |||||
| uint64_t size; /* address size */ | |||||
| uint64_t baddr; /* address */ | |||||
| int64_t irq; /* device interrupt number */ | |||||
| char *name; /* device name */ | |||||
| char *arg; /* device arguments */ | |||||
| struct mmio_emul_info *next; /* pointer for linked list */ | |||||
| struct mmio_devinst *di; /* pointer to device instance */ | |||||
| } *mmio_emul_info_head = NULL; | |||||
| /* | |||||
| * MMIO options are in the form: | |||||
| * | |||||
| * <size>@<base_addr>#<irq>:<emul>[,<config>] | |||||
| * | |||||
| * - size is the number of bytes required for the device mmio | |||||
| * - base_addr is the base address for the MMIO mapped device; | |||||
| * - irq specifies the device interrupt number the value MUST be a DECIMAL | |||||
| * integer; if the device does not use interrupts, use -1 | |||||
| * - emul is a string describing the type of device - e.g., virtio-net; | |||||
| * - config is an optional string, depending on the device, that is used | |||||
| * for configuration | |||||
| * | |||||
| * Examples of use: | |||||
| * 0x200@0x100000#25:virtio-net,tap0 | |||||
| * 0x100@0x200000#-1:dummy | |||||
| */ | |||||
| static void | |||||
| mmio_parse_opts_usage(const char *args) | |||||
| { | |||||
| fprintf(stderr, "Invalid mmio arguments \"%s\"\r\n", args); | |||||
| } | |||||
| /* | |||||
| * checks if two memory regions overlap | |||||
| * checks are not required if one of the pointers is null | |||||
| */ | |||||
| static int | |||||
| mmio_mem_overlap(uint64_t pa, uint64_t sa, uint64_t pb, uint64_t sb) | |||||
| { | |||||
| #define IN_INTERVAL(lower, value, upper) \ | |||||
| (((lower) < (value)) && ((value) < (upper))) | |||||
| if ((pa == 0) || (pb == 0)) | |||||
| return 0; | |||||
| if (IN_INTERVAL(pa, pb, pa + sa) && | |||||
| IN_INTERVAL(pb, pa, pb + sb)) | |||||
| return 1; | |||||
| return 0; | |||||
| #undef IN_INTERVAL | |||||
| } | |||||
| int | |||||
| mmio_parse_opts(const char *args) | |||||
| { | |||||
| char *emul, *config, *str; | |||||
| uint64_t size, baddr; | |||||
| int64_t irq; | |||||
| int error; | |||||
| struct mmio_emul_info *dif; | |||||
| error = -1; | |||||
| emul = config = NULL; | |||||
| baddr = 0, size = 0; | |||||
| str = strdup(args); | |||||
| if ((emul = strchr(str, ':')) != NULL) { | |||||
| *emul++ = '\0'; | |||||
| /* <size>@<base-addr>#<irq> */ | |||||
| if (sscanf(str, "%jx@%jx#%jd", &size, &baddr, &irq) != 3 && | |||||
| sscanf(str, "%jx@%jx#%jd", &size, &baddr, &irq) != 3) { | |||||
| mmio_parse_opts_usage(str); | |||||
| goto parse_error; | |||||
| } | |||||
| } else { | |||||
| mmio_parse_opts_usage(str); | |||||
| goto parse_error; | |||||
| } | |||||
| if ((config = strchr(emul, ',')) != NULL) | |||||
| *config++ = '\0'; | |||||
| /* | |||||
| * check if the required address can be obtained; | |||||
| * if an address has not been requested, ignore the checks | |||||
| * (however, an address will have to be later identified) | |||||
| */ | |||||
| if (baddr != 0) { | |||||
| for (dif = mmio_emul_info_head; dif != NULL; dif = dif->next) | |||||
| if (mmio_mem_overlap(dif->baddr, dif->size, | |||||
| baddr, size)) | |||||
| break; | |||||
| if (dif != NULL) { | |||||
| fprintf(stderr, "The requested address 0x%jx is " | |||||
| "already bound or overlapping\r\n", baddr); | |||||
| error = EINVAL; | |||||
| goto parse_error; | |||||
| } | |||||
| } | |||||
| dif = calloc(1, sizeof(struct mmio_emul_info)); | |||||
| if (dif == NULL) { | |||||
| error = ENOMEM; | |||||
| goto parse_error; | |||||
| } | |||||
| dif->next = mmio_emul_info_head; | |||||
| mmio_emul_info_head = dif; | |||||
| dif->size = size; | |||||
| dif->baddr = baddr; | |||||
| dif->irq = irq; | |||||
| if ((emul != NULL) && (strlen(emul)) > 0) | |||||
| dif->name = strdup(emul); | |||||
| else | |||||
| dif->name = NULL; | |||||
| if ((config != NULL) && (strlen(config)) > 0) | |||||
| dif->arg = strdup(config); | |||||
| else | |||||
| dif->arg = NULL; | |||||
| error = 0; | |||||
| parse_error: | |||||
| free(str); | |||||
| return error; | |||||
| } | |||||
| static int | |||||
| mmio_mem_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr, | |||||
| int size, uint64_t *val, void *arg1, long arg2) | |||||
| { | |||||
| struct mmio_devinst *di = arg1; | |||||
| struct mmio_devemu *de = di->pi_d; | |||||
| uint64_t offset; | |||||
| int bidx = (int) arg2; | |||||
| assert(di->addr.baddr <= addr && | |||||
| addr + size <= di->addr.baddr + di->addr.size); | |||||
| offset = addr - di->addr.baddr; | |||||
| if (dir == MEM_F_WRITE) { | |||||
| if (size == 8) { | |||||
| (*de->de_write)(ctx, vcpu, di, bidx, offset, | |||||
| 4, *val & 0xffffffff); | |||||
| (*de->de_write)(ctx, vcpu, di, bidx, offset + 4, | |||||
| 4, *val >> 32); | |||||
| } else { | |||||
| (*de->de_write)(ctx, vcpu, di, bidx, offset, | |||||
| size, *val); | |||||
| } | |||||
| } else { | |||||
| if (size == 8) { | |||||
| *val = (*de->de_read)(ctx, vcpu, di, bidx, | |||||
| offset, 4); | |||||
| *val |= (*de->de_read)(ctx, vcpu, di, bidx, | |||||
| offset + 4, 4) << 32; | |||||
| } else { | |||||
| *val = (*de->de_read)(ctx, vcpu, di, bidx, | |||||
| offset, size); | |||||
| } | |||||
| } | |||||
| return (0); | |||||
| } | |||||
| static void | |||||
| modify_mmio_registration(struct mmio_devinst *di, int registration) | |||||
| { | |||||
| int error; | |||||
| struct mem_range mr; | |||||
| bzero(&mr, sizeof(struct mem_range)); | |||||
| mr.name = di->pi_name; | |||||
| mr.base = di->addr.baddr; | |||||
| mr.size = di->addr.size; | |||||
| if (registration) { | |||||
| mr.flags = MEM_F_RW; | |||||
| mr.handler = mmio_mem_handler; | |||||
| mr.arg1 = di; | |||||
| mr.arg2 = 0; | |||||
| error = register_mem(&mr); | |||||
| } else { | |||||
| error = unregister_mem(&mr); | |||||
| } | |||||
| assert(error == 0); | |||||
| } | |||||
| static void | |||||
| register_mmio(struct mmio_devinst *di) | |||||
| { | |||||
| return modify_mmio_registration(di, 1); | |||||
| } | |||||
| static void | |||||
| unregister_mmio(struct mmio_devinst *di) | |||||
| { | |||||
| return modify_mmio_registration(di, 0); | |||||
| } | |||||
| /* | |||||
| * Update the MMIO address that is decoded | |||||
| */ | |||||
| static void | |||||
| update_mem_address(struct mmio_devinst *di, uint64_t addr) | |||||
| { | |||||
| /* TODO: check if the decoding is running */ | |||||
| unregister_mmio(di); | |||||
| di->addr.baddr = addr; | |||||
| register_mmio(di); | |||||
| } | |||||
| static int | |||||
| mmio_alloc_resource(uint64_t *baseptr, uint64_t limit, uint64_t size, | |||||
| uint64_t *addr) | |||||
| { | |||||
| uint64_t base; | |||||
| assert((size & (size - 1)) == 0); /* must be a power of 2 */ | |||||
| base = roundup2(*baseptr, size); | |||||
| if (base + size <= limit) { | |||||
| *addr = base; | |||||
| *baseptr = base + size; | |||||
| return (0); | |||||
| } else | |||||
| return (-1); | |||||
| } | |||||
| int | |||||
| mmio_alloc_mem(struct mmio_devinst *di) | |||||
| { | |||||
| int error; | |||||
| uint64_t *baseptr, limit, addr, size; | |||||
| baseptr = &di->addr.baddr; | |||||
| size = di->addr.size; | |||||
| limit = DEVEMU_MEMLIMIT; | |||||
| if ((size & (size - 1)) != 0) | |||||
| /* Round up to a power of 2 */ | |||||
| size = 1UL << flsl(size); | |||||
| error = mmio_alloc_resource(baseptr, limit, size, &addr); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| di->addr.baddr = addr; | |||||
| register_mmio(di); | |||||
| return (0); | |||||
| } | |||||
| static struct mmio_devemu * | |||||
| mmio_finddev(char *name) | |||||
| { | |||||
| struct mmio_devemu **dpp, *dp; | |||||
| SET_FOREACH(dpp, mmio_set) { | |||||
| dp = *dpp; | |||||
| if (!strcmp(dp->de_emu, name)) | |||||
| return (dp); | |||||
| } | |||||
| return (NULL); | |||||
| } | |||||
| static int | |||||
| mmio_init(struct vmctx *ctx, struct mmio_devemu *de, struct mmio_emul_info *dif) | |||||
| { | |||||
| struct mmio_devinst *di; | |||||
| int error; | |||||
| di = calloc(1, sizeof(struct mmio_devinst)); | |||||
| if (di == NULL) | |||||
| return (ENOMEM); | |||||
| di->pi_d = de; | |||||
| di->pi_vmctx = ctx; | |||||
| snprintf(di->pi_name, DI_NAMESZ, "%s-mmio", de->de_emu); | |||||
| di->di_lintr.state = IDLE; | |||||
| di->di_lintr.irq = dif->irq; | |||||
| pthread_mutex_init(&di->di_lintr.lock, NULL); | |||||
| di->addr.baddr = dif->baddr; | |||||
| di->addr.size = dif->size; | |||||
| /* some devices (e.g., virtio-net) use these as uniquifiers; irq number | |||||
| * should be unique and sufficient */ | |||||
| di->pi_slot = dif->irq; | |||||
| di->di_func = dif->irq; | |||||
| error = (*de->de_init)(ctx, di, dif->arg); | |||||
| if (error == 0) { | |||||
| dif->di = di; | |||||
| } else { | |||||
| fprintf(stderr, "Device \"%s\": initialization failed\r\n", | |||||
| di->pi_name); | |||||
| fprintf(stderr, "Device arguments were: %s\r\n", dif->arg); | |||||
| free(di); | |||||
| } | |||||
| return (error); | |||||
| } | |||||
| static void | |||||
| init_mmio_error(const char *name) | |||||
| { | |||||
| struct mmio_devemu **mdpp, *mdp; | |||||
| fprintf(stderr, "Device \"%s\" does not exist\r\n", name); | |||||
| fprintf(stderr, "The following devices are available:\r\n"); | |||||
| SET_FOREACH(mdpp, mmio_set) { | |||||
| mdp = *mdpp; | |||||
| fprintf(stderr, "\t%s\r\n", mdp->de_emu); | |||||
| } | |||||
| } | |||||
| int init_mmio(struct vmctx *ctx) | |||||
| { | |||||
| struct mmio_devemu *de; | |||||
| struct mmio_emul_info *dif; | |||||
| int error; | |||||
| mmio_membase = DEVEMU_MEMBASE; | |||||
| for (dif = mmio_emul_info_head; dif != NULL; dif = dif->next) { | |||||
| if (dif->name == NULL) | |||||
| continue; | |||||
| de = mmio_finddev(dif->name); | |||||
| if (de == NULL) { | |||||
| init_mmio_error(dif->name); | |||||
| return (1); | |||||
| } | |||||
| error = mmio_init(ctx, de, dif); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| /* | |||||
| * as specified in the amd64 implementation, add some | |||||
| * slop to the memory resources decoded, in order to | |||||
| * give the guest some flexibility to reprogram the addresses | |||||
| */ | |||||
| mmio_membase += MEM_ROUNDUP; | |||||
| mmio_membase = roundup2(mmio_membase, MEM_ROUNDUP); | |||||
| } | |||||
| /* activate the interrupts */ | |||||
| for (dif = mmio_emul_info_head; dif != NULL; dif = dif->next) | |||||
| if (dif->di != NULL) | |||||
| mmio_lintr_route(dif->di); | |||||
| /* TODO: register fallback handlers? */ | |||||
| return (0); | |||||
| } | |||||
| void | |||||
| mmio_lintr_request(struct mmio_devinst *di) | |||||
| { | |||||
| /* do nothing */ | |||||
| } | |||||
| static void | |||||
| mmio_lintr_route(struct mmio_devinst *di) | |||||
| { | |||||
| /* do nothing */ | |||||
| } | |||||
| void | |||||
| mmio_lintr_assert(struct mmio_devinst *di) | |||||
| { | |||||
| pthread_mutex_lock(&di->di_lintr.lock); | |||||
| if (di->di_lintr.state == IDLE) { | |||||
| di->di_lintr.state = ASSERTED; | |||||
| mmio_irq_assert(di); | |||||
| } | |||||
| pthread_mutex_unlock(&di->di_lintr.lock); | |||||
| } | |||||
| void | |||||
| mmio_lintr_deassert(struct mmio_devinst *di) | |||||
| { | |||||
| pthread_mutex_lock(&di->di_lintr.lock); | |||||
| if (di->di_lintr.state == ASSERTED) { | |||||
| mmio_irq_deassert(di); | |||||
| di->di_lintr.state = IDLE; | |||||
| } else if (di->di_lintr.state == PENDING) { | |||||
| di->di_lintr.state = IDLE; | |||||
| } | |||||
| pthread_mutex_unlock(&di->di_lintr.lock); | |||||
| } | |||||
| /* TODO: Add dummy? */ | |||||