/* * viracpi.c: ACPI table(s) parser * * Copyright (C) 2023 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; If not, see * . */ #include #include #include #include #define LIBVIRT_VIRACPIPRIV_H_ALLOW #include "internal.h" #include "viracpi.h" #include "viracpipriv.h" #include "viralloc.h" #include "virerror.h" #include "virfile.h" #include "virlog.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("util.acpi"); typedef struct virIORTHeader virIORTHeader; struct virIORTHeader { uint32_t signature; uint32_t length; uint8_t revision; uint8_t checksum; char oem_id[6]; char oem_table_id[8]; char oem_revision[4]; char creator_id[4]; char creator_revision[4]; /* Technically, the following are not part of header, but * they immediately follow the header and are in the table * exactly once. */ uint32_t nnodes; uint32_t nodes_offset; /* Here follows reserved and padding fields. Ain't nobody's * interested in that. */ } ATTRIBUTE_PACKED; VIR_ENUM_IMPL(virIORTNodeType, VIR_IORT_NODE_TYPE_LAST, "ITS Group", "Named Component", "Root Complex", "SMMUv1 or SMMUv2", "SMMUv3", "PMCG", "Memory range"); static int virAcpiParseIORTNodeHeader(int fd, const char *filename, virIORTNodeHeader *nodeHeader) { g_autofree char *nodeHeaderBuf = NULL; const char *typeStr = NULL; int nodeHeaderLen; nodeHeaderLen = virFileReadHeaderFD(fd, sizeof(*nodeHeader), &nodeHeaderBuf); if (nodeHeaderLen < 0) { virReportSystemError(errno, _("cannot read node header '%1$s'"), filename); return -1; } if (nodeHeaderLen != sizeof(*nodeHeader)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IORT table node header ended early")); return -1; } memcpy(nodeHeader, nodeHeaderBuf, nodeHeaderLen); typeStr = virIORTNodeTypeTypeToString(nodeHeader->type); VIR_DEBUG("IORT node header: type = %" PRIu8 " (%s) len = %" PRIu16, nodeHeader->type, NULLSTR(typeStr), nodeHeader->len); /* Basic sanity check. While there's a type specific data * that follows the node header, the node length should be at * least size of header itself. */ if (nodeHeader->len < sizeof(*nodeHeader)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("IORT table node type %1$s has invalid length: got %2$u, expected at least %3$zu"), NULLSTR(typeStr), (unsigned int)nodeHeader->len, sizeof(*nodeHeader)); return -1; } return 0; } static ssize_t virAcpiParseIORTNodes(int fd, const char *filename, const virIORTHeader *header, virIORTNodeHeader **nodesRet) { g_autofree virIORTNodeHeader *nodes = NULL; size_t nnodes = 0; off_t pos; /* Firstly, reset position to the start of nodes. */ if ((pos = lseek(fd, header->nodes_offset, SEEK_SET)) < 0) { virReportSystemError(errno, _("cannot seek in '%1$s'"), filename); return -1; } for (; pos < header->length;) { virIORTNodeHeader node; if (virAcpiParseIORTNodeHeader(fd, filename, &node) < 0) return -1; if ((pos = lseek(fd, pos + node.len, SEEK_SET)) < 0) { virReportSystemError(errno, _("cannot seek in '%1$s'"), filename); return -1; } VIR_APPEND_ELEMENT(nodes, nnodes, node); } *nodesRet = g_steal_pointer(&nodes); return nnodes; } ssize_t virAcpiParseIORT(virIORTNodeHeader **nodesRet, const char *filename) { VIR_AUTOCLOSE fd = -1; g_autofree char *headerBuf = NULL; int headerLen; virIORTHeader header; if ((fd = open(filename, O_RDONLY)) < 0) { virReportSystemError(errno, _("cannot open '%1$s'"), filename); return -1; } headerLen = virFileReadHeaderFD(fd, sizeof(header), &headerBuf); if (headerLen < 0) { virReportSystemError(errno, _("cannot read header '%1$s'"), filename); return -1; } if (headerLen != sizeof(header)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IORT table header ended early")); return -1; } memcpy(&header, headerBuf, headerLen); VIR_DEBUG("IORT header: len = %" PRIu32 " revision = %" PRIu8 " nnodes = %" PRIu32 " OEM = %s", header.length, header.revision, header.nnodes, header.oem_id); return virAcpiParseIORTNodes(fd, filename, &header, nodesRet); } #define IORT_PATH "/sys/firmware/acpi/tables/IORT" /** * virAcpiHasSMMU: * * Parse IORT table trying to find SMMU node entry. * Since IORT is ARM specific ACPI table, it doesn't make much * sense to call this function on other platforms and expect * sensible result. * * Returns: 0 if no SMMU node was found, * 1 if a SMMU node was found (i.e. host supports SMMU), * -1 otherwise (with error reported). */ int virAcpiHasSMMU(void) { g_autofree virIORTNodeHeader *nodes = NULL; ssize_t nnodes = -1; size_t i; if ((nnodes = virAcpiParseIORT(&nodes, IORT_PATH)) < 0) return -1; for (i = 0; i < nnodes; i++) { if (nodes[i].type == VIR_IORT_NODE_TYPE_SMMUV1_OR_SMMUV2 || nodes[i].type == VIR_IORT_NODE_TYPE_SMMUV3) { return 1; } } return 0; }