/* * Copyright 2014, Ithamar R. Adema * All rights reserved. Distributed under the terms of the MIT License. * * Copyright 2015-2022, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include }; //#define TRACE_FDT #ifdef TRACE_FDT #define TRACE(x...) dprintf(x) #else #define TRACE(x...) #endif #define GIC_INTERRUPT_CELL_TYPE 0 #define GIC_INTERRUPT_CELL_ID 1 #define GIC_INTERRUPT_CELL_FLAGS 2 #define GIC_INTERRUPT_TYPE_SPI 0 #define GIC_INTERRUPT_TYPE_PPI 1 #define GIC_INTERRUPT_BASE_SPI 32 #define GIC_INTERRUPT_BASE_PPI 16 extern void* gFDT; device_manager_info* gDeviceManager; extern fdt_bus_module_info gBusModule; extern fdt_device_module_info gDeviceModule; //#pragma mark - struct fdt_bus { device_node* node; HashMap, device_node*> phandles; }; struct fdt_device { device_node* node; device_node* bus; }; struct fdt_interrupt_map_entry { uint32_t childAddr; uint32_t childIrq; uint32_t parentIrqCtrl; uint32_t parentIrq; }; struct fdt_interrupt_map { uint32_t childAddrMask; uint32_t childIrqMask; Vector fInterruptMap; }; static status_t fdt_register_node(fdt_bus* bus, int node, device_node* parentDev, device_node*& curDev) { TRACE("%s('%s', %p)\n", __func__, fdt_get_name(gFDT, node, NULL), parentDev); const void* prop; int propLen; Vector attrs; int nameLen = 0; const char *name = fdt_get_name(gFDT, node, &nameLen); if (name == NULL) { dprintf("%s ERROR: fdt_get_name: %s\n", __func__, fdt_strerror(nameLen)); return B_ERROR; } attrs.Add({ B_DEVICE_BUS, B_STRING_TYPE, {.string = "fdt"}}); attrs.Add({ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = (strcmp(name, "") != 0) ? name : "Root" }}); attrs.Add({ "fdt/node", B_UINT32_TYPE, {.ui32 = (uint32)node}}); attrs.Add({ "fdt/name", B_STRING_TYPE, {.string = name}}); prop = fdt_getprop(gFDT, node, "device_type", &propLen); if (prop != NULL) attrs.Add({ "fdt/device_type", B_STRING_TYPE, { .string = (const char*)prop }}); prop = fdt_getprop(gFDT, node, "compatible", &propLen); if (prop != NULL) { const char* propStr = (const char*)prop; const char* propEnd = propStr + propLen; while (propEnd - propStr > 0) { int curLen = strlen(propStr); attrs.Add({ "fdt/compatible", B_STRING_TYPE, { .string = propStr }}); propStr += curLen + 1; } } attrs.Add({}); status_t res = gDeviceManager->register_node(parentDev, "bus_managers/fdt/driver_v1", &attrs[0], NULL, &curDev); if (res < B_OK) return res; prop = fdt_getprop(gFDT, node, "phandle", &propLen); if (prop != NULL) bus->phandles.Put(fdt32_to_cpu(*(uint32_t*)prop), curDev); return B_OK; } static void fdt_traverse(fdt_bus* bus, int &node, int &depth, device_node* parentDev) { int curDepth = depth; #if 0 for (int i = 0; i < depth; i++) dprintf(" "); dprintf("node('%s')\n", fdt_get_name(gFDT, node, NULL)); #endif device_node* curDev; fdt_register_node(bus, node, parentDev, curDev); node = fdt_next_node(gFDT, node, &depth); while (node >= 0 && depth == curDepth + 1) { fdt_traverse(bus, node, depth, curDev); } } //#pragma mark bus static int32 fdt_bus_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: TRACE("fdt root init\n"); return B_OK; case B_MODULE_UNINIT: TRACE("fdt root uninit\n"); return B_OK; } return B_BAD_VALUE; } static float fdt_bus_supports_device(device_node* parent) { TRACE("fdt_bus_supports_device\n"); // make sure parent is really device root const char* bus; if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) return B_ERROR; if (strcmp(bus, "root")) return 0.0; return 1.0; } static status_t fdt_bus_register_device(device_node* parent) { TRACE("+fdt_bus_register_device\n"); struct ScopeExit { ScopeExit() {TRACE("-fdt_bus_register_device\n");} } scopeExit; device_attr attrs[] = { {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "FDT"}}, {B_DEVICE_FLAGS, B_UINT32_TYPE, {.ui32 = B_KEEP_DRIVER_LOADED}}, {} }; return gDeviceManager->register_node( parent, "bus_managers/fdt/root/driver_v1", attrs, NULL, NULL); } static status_t fdt_bus_init(device_node* node, void** cookie) { TRACE("fdt_bus_init\n"); if (gFDT == NULL) { TRACE("FDT is NULL!\n"); return B_DEVICE_NOT_FOUND; } ObjectDeleter bus(new(std::nothrow) fdt_bus()); if (!bus.IsSet()) return B_NO_MEMORY; // gFDT is stored in kernel_args and will be freed, so copy it to kernel heap. size_t size = fdt_totalsize(gFDT); void* newFDT = malloc(size); if (newFDT == NULL) return B_NO_MEMORY; memcpy(newFDT, gFDT, size); gFDT = newFDT; bus->node = node; *cookie = bus.Detach(); return B_OK; } static void fdt_bus_uninit(void* cookie) { TRACE("fdt_bus_uninit\n"); ObjectDeleter bus((fdt_bus*)cookie); } static status_t fdt_bus_register_child_devices(void* cookie) { TRACE("fdt_bus_register_child_devices\n"); fdt_bus* bus = (fdt_bus*)cookie; status_t res = gDeviceManager->publish_device(bus->node, "bus/fdt/blob", "bus_managers/fdt/device/v1"); if (res < B_OK) return res; int node = -1, depth = -1; node = fdt_next_node(gFDT, node, &depth); fdt_traverse(bus, node, depth, bus->node); return B_OK; } device_node* fdt_bus_node_by_phandle(fdt_bus* bus, int phandle) { ASSERT(bus != NULL); device_node** devNode; if (!bus->phandles.Get(phandle, devNode)) return NULL; return *devNode; } //#pragma mark device static status_t fdt_device_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: case B_MODULE_UNINIT: return B_OK; } return B_BAD_VALUE; } static status_t fdt_device_init_driver(device_node* node, void** cookie) { TRACE("fdt_device_init_driver()\n"); ObjectDeleter dev(new(std::nothrow) fdt_device()); if (!dev.IsSet()) return B_NO_MEMORY; dev->node = node; // get bus from parent node DeviceNodePutter<&gDeviceManager> parent( gDeviceManager->get_parent_node(node)); driver_module_info* parentModule; void* parentDev; ASSERT(gDeviceManager->get_driver( parent.Get(), &parentModule, &parentDev) >= B_OK); if (parentModule == (driver_module_info*)&gDeviceModule) dev->bus = ((fdt_device*)parentDev)->bus; else if (parentModule == (driver_module_info*)&gBusModule) dev->bus = parent.Get(); else panic("bad parent node"); *cookie = dev.Detach(); return B_OK; } static void fdt_device_uninit_driver(void* cookie) { TRACE("fdt_device_uninit_driver()\n"); ObjectDeleter dev((fdt_device*)cookie); } static status_t fdt_device_register_child_devices(void* cookie) { TRACE("fdt_device_register_child_devices()\n"); return B_OK; } static device_node* fdt_device_get_bus(fdt_device* dev) { ASSERT(dev != NULL); return dev->bus; } static const char* fdt_device_get_name(fdt_device* dev) { ASSERT(dev != NULL); uint32 fdtNode; ASSERT(gDeviceManager->get_attr_uint32( dev->node, "fdt/node", &fdtNode, false) >= B_OK); return fdt_get_name(gFDT, (int)fdtNode, NULL); } static const void* fdt_device_get_prop(fdt_device* dev, const char* name, int* len) { ASSERT(dev != NULL); uint32 fdtNode; ASSERT(gDeviceManager->get_attr_uint32( dev->node, "fdt/node", &fdtNode, false) >= B_OK); return fdt_getprop(gFDT, (int)fdtNode, name, len); } static uint32 fdt_get_address_cells(const void* fdt, int node) { uint32 res = 2; int parent = fdt_parent_offset(fdt, node); if (parent < 0) return res; uint32 *prop = (uint32*)fdt_getprop(fdt, parent, "#address-cells", NULL); if (prop == NULL) return res; res = fdt32_to_cpu(*prop); return res; } static uint32 fdt_get_size_cells(const void* fdt, int node) { uint32 res = 1; int parent = fdt_parent_offset(fdt, node); if (parent < 0) return res; uint32 *prop = (uint32*)fdt_getprop(fdt, parent, "#size-cells", NULL); if (prop == NULL) return res; res = fdt32_to_cpu(*prop); return res; } static bool fdt_device_get_reg(fdt_device* dev, uint32 ord, uint64* regs, uint64* len) { ASSERT(dev != NULL); uint32 fdtNode; ASSERT(gDeviceManager->get_attr_uint32( dev->node, "fdt/node", &fdtNode, false) >= B_OK); int propLen; const void* prop = fdt_getprop(gFDT, (int)fdtNode, "reg", &propLen); if (prop == NULL) return false; uint32 addressCells = fdt_get_address_cells(gFDT, fdtNode); uint32 sizeCells = fdt_get_size_cells(gFDT, fdtNode); size_t entrySize = 4 * (addressCells + sizeCells); if ((ord + 1) * entrySize > (uint32)propLen) return false; const void* addressPtr = (const uint8*)prop + ord * entrySize; const void* sizePtr = (const uint32*)addressPtr + addressCells; switch (addressCells) { case 1: *regs = fdt32_to_cpu(*(const uint32*)addressPtr); break; case 2: *regs = fdt64_to_cpu(*(const uint64*)addressPtr); break; default: return false; } switch (sizeCells) { case 1: *len = fdt32_to_cpu(*(const uint32*)sizePtr); break; case 2: *len = fdt64_to_cpu(*(const uint64*)sizePtr); break; default: return false; } return true; } static uint32 fdt_get_interrupt_parent(fdt_device* dev, int node) { while (node >= 0) { uint32* prop; int propLen; prop = (uint32*)fdt_getprop(gFDT, node, "interrupt-parent", &propLen); if (prop != NULL && propLen == 4) { return fdt32_to_cpu(*prop); } node = fdt_parent_offset(gFDT, node); } return 0; } static uint32 fdt_get_interrupt_cells(uint32 interrupt_parent_phandle) { if (interrupt_parent_phandle > 0) { int node = fdt_node_offset_by_phandle(gFDT, interrupt_parent_phandle); if (node >= 0) { uint32* prop; int propLen; prop = (uint32*)fdt_getprop(gFDT, node, "#interrupt-cells", &propLen); if (prop != NULL && propLen == 4) { return fdt32_to_cpu(*prop); } } } return 1; } static bool fdt_device_get_interrupt(fdt_device* dev, uint32 index, device_node** interruptController, uint64* interrupt) { ASSERT(dev != NULL); uint32 fdtNode; ASSERT(gDeviceManager->get_attr_uint32( dev->node, "fdt/node", &fdtNode, false) >= B_OK); int propLen; const uint32 *prop = (uint32*)fdt_getprop(gFDT, (int)fdtNode, "interrupts-extended", &propLen); if (prop == NULL) { uint32 interruptParent = fdt_get_interrupt_parent(dev, fdtNode); uint32 interruptCells = fdt_get_interrupt_cells(interruptParent); prop = (uint32*)fdt_getprop(gFDT, (int)fdtNode, "interrupts", &propLen); if (prop == NULL) return false; if ((index + 1) * interruptCells * sizeof(uint32) > (uint32)propLen) return false; uint32 offset = interruptCells * index; uint32 interruptNumber = 0; if ((interruptCells == 1) || (interruptCells == 2)) { interruptNumber = fdt32_to_cpu(*(prop + offset)); } else if (interruptCells == 3) { uint32 interruptType = fdt32_to_cpu(prop[offset + GIC_INTERRUPT_CELL_TYPE]); interruptNumber = fdt32_to_cpu(prop[offset + GIC_INTERRUPT_CELL_ID]); if (interruptType == GIC_INTERRUPT_TYPE_SPI) interruptNumber += GIC_INTERRUPT_BASE_SPI; else if (interruptType == GIC_INTERRUPT_TYPE_PPI) interruptNumber += GIC_INTERRUPT_BASE_PPI; } else { panic("unsupported interruptCells"); } if (interrupt != NULL) *interrupt = interruptNumber; if (interruptController != NULL && interruptParent != 0) { fdt_bus* bus; ASSERT(gDeviceManager->get_driver(dev->bus, NULL, (void**)&bus) >= B_OK); *interruptController = fdt_bus_node_by_phandle(bus, interruptParent); } return true; } if ((index + 1) * 8 > (uint32)propLen) return false; if (interruptController != NULL) { uint32 phandle = fdt32_to_cpu(*(prop + 2 * index)); fdt_bus* bus; ASSERT(gDeviceManager->get_driver( dev->bus, NULL, (void**)&bus) >= B_OK); *interruptController = fdt_bus_node_by_phandle(bus, phandle); } if (interrupt != NULL) *interrupt = fdt32_to_cpu(*(prop + 2 * index + 1)); return true; } static struct fdt_interrupt_map * fdt_device_get_interrupt_map(struct fdt_device* dev) { int fdtNode; ASSERT(gDeviceManager->get_attr_uint32( dev->node, "fdt/node", (uint32*)&fdtNode, false) >= B_OK); ObjectDeleter interrupt_map(new struct fdt_interrupt_map()); int intMapMaskLen; const void* intMapMask = fdt_getprop(gFDT, fdtNode, "interrupt-map-mask", &intMapMaskLen); if (intMapMask == NULL || intMapMaskLen != 4 * 4) { dprintf(" interrupt-map-mask property not found or invalid\n"); return NULL; } interrupt_map->childAddrMask = B_BENDIAN_TO_HOST_INT32(*((uint32*)intMapMask + 0)); interrupt_map->childIrqMask = B_BENDIAN_TO_HOST_INT32(*((uint32*)intMapMask + 3)); int intMapLen; const void* intMapAddr = fdt_getprop(gFDT, fdtNode, "interrupt-map", &intMapLen); if (intMapAddr == NULL) { dprintf(" interrupt-map property not found\n"); return NULL; } int addressCells = 3; int interruptCells = 1; int phandleCells = 1; const void *property; property = fdt_getprop(gFDT, fdtNode, "#address-cells", NULL); if (property != NULL) addressCells = B_BENDIAN_TO_HOST_INT32(*(uint32*)property); property = fdt_getprop(gFDT, fdtNode, "#interrupt-cells", NULL); if (property != NULL) interruptCells = B_BENDIAN_TO_HOST_INT32(*(uint32*)property); uint32_t *it = (uint32_t*)intMapAddr; while ((uint8_t*)it - (uint8_t*)intMapAddr < intMapLen) { struct fdt_interrupt_map_entry irqEntry; irqEntry.childAddr = B_BENDIAN_TO_HOST_INT32(*it); it += addressCells; irqEntry.childIrq = B_BENDIAN_TO_HOST_INT32(*it); it += interruptCells; irqEntry.parentIrqCtrl = B_BENDIAN_TO_HOST_INT32(*it); it += phandleCells; int parentAddressCells = 0; int parentInterruptCells = 1; int interruptParent = fdt_node_offset_by_phandle(gFDT, irqEntry.parentIrqCtrl); if (interruptParent >= 0) { property = fdt_getprop(gFDT, interruptParent, "#address-cells", NULL); if (property != NULL) parentAddressCells = B_BENDIAN_TO_HOST_INT32(*(uint32*)property); property = fdt_getprop(gFDT, interruptParent, "#interrupt-cells", NULL); if (property != NULL) parentInterruptCells = B_BENDIAN_TO_HOST_INT32(*(uint32*)property); } it += parentAddressCells; if ((parentInterruptCells == 1) || (parentInterruptCells == 2)) { irqEntry.parentIrq = B_BENDIAN_TO_HOST_INT32(*it); } else if (parentInterruptCells == 3) { uint32 interruptType = fdt32_to_cpu(it[GIC_INTERRUPT_CELL_TYPE]); uint32 interruptNumber = fdt32_to_cpu(it[GIC_INTERRUPT_CELL_ID]); if (interruptType == GIC_INTERRUPT_TYPE_SPI) irqEntry.parentIrq = interruptNumber + GIC_INTERRUPT_BASE_SPI; else if (interruptType == GIC_INTERRUPT_TYPE_PPI) irqEntry.parentIrq = interruptNumber + GIC_INTERRUPT_BASE_PPI; else irqEntry.parentIrq = interruptNumber; } it += parentInterruptCells; interrupt_map->fInterruptMap.PushBack(irqEntry); } return interrupt_map.Detach(); } static void fdt_device_print_interrupt_map(struct fdt_interrupt_map* interruptMap) { if (interruptMap == NULL) return; dprintf("interrupt_map_mask: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", interruptMap->childAddrMask, interruptMap->childIrqMask); dprintf("interrupt_map:\n"); for (Vector::Iterator it = interruptMap->fInterruptMap.Begin(); it != interruptMap->fInterruptMap.End(); it++) { dprintf("childAddr=0x%08" PRIx32 ", childIrq=%" PRIu32 ", parentIrqCtrl=%" PRIu32 ", parentIrq=%" PRIu32 "\n", it->childAddr, it->childIrq, it->parentIrqCtrl, it->parentIrq); } } static uint32 fdt_device_lookup_interrupt_map(struct fdt_interrupt_map* interruptMap, uint32 childAddr, uint32 childIrq) { if (interruptMap == NULL) return 0xffffffff; childAddr &= interruptMap->childAddrMask; childIrq &= interruptMap->childIrqMask; for (Vector::Iterator it = interruptMap->fInterruptMap.Begin(); it != interruptMap->fInterruptMap.End(); it++) { if ((it->childAddr == childAddr) && (it->childIrq == childIrq)) return it->parentIrq; } return 0xffffffff; } //#pragma mark devfs node static status_t fdt_devfs_node_read(void *cookie, off_t pos, void *buffer, size_t *_length) { if (pos < 0) return B_BAD_VALUE; size_t size = fdt_totalsize(gFDT); if ((uint64)pos >= size) { *_length = 0; return B_OK; } size_t readSize = *_length; if (pos + readSize > size) readSize = size - pos; status_t res = user_memcpy(buffer, (uint8*)gFDT + pos, readSize); if (res < B_OK) return res; *_length = readSize; return B_OK; } //#pragma mark - fdt_bus_module_info gBusModule = { { { "bus_managers/fdt/root/driver_v1", 0, fdt_bus_std_ops }, fdt_bus_supports_device, fdt_bus_register_device, fdt_bus_init, fdt_bus_uninit, fdt_bus_register_child_devices, NULL, // rescan devices NULL, // device removed }, fdt_bus_node_by_phandle, }; fdt_device_module_info gDeviceModule = { { { "bus_managers/fdt/driver_v1", 0, fdt_device_std_ops }, NULL, // supports device NULL, // register device (our parent registered us) fdt_device_init_driver, fdt_device_uninit_driver, fdt_device_register_child_devices, NULL, // rescan devices NULL, // device removed }, fdt_device_get_bus, fdt_device_get_name, fdt_device_get_prop, fdt_device_get_reg, fdt_device_get_interrupt, fdt_device_get_interrupt_map, fdt_device_print_interrupt_map, fdt_device_lookup_interrupt_map, }; device_module_info gDevfsNodeModule = { .info = { .name = "bus_managers/fdt/device/v1" }, .init_device = [](void *driverCookie, void **_deviceCookie) { *_deviceCookie = NULL; return B_OK; }, .uninit_device = [](void *deviceCookie) {}, .open = [](void *deviceCookie, const char *path, int openMode, void **_cookie) { return B_OK; }, .close = [](void *cookie) { return B_OK; }, .free = [](void *cookie) { return B_OK; }, .read = fdt_devfs_node_read, .control = [](void *cookie, uint32 op, void *buffer, size_t length) { return B_DEV_INVALID_IOCTL; }, }; module_info* modules[] = { (module_info*)&gBusModule, (module_info*)&gDeviceModule, (module_info*)&gDevfsNodeModule, NULL }; module_dependency module_dependencies[] = { { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager }, { NULL } };