/* * Copyright (C) 2006, Intel Corporation * * This file is part of the Linux-ready Firmware Developer Kit * * This program file 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;version 2.1 of the License. * * 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 program in a file named COPYING; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../biostest.h" #include #include #include #include #include #define MASK_4K 0xfff /* DMA Remapping Reporting Table (DMAR) */ struct acpi_table_dmar { u_int8_t head[36]; u_int8_t haw; u_int8_t flags; u_int8_t reserved[10]; } __attribute__((packed)); #define DMAR_HEADER_SIZE sizeof(struct acpi_table_dmar) struct acpi_dmar_entry_header { u_int16_t type; u_int16_t length; } __attribute__((packed)); enum acpi_dmar_entry_type { ACPI_DMAR_DRHD = 0, ACPI_DMAR_RMRR, ACPI_DMAR_ASTR, ACPI_DMAR_ENTRY_COUNT }; struct acpi_table_drhd { struct acpi_dmar_entry_header header; u_int8_t flags; /* BIT0: INCLUDE_ALL */ u_int8_t reserved; u_int16_t segment; u_int64_t address; /* register base address for this drhd */ } __attribute__ ((packed)); struct acpi_table_rmrr { struct acpi_dmar_entry_header header; u_int16_t reserved; u_int16_t segment; u_int64_t base_address; u_int64_t end_address; } __attribute__ ((packed)); enum acpi_dev_scope_type { ACPI_DEV_ENDPOINT=0x01, ACPI_DEV_P2PBRIDGE, ACPI_DEV_IOAPIC, ACPI_DEV_HPET, ACPI_DEV_ENTRY_COUNT }; struct acpi_dev_scope { u_int8_t dev_type; u_int8_t length; u_int16_t reserved; u_int8_t enumeration_id; u_int8_t start_bus; } __attribute__((packed)); struct acpi_pci_path { u_int8_t dev; u_int8_t fn; } __attribute__((packed)); #define MIN_SCOPE_LEN (sizeof(struct acpi_pci_path) + \ sizeof(struct acpi_dev_scope)) /* * = -1, no such device * = 0, normal pci device * = 1, pci bridge, sec_bus gets set */ static int read_pci_device_secondary_bus_number(u_int8_t seg, u_int8_t bus, u_int8_t dev, u_int8_t fn, u_int8_t *sec_bus) { FILE *file; char str[80]; char configs[64]; size_t count; sprintf(str, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/config", seg, bus, dev, fn); file = fopen(str, "r"); if (!file) return -1; count = fread(configs, sizeof(char), 64, file); if (count < 64) return -1; fclose(file); /* header type is at 0x0e */ if ((configs[0xe] & 0x7f) != 1) //not a pci bridge return 0; *sec_bus = configs[0x19]; //secondary bus number return 1; } static int acpi_parse_one_dev_scope(struct acpi_dev_scope *scope, int seg) { struct acpi_pci_path *path; int count; u_int8_t bus, sec_bus = 0; int dev_type; if (scope->length < MIN_SCOPE_LEN) { report_result("DMAR", FAIL, "Invalid device scope entry", NULL, NULL); return EINVAL; } if (scope->dev_type >= ACPI_DEV_ENTRY_COUNT) { report_result("DMAR", WARN, "Unknown device scope type", NULL, NULL); return EINVAL; } if (scope->dev_type > ACPI_DEV_P2PBRIDGE) { report_result("DMAR", INFO, "Unknown device scope type, the test case should be fixed", NULL, NULL); return EINVAL; } bus = scope->start_bus; count = (scope->length - sizeof(struct acpi_dev_scope)) /sizeof(struct acpi_pci_path); path = (struct acpi_pci_path *)(scope + 1); if (!count) goto error; dev_type = 1; while (count) { if (dev_type <= 0) //last device isn't a pci bridge goto error; dev_type = read_pci_device_secondary_bus_number(seg, bus, path->dev, path->fn, &sec_bus); if (dev_type < 0) //no such device goto error; path ++; count --; bus = sec_bus; } if ((scope->dev_type == ACPI_DEV_ENDPOINT && dev_type > 0) || (scope->dev_type == ACPI_DEV_P2PBRIDGE && dev_type == 0)) { report_result("DMAR", FAIL, "Device scope type not match", NULL, NULL); return EINVAL; } return 0; error: report_result("DMAR", FAIL, "Device scope device not found", NULL, NULL); return EINVAL; } static int acpi_parse_dev_scope(void *start, void *end, int seg) { struct acpi_dev_scope *scope; int ret; while (start < end) { scope = start; ret = acpi_parse_one_dev_scope(scope, seg); if (ret) return ret; start += scope->length; } return 0; } static int acpi_parse_one_drhd(struct acpi_dmar_entry_header *header) { static int include_all; struct acpi_table_drhd *drhd = (struct acpi_table_drhd*)header; if (drhd->address & MASK_4K) { report_result("DMAR", FAIL, "Invalid drhd register address", NULL, NULL); return EINVAL; } if (drhd->flags & 1) { if (include_all == 1) { report_result("DMAR", FAIL, "Multiple drhds have include_all flag set", NULL, NULL); return EINVAL; } include_all = 1; } else { return acpi_parse_dev_scope((void *)(drhd + 1), ((void *)drhd) + header->length, drhd->segment); } return 0; } static int acpi_parse_one_rmrr(struct acpi_dmar_entry_header *header) { struct acpi_table_rmrr *rmrr = (struct acpi_table_rmrr *)header; if ((rmrr->base_address & MASK_4K) || (rmrr->end_address < rmrr->base_address) || ((rmrr->end_address - rmrr->base_address + 1) & MASK_4K)) { report_result("DMAR", FAIL, "Invalid rmrr range address", NULL, NULL); return EINVAL; } return acpi_parse_dev_scope((void *)(rmrr + 1), ((void*)rmrr) + header->length, rmrr->segment); } static int dmar_acpi_table_check(void) { unsigned long address; unsigned long size; char *table_ptr = NULL; struct acpi_dmar_entry_header *header; int found_dmar = 0; if (locate_acpi_table("DMAR", &address, &size)) { report_result("DMAR", INFO, "No DMAR ACPI table found.", NULL, NULL); goto out; } found_dmar = 1; if (address == 0 || size <= DMAR_HEADER_SIZE) { report_result("DMAR", FAIL, "Invalid DMAR ACPI table", NULL, NULL); goto out; } table_ptr = copy_acpi_table(address, "DMAR"); if (table_ptr == NULL) { report_result("DMAR", FAIL, "Invalid DMAR ACPI table size", NULL, NULL); goto out; } header = (struct acpi_dmar_entry_header *)(table_ptr+DMAR_HEADER_SIZE); while ((unsigned long)header < (unsigned long)(table_ptr + size)) { switch (header->type) { case ACPI_DMAR_DRHD: if (acpi_parse_one_drhd(header)) goto out; break; case ACPI_DMAR_RMRR: if (acpi_parse_one_rmrr(header)) goto out; break; } header = ((void *)header) + header->length; } report_result("DMAR", PASS, "DMAR ACPI table is ok", NULL, NULL); out: free(table_ptr); return found_dmar; } static void dmar_check_line(gpointer data, gpointer user_data) { char *line = (char *)data; if (strstr(line, "DMAR:[fault reason")) report_result("DMAR", FAIL, line, NULL, NULL); } static void do_manual_dmar_test(void) { int found_dmar; start_test("DMAR", "(experimental) DMA Remapping (VT-d) test", "Verify if DMA remapping is sane."); found_dmar = dmar_acpi_table_check(); /* Runtime check */ if (found_dmar) g_list_foreach(boot_dmesg, dmar_check_line, NULL); finish_test("DMAR"); } void run_test(void) { if (safe_mode) register_interactive_test("DMAR test", do_manual_dmar_test); else do_manual_dmar_test(); }