388 changes: 388 additions & 0 deletions src-gnu/pci_user-gnu.c
@@ -0,0 +1,388 @@
/*-
* Copyright (c) 2014 Antti Kantee. All Rights Reserved.
* Copyright (c) 2015 Robert Millan
* Copyright (c) 2009,2010 Zheng Da
*
* 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 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.
*/

/*-
* intrthread() is based on code from intloop() function, obtained
* from the Debian version of the Hurd (in libddekit/interrupt.c),
* which is written by Zheng Da and is licensed under the same terms
* as the rest of this file. Public statement available at:
*
* https://www.freelists.org/post/rumpkernel-users/licensing-of-intloop-in-libddekitinterruptc,1
* https://lists.gnu.org/archive/html/bug-hurd/2015-11/msg00021.html
* https://lists.debian.org/debian-hurd/2015/11/msg00021.html
*/

#define _GNU_SOURCE 1

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/queue.h>
#include <sys/io.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <rump/rumpuser_component.h>

#include <hurd.h>
#include <device/device.h>

#include <pciaccess.h>

#include "pci_user.h"
#include "experimental_U.h"
#include <device/intr.h>

#define RUMP_IRQ_PRIO 2

/* highest dev for which we've returned something sensible in config space */
static pthread_mutex_t genericmtx = PTHREAD_MUTEX_INITIALIZER;
static int highestdev = -1;

static mach_port_t master_host;
static mach_port_t master_device;

int
rumpcomp_pci_iospace_init(void)
{
if (ioperm(0, 0x10000, 1))
return rumpuser_component_errtrans(errno);

return 0;
}

#define NUMDEVS 32
static struct pci_device *pci_devices[NUMDEVS];

static void
pci_userspace_init(void)
{
/* FIXME: add a hook to make rump call this, once and only once */
static int is_init = 0;
if (is_init)
return;
is_init = 1;

if (get_privileged_ports (&master_host, &master_device))
err(1, "get_privileged_ports");

pci_system_init ();
struct pci_device_iterator *dev_iter;
struct pci_device *pci_dev;
dev_iter = pci_slot_match_iterator_create (NULL);
int i = 0;
while ((pci_dev = pci_device_next (dev_iter)) != NULL) {
pci_devices[i++] = pci_dev;
}
}

void *
rumpcomp_pci_map(unsigned long addr, unsigned long len)
{
errno = rumpuser_component_errtrans(ENOSYS);
return NULL;
}

int
rumpcomp_pci_confread(unsigned bus, unsigned dev, unsigned fun,
int reg, unsigned int *rv)
{
*rv = 0xffffffff;
if (fun != 0 || bus != 0)
return 1;

if (dev >= NUMDEVS)
return 1;

pci_userspace_init();

pci_device_cfg_read_u32(pci_devices[dev], rv, reg);

pthread_mutex_lock(&genericmtx);
if ((int)dev > highestdev)
highestdev = dev;
pthread_mutex_unlock(&genericmtx);

return 0;
}

int
rumpcomp_pci_confwrite(unsigned bus, unsigned dev, unsigned fun,
int reg, unsigned int v)
{
assert(bus == 0 && fun == 0);

if (dev >= NUMDEVS)
return 1;

pci_userspace_init();

pci_device_cfg_write_u32(pci_devices[dev], v, reg);

return 0;
}

/* this is a multifunction data structure! */
struct irq {
unsigned magic_cookie;
unsigned device;

int (*handler)(void *);
void *data;
int intrline;

LIST_ENTRY(irq) entries;
};
static LIST_HEAD(, irq) irqs = LIST_HEAD_INITIALIZER(&irqs);

static void *
intrthread(void *arg)
{
struct irq *irq = arg;
mach_port_t delivery_port;
mach_port_t pset, psetcntl;
int ret;
int val;

rumpuser_component_kthread();

ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE,
&delivery_port);
if (ret)
err(ret, "mach_port_allocate");

ret = thread_get_assignment (mach_thread_self (), &pset);
if (ret)
err(ret, "thread_get_assignment");

ret = host_processor_set_priv (master_host, pset, &psetcntl);
if (ret)
err(ret, "host_processor_set_priv");

thread_max_priority (mach_thread_self (), psetcntl, 0);
ret = thread_priority (mach_thread_self (), RUMP_IRQ_PRIO, 0);
if (ret)
err(ret, "thread_priority");

ret = device_intr_register(master_device, irq->intrline,
0, 0x04000000, delivery_port,
MACH_MSG_TYPE_MAKE_SEND);
if (ret) {
warn("device_intr_register");
return 0;
}

device_intr_enable (master_device, irq->intrline, TRUE);

