⛏️ index : haiku.git

/*
 * Copyright 2007, Hugo Santos. All Rights Reserved.
 * Copyright 2004, Marcus Overhagen. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 */


extern "C" {
#include "device.h"
}

#include <cstdlib>

#include <arch/cpu.h>
#include <interrupts.h>

extern "C" {
#include <compat/dev/pci/pcireg.h>
#include <compat/dev/pci/pcivar.h>
#include <compat/machine/resource.h>
#include <compat/sys/mutex.h>
#include <compat/machine/bus.h>
#include <compat/sys/rman.h>
#include <compat/sys/bus.h>
}


//#define DEBUG_BUS_SPACE_RW
#ifdef DEBUG_BUS_SPACE_RW
#	define TRACE_BUS_SPACE_RW(x) driver_printf x
#else
#	define TRACE_BUS_SPACE_RW(x)
#endif


struct internal_intr {
	device_t		dev;
	driver_filter_t* filter;
	driver_intr_t	*handler;
	void			*arg;
	int				irq;
	uint32			flags;

	thread_id		thread;
	sem_id			sem;
	int32			handling;
};

static int32 intr_wrapper(void *data);


static area_id
map_mem(void **virtualAddr, phys_addr_t _phy, size_t size, uint32 protection,
	const char *name)
{
	uint32 offset = _phy & (B_PAGE_SIZE - 1);
	phys_addr_t physicalAddr = _phy - offset;
	area_id area;

	size = roundup(size + offset, B_PAGE_SIZE);
	area = map_physical_memory(name, physicalAddr, size, B_ANY_KERNEL_ADDRESS,
		protection, virtualAddr);
	if (area < B_OK)
		return area;

	*virtualAddr = (uint8 *)(*virtualAddr) + offset;

	return area;
}


static int
bus_alloc_irq_resource(device_t dev, struct resource *res)
{
	uint8 irq = pci_read_config(dev, PCI_interrupt_line, 1);
	if (irq == 0 || irq == 0xff)
		return -1;

	res->r_bustag = BUS_SPACE_TAG_IRQ;
	res->r_bushandle = irq;
	return 0;
}


static int
bus_alloc_mem_resource(device_t dev, struct resource *res, pci_info *info,
	int bar_index)
{
	phys_addr_t addr = info->u.h0.base_registers[bar_index];
	uint64 size = info->u.h0.base_register_sizes[bar_index];
	uchar flags = info->u.h0.base_register_flags[bar_index];

	// reject empty regions
	if (size == 0)
		return -1;

	// reject I/O space
	if ((flags & PCI_address_space) != 0)
		return -1;

	// TODO: check flags & PCI_address_prefetchable ?

	if ((flags & PCI_address_type) == PCI_address_type_64) {
		addr |= (uint64)info->u.h0.base_registers[bar_index + 1] << 32;
		size |= (uint64)info->u.h0.base_register_sizes[bar_index + 1] << 32;
	}

	// enable this I/O resource
	if (pci_enable_io(dev, SYS_RES_MEMORY) != 0)
		return -1;

	void *virtualAddr;

	res->r_mapped_area = map_mem(&virtualAddr, addr, size,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, "bus_alloc_resource(MEMORY)");
	if (res->r_mapped_area < B_OK)
		return -1;

	res->r_bustag = BUS_SPACE_TAG_MEM;
	res->r_bushandle = (bus_space_handle_t)virtualAddr;
	return 0;
}


static int
bus_alloc_ioport_resource(device_t dev, struct resource *res, pci_info *info,
	int bar_index)
{
	uint32 size = info->u.h0.base_register_sizes[bar_index];
	uchar flags = info->u.h0.base_register_flags[bar_index];

	// reject empty regions
	if (size == 0)
		return -1;

	// reject memory space
	if ((flags & PCI_address_space) == 0)
		return -1;

	// enable this I/O resource
	if (pci_enable_io(dev, SYS_RES_IOPORT) != 0)
		return -1;

	res->r_bustag = BUS_SPACE_TAG_IO;
	res->r_bushandle = info->u.h0.base_registers[bar_index];
	return 0;
}


static int
bus_register_to_bar_index(pci_info *info, int regid)
{
	// check the offset really is of a BAR
	if (regid < PCI_base_registers || (regid % sizeof(uint32) != 0)
		|| (regid >= PCI_base_registers + 6 * (int)sizeof(uint32))) {
		return -1;
	}

	// turn offset into array index
	regid -= PCI_base_registers;
	regid /= sizeof(uint32);
	return regid;
}


struct resource *
bus_alloc_resource(device_t dev, int type, int *rid, unsigned long start,
	unsigned long end, unsigned long count, uint32 flags)
{
	struct resource *res;
	int result = -1;

	if (type != SYS_RES_IRQ && type != SYS_RES_MEMORY
		&& type != SYS_RES_IOPORT)
		return NULL;

	device_printf(dev, "bus_alloc_resource(%i, [%i], 0x%lx, 0x%lx, 0x%lx,"
		"0x%" B_PRIx32 ")\n", type, *rid, start, end, count, flags);

	// maybe a local array of resources is enough
	res = (struct resource *)malloc(sizeof(struct resource));
	if (res == NULL)
		return NULL;

	if (type == SYS_RES_IRQ) {
		if (*rid == 0) {
			// pinned interrupt
			result = bus_alloc_irq_resource(dev, res);
		} else {
			// msi or msi-x interrupt at index *rid - 1
			pci_info* info = get_device_pci_info(dev);
			res->r_bustag = BUS_SPACE_TAG_MSI;
			res->r_bushandle = info->u.h0.interrupt_line + *rid - 1;
			result = 0;
		}
	} else if (type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) {
		pci_info* info = get_device_pci_info(dev);
		int bar_index = bus_register_to_bar_index(info, *rid);
		if (bar_index >= 0) {
			if (type == SYS_RES_MEMORY)
				result = bus_alloc_mem_resource(dev, res, info, bar_index);
			else
				result = bus_alloc_ioport_resource(dev, res, info, bar_index);
		}
	}

	if (result < 0) {
		free(res);
		return NULL;
	}

	res->r_type = type;
	return res;
}


int
bus_release_resource(device_t dev, int type, int rid, struct resource *res)
{
	if (res->r_type != type)
		panic("bus_release_resource: mismatch");

	if (type == SYS_RES_MEMORY)
		delete_area(res->r_mapped_area);

	free(res);
	return 0;
}


int
bus_alloc_resources(device_t dev, struct resource_spec *resourceSpec,
	struct resource **resources)
{
	int i;

	for (i = 0; resourceSpec[i].type != -1; i++) {
		resources[i] = bus_alloc_resource_any(dev,
			resourceSpec[i].type, &resourceSpec[i].rid, resourceSpec[i].flags);
		if (resources[i] == NULL
			&& (resourceSpec[i].flags & RF_OPTIONAL) == 0) {
			for (++i; resourceSpec[i].type != -1; i++) {
				resources[i] = NULL;
			}

			bus_release_resources(dev, resourceSpec, resources);
			return ENXIO;
		}
	}
	return 0;
}


void
bus_release_resources(device_t dev, const struct resource_spec *resourceSpec,
	struct resource **resources)
{
	int i;

	for (i = 0; resourceSpec[i].type != -1; i++) {
		if (resources[i] == NULL)
			continue;

		bus_release_resource(dev, resourceSpec[i].type, resourceSpec[i].rid,
			resources[i]);
		resources[i] = NULL;
	}
}


bus_space_handle_t
rman_get_bushandle(struct resource *res)
{
	return res->r_bushandle;
}


bus_space_tag_t
rman_get_bustag(struct resource *res)
{
	return res->r_bustag;
}


int
rman_get_rid(struct resource *res)
{
	return 0;
}


void*
rman_get_virtual(struct resource *res)
{
	return NULL;
}


bus_addr_t
rman_get_start(struct resource *res)
{
	return res->r_bushandle;
}


bus_size_t
rman_get_size(struct resource *res)
{
	area_info info;
	if (get_area_info(res->r_mapped_area, &info) != B_OK)
		return 0;
	return info.size;
}


//	#pragma mark - Interrupt handling


static int32
intr_wrapper(void *data)
{
	struct internal_intr *intr = (struct internal_intr *)data;

	//device_printf(intr->dev, "in interrupt handler.\n");

	if (!HAIKU_CHECK_DISABLE_INTERRUPTS(intr->dev))
		return B_UNHANDLED_INTERRUPT;

	release_sem_etc(intr->sem, 1, B_DO_NOT_RESCHEDULE);
	return intr->handling ? B_HANDLED_INTERRUPT : B_INVOKE_SCHEDULER;
}


static int32
intr_handler(void *data)
{
	struct internal_intr *intr = (struct internal_intr *)data;
	status_t status;

	while (1) {
		status = acquire_sem(intr->sem);
		if (status < B_OK)
			break;

		//device_printf(intr->dev, "in soft interrupt handler.\n");

		atomic_or(&intr->handling, 1);
		if ((intr->flags & INTR_MPSAFE) == 0)
			mtx_lock(&Giant);

		intr->handler(intr->arg);

		if ((intr->flags & INTR_MPSAFE) == 0)
			mtx_unlock(&Giant);
		atomic_and(&intr->handling, 0);
		HAIKU_REENABLE_INTERRUPTS(intr->dev);
	}

	return 0;
}


static void
free_internal_intr(struct internal_intr *intr)
{
	if (intr->sem >= B_OK) {
		status_t status;
		delete_sem(intr->sem);
		wait_for_thread(intr->thread, &status);
	}

	free(intr);
}


int
bus_setup_intr(device_t dev, struct resource *res, int flags,
	driver_filter_t* filter, driver_intr_t handler, void *arg, void **_cookie)
{
	struct internal_intr *intr = (struct internal_intr *)malloc(
		sizeof(struct internal_intr));
	char semName[64];
	status_t status;

	if (intr == NULL)
		return B_NO_MEMORY;

	intr->dev = dev;
	intr->filter = filter;
	intr->handler = handler;
	intr->arg = arg;
	intr->irq = res->r_bushandle;
	intr->flags = flags;
	intr->sem = -1;
	intr->thread = -1;

	if (filter != NULL) {
		status = install_io_interrupt_handler(intr->irq,
			(interrupt_handler)intr->filter, intr->arg, 0);
	} else {
		snprintf(semName, sizeof(semName), "%s intr", dev->device_name);

		intr->sem = create_sem(0, semName);
		if (intr->sem < B_OK) {
			free(intr);
			return B_NO_MEMORY;
		}

		snprintf(semName, sizeof(semName), "%s intr handler", dev->device_name);

		intr->thread = spawn_kernel_thread(intr_handler, semName,
			B_REAL_TIME_DISPLAY_PRIORITY, intr);
		if (intr->thread < B_OK) {
			delete_sem(intr->sem);
			free(intr);
			return B_NO_MEMORY;
		}

		status = install_io_interrupt_handler(intr->irq,
			intr_wrapper, intr, 0);
	}

	if (status == B_OK && res->r_bustag == BUS_SPACE_TAG_MSI) {
		// this is an msi, enable it
		struct root_device_softc* root_softc = ((struct root_device_softc *)dev->root->softc);
		if (root_softc->is_msi) {
			if (gPci->enable_msi(root_softc->pci_info.bus, root_softc->pci_info.device,
					root_softc->pci_info.function) != B_OK) {
				device_printf(dev, "enabling msi failed\n");
				bus_teardown_intr(dev, res, intr);
				return ENODEV;
			}
		} else if (root_softc->is_msix) {
			if (gPci->enable_msix(root_softc->pci_info.bus, root_softc->pci_info.device,
					root_softc->pci_info.function) != B_OK) {
				device_printf(dev, "enabling msix failed\n");
				bus_teardown_intr(dev, res, intr);
				return ENODEV;
			}
		}
	}

	if (status < B_OK) {
		free_internal_intr(intr);
		return status;
	}

	resume_thread(intr->thread);

	*_cookie = intr;
	return 0;
}


int
bus_teardown_intr(device_t dev, struct resource *res, void *arg)
{
	struct internal_intr *intr = (struct internal_intr *)arg;
	if (intr == NULL)
		return -1;

	struct root_device_softc *root = (struct root_device_softc *)dev->root->softc;

	if (root->is_msi || root->is_msix) {
		// disable msi generation
		pci_info *info = &root->pci_info;
		gPci->disable_msi(info->bus, info->device, info->function);
	}

	if (intr->filter != NULL) {
		remove_io_interrupt_handler(intr->irq, (interrupt_handler)intr->filter,
			intr->arg);
	} else {
		remove_io_interrupt_handler(intr->irq, intr_wrapper, intr);
	}

	free_internal_intr(intr);
	return 0;
}


int
bus_bind_intr(device_t dev, struct resource *res, int cpu)
{
	if (dev->parent == NULL)
		return EINVAL;

	// TODO
	return 0;
}


int bus_describe_intr(device_t dev, struct resource *irq, void *cookie,
	const char* fmt, ...)
{
	if (dev->parent == NULL)
		return EINVAL;

	// we don't really support names for interrupts
	return 0;
}


//	#pragma mark - bus functions


bus_dma_tag_t
bus_get_dma_tag(device_t dev)
{
	return NULL;
}


int
bus_generic_suspend(device_t dev)
{
	UNIMPLEMENTED();
	return B_ERROR;
}


int
bus_generic_resume(device_t dev)
{
	UNIMPLEMENTED();
	return B_ERROR;
}


void
bus_generic_shutdown(device_t dev)
{
	UNIMPLEMENTED();
}


int
bus_print_child_header(device_t dev, device_t child)
{
	UNIMPLEMENTED();
	return B_ERROR;
}


int
bus_print_child_footer(device_t dev, device_t child)
{
	UNIMPLEMENTED();
	return B_ERROR;
}


int
bus_generic_print_child(device_t dev, device_t child)
{
	UNIMPLEMENTED();
	return B_ERROR;
}


void
bus_generic_driver_added(device_t dev, driver_t *driver)
{
	UNIMPLEMENTED();
}


int
bus_child_present(device_t child)
{
	device_t parent = device_get_parent(child);
	if (parent == NULL)
		return 0;

	return bus_child_present(parent);
}


void
bus_enumerate_hinted_children(device_t bus)
{
#if 0
	UNIMPLEMENTED();
#endif
}