int irq_server (mach_msg_header_t *inp, mach_msg_header_t *outp) {
mach_intr_notification_t *intr_header = (mach_intr_notification_t *) inp;

((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY;
if (inp->msgh_id != MACH_INTR_NOTIFY)
return 0;

/* It's an interrupt not for us. It shouldn't happen. */
if (intr_header->line != irq->intrline) {
printf ("We get interrupt %d, %d is expected",
intr_header->line, irq->intrline);
return 1;
}

rumpcomp_pci_confread(0, irq->device, 0, 0x04, &val);
if (val & 0x400) {
printf("interrupt disabled!\n");
val &= ~0x400;
rumpcomp_pci_confwrite(0, irq->device, 0, 0x04, val);
}

rumpuser_component_schedule(NULL);
irq->handler(irq->data);
rumpuser_component_unschedule();

/* If the irq has been disabled by the linux device,
* we don't need to reenable the real one. */
device_intr_enable (master_device, irq->intrline, TRUE);

return 1;
}

mach_msg_server (irq_server, 0, delivery_port);

return NULL;
}

int
rumpcomp_pci_irq_map(unsigned bus, unsigned device, unsigned fun,
int intrline, unsigned cookie)
{
struct irq *irq;

irq = malloc(sizeof(*irq));
if (irq == NULL)
return ENOENT;

irq->magic_cookie = cookie;
irq->device = device;
irq->intrline = intrline;

pthread_mutex_lock(&genericmtx);
LIST_INSERT_HEAD(&irqs, irq, entries);
pthread_mutex_unlock(&genericmtx);

return 0;
}

void *
rumpcomp_pci_irq_establish(unsigned cookie, int (*handler)(void *), void *data)
{
struct irq *irq;
pthread_t pt;

pthread_mutex_lock(&genericmtx);
LIST_FOREACH(irq, &irqs, entries) {
if (irq->magic_cookie == cookie)
break;
}
pthread_mutex_unlock(&genericmtx);
if (!irq)
return NULL;

irq->handler = handler;
irq->data = data;

if (pthread_create(&pt, NULL, intrthread, irq) != 0) {
warn("interrupt thread create");
free(irq);
return NULL;
}

return irq;
}

struct virt_to_mach {
unsigned long pa;
unsigned long va;

LIST_ENTRY(virt_to_mach) entries;
};
static LIST_HEAD(, virt_to_mach) virt_to_mach_list = LIST_HEAD_INITIALIZER(&virt_to_mach_list);

/*
* Allocate physically contiguous memory. We could be slightly more
* efficient here and implement an allocator on top of the
* hugepages to ensure that they get used more efficiently. TODO4u
*/
int
rumpcomp_pci_dmalloc(size_t size, size_t align,
unsigned long *pap, unsigned long *vap)
{
struct virt_to_mach *virt_to_mach;
const size_t pagesize = getpagesize();

if (align > pagesize) {
warnx("requested alignment (%x) is larger than page size (%x)", align, pagesize);
return 1;
}

pci_userspace_init();

if (vm_allocate_contiguous (master_host, mach_task_self(), vap, pap, size)) {
warn("vm_allocate_contiguous");
return 1;
}

assert(*pap);

virt_to_mach = malloc(sizeof(*virt_to_mach));
if (virt_to_mach == NULL)
return errno;

virt_to_mach->pa = *pap;
virt_to_mach->va = *vap;

LIST_INSERT_HEAD(&virt_to_mach_list, virt_to_mach, entries);

return 0;
}

void
rumpcomp_pci_dmafree(unsigned long vap, size_t size)
{
void *v = (void *) vap;
struct virt_to_mach *virt_to_mach;

munmap(v, size);

LIST_FOREACH(virt_to_mach, &virt_to_mach_list, entries) {
if (virt_to_mach->va == (uintptr_t)vap) {
LIST_REMOVE(virt_to_mach, entries);
break;
}
}
}

/*
* "maps" dma memory into virtual address space. For now, we just
* rely on it already being mapped. This means that support for
* >1 segs is not supported. We could call mremap() ...
*/
int
rumpcomp_pci_dmamem_map(struct rumpcomp_pci_dmaseg *dss, size_t nseg,
size_t totlen, void **vap)
{
if (nseg > 1) {
printf("dmamem_map for >1 seg currently not supported");
return ENOTSUP;
}

*vap = (void *)dss[0].ds_vacookie;
return 0;
}

/*
* Finds the physical address for the given virtual address.
*/
unsigned long
rumpcomp_pci_virt_to_mach(void *virt)
{
struct virt_to_mach *virt_to_mach;

/* FIXME: find a Mach API to do this properly, and drop the bookkeeping */

LIST_FOREACH(virt_to_mach, &virt_to_mach_list, entries) {
if (virt_to_mach->va == (uintptr_t)virt)
return virt_to_mach->pa;
}
return 0;
}
2 changes: 2 additions & 0 deletions src-gnu/rumpcomp_userfeatures_pci.h
@@ -0,0 +1,2 @@
#define RUMPCOMP_USERFEATURE_PCI_IOSPACE
#define RUMPCOMP_USERFEATURE_PCI_DMAFREE