/* * ps4-kexec - a kexec() implementation for Orbis OS / FreeBSD * * Copyright (C) 2015-2016 shuffle2 <godisgovernment@gmail.com> * Copyright (C) 2015-2016 Hector Martin "marcan" <marcan@marcan.st> * * This code is licensed to you under the 2-clause BSD license. See the LICENSE * file for more information. */ #include "types.h" #ifdef TESTING # include <stdio.h> # include <sys/mman.h> # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> # include <string.h> #else # include "kernel.h" # include "string.h" # define printf kern.printf #endif #define SIG32(s0, s1, s2, s3) (s0 | (s1 << 8) | (s2 << 16) | (s3 << 24)) #define PSIG32(s) (u8)s, (u8)(s >> 8), (u8)(s >> 16), (u8)(s >> 24) #define PACKED __attribute__((packed)) #define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) struct RSDP { u64 sig; u8 checksum; u8 oemid[6]; u8 rev; u32 rsdt_addr; u32 length; u64 xsdt_addr; u8 ext_checksum; u8 rsvd[3]; } PACKED; struct SDTH { u32 sig; u32 length; u8 rev; u8 checksum; u8 oem_id[6]; u8 oem_tid[8]; u32 oem_rev; u8 creator_id[4]; u32 creator_rev; } PACKED; struct RSDT { struct SDTH hdr; u32 table_addr[]; } PACKED; struct XSDT { struct SDTH hdr; u64 table_addr[]; } PACKED; struct FADT { struct SDTH hdr; u32 facs; u32 dsdt; // more stuff... } PACKED; struct ivhd_entry4 { u8 type; u16 devid; u8 flags; } PACKED; struct ivhd_header { u8 type; u8 flags; u16 length; u16 devid; u16 cap_ptr; u64 mmio_phys; u16 pci_seg; u16 info; u32 efr_attr; } PACKED; struct IVRS { struct SDTH hdr; u32 IVinfo; u8 reserved[8]; struct ivhd_header hd_hdr; struct ivhd_entry4 hd_entries[3]; } PACKED; // We have enough space to use the second half of the 64KB table area // as scratch space for building the tables #define BUFFER_OFF 0x8000 #define P2M(p) (((u64)(p)) - phys_base + map_base) #define M2P(p) ((((void*)(p)) - map_base) + phys_base) #define B2P(p) ((((void*)(p)) - buf_base) + phys_base) #define ALIGN(s) p = (void*)((u64)(p + s - 1) & (-s)) #define PADB(s) p += (s) #define ALLOCB(s) ({void *tmp=p; PADB(s); tmp;}) #define ALLOC(t) (t)ALLOCB(sizeof(t)) #define COPYB(sz, s) ({void *tmp=p; memcpy(p, s, (sz)); p += (sz); tmp;}) #define COPYT(s) COPYB(((struct SDTH*)s)->length, s) #define COPYTP(s) COPYT(P2M(s)) #define COPY(t, s) ({void *tmp=p; *(t*)p = *(t*)s; p += sizeof(t); (t*)tmp;}) #define COPYP(t, s) COPY(t, P2M(s)) static void rsdp_checksum(struct RSDP *rsdp) { rsdp->checksum = rsdp->ext_checksum = 0; u8 sum = 0; for (int i = 0; i < 20; i++) sum += ((u8*)rsdp)[i]; rsdp->checksum = -sum; sum = 0; for (int i = 0; i < sizeof(*rsdp); i++) sum += ((u8*)rsdp)[i]; rsdp->ext_checksum = -sum; } static void table_checksum(void *table) { struct SDTH *hdr = table; hdr->checksum = 0; u8 sum = 0; for (int i = 0; i < hdr->length; i++) sum += ((u8*)table)[i]; hdr->checksum = -sum; } #define IVHD_FLAG_ISOC_EN_MASK 0x08 #define IVHD_DEV_ALL 0x01 #define IVHD_DEV_SELECT 0x02 #define IVHD_DEV_SELECT_RANGE_START 0x03 #define IVHD_DEV_RANGE_END 0x04 #define ACPI_DEVFLAG_SYSMGT1 0x10 #define ACPI_DEVFLAG_SYSMGT2 0x20 static void *build_ivrs(struct IVRS *ivrs) { memset(ivrs, 0, sizeof(*ivrs)); ivrs->hdr.sig = SIG32('I', 'V', 'R', 'S'); ivrs->hdr.length = sizeof(*ivrs); ivrs->hdr.rev = 1; memcpy(ivrs->hdr.oem_id, "F0F ", 6); memcpy(ivrs->hdr.oem_tid, "PS4KEXEC", 8); ivrs->hdr.oem_rev = 0x20161225; memcpy(ivrs->hdr.creator_id, "KEXC", 4); ivrs->hdr.creator_rev = 0x20161225; ivrs->IVinfo = 0x00203040; struct ivhd_header *hdr = &ivrs->hd_hdr; hdr->type = 0x10; hdr->flags = /*coherent | */(1 << 5) | IVHD_FLAG_ISOC_EN_MASK; hdr->length = sizeof(ivrs->hd_hdr) + sizeof(ivrs->hd_entries); hdr->devid = PCI_DEVFN(0, 2); hdr->cap_ptr = 0x40; // from config space + 0x34 hdr->mmio_phys = 0xfc000000; hdr->pci_seg = 0; hdr->info = 0; // msi msg num? (the pci cap should be written by software) // HATS = 0b10, PNBanks = 2, PNCounters = 4, IASup = 1 hdr->efr_attr = (2 << 30) | (2 << 17) | (4 << 13) | (1 << 5); struct ivhd_entry4 *entries = &ivrs->hd_entries[0]; // on fbsd, all aeolia devfns have active entries except memories (func 6) // not sure if this is just because it wasn't in use when i dumped it? // all entries are r/w // IntCtl = 0b01 and IV = 1 are set for all entries (irqs are forwarded) // apcie has SysMgt = 0b11 (others are 0b00). (device-initiated dmas are translated) // Modes: // 4 level: // apcie // 3 level: // all others // the way to encode this info into the IVHD entries is fairly arbitrary... entries[0].type = IVHD_DEV_SELECT; entries[0].devid = PCI_DEVFN(20, 0); entries[0].flags = ACPI_DEVFLAG_SYSMGT1 | ACPI_DEVFLAG_SYSMGT2; entries[1].type = IVHD_DEV_SELECT_RANGE_START; entries[1].devid = PCI_DEVFN(20, 1); entries[1].flags = 0; entries[2].type = IVHD_DEV_RANGE_END; entries[2].devid = PCI_DEVFN(20, 7); entries[2].flags = 0; table_checksum(ivrs); return ivrs + 1; } void fix_acpi_tables(void *map_base, u64 phys_base) { void *buf_base = map_base + 0x8000; void *p = buf_base; memset(buf_base, 0, 0x8000); printf("Fixing ACPI tables at 0x%llx (%p)\n", phys_base, map_base); struct RSDP *rsdp = COPYP(struct RSDP, phys_base); printf("RSDT at 0x%x\n", rsdp->rsdt_addr); printf("XSDT at 0x%llx\n", rsdp->xsdt_addr); struct RSDT *rsdt_src = P2M(rsdp->rsdt_addr); struct RSDT *rsdt = COPYTP(rsdp->rsdt_addr); rsdp->rsdt_addr = B2P(rsdt); PADB(0x30); // this gives us space for new tables struct XSDT *xsdt = COPYTP(rsdp->xsdt_addr); rsdp->xsdt_addr = B2P(xsdt); PADB(0x60); struct FADT *fadt = NULL; int cnt = (rsdt_src->hdr.length - sizeof(*rsdt)) / 4; int i; for (i = 0; i < cnt; i++) { struct SDTH *hdr = P2M(rsdt_src->table_addr[i]); printf("%c%c%c%c at 0x%x\n", PSIG32(hdr->sig), rsdt_src->table_addr[i]); switch (hdr->sig) { case SIG32('F', 'A', 'C', 'P'): { fadt = (void*)hdr; printf("FACS at 0x%x\n", fadt->facs); printf("DSDT at 0x%x\n", fadt->dsdt); // Sony puts the FACS before the FADT, unaligned, which is // noncompliant, but let's keep it there u8 *facs = COPYB(64, P2M(fadt->facs)); fadt = (void*)(hdr = COPYT(hdr)); fadt->facs = B2P(facs); PADB(0x38); break; } case SIG32('S', 'S', 'D', 'T'): { // Put the DSDT before the SSDT if (fadt) { PADB(0xf0); u8 *dsdt = COPYTP(fadt->dsdt); fadt->dsdt = B2P(dsdt); PADB(0x174); table_checksum(fadt); } else { printf("ERROR: no FADT yet?\n"); } hdr = COPYT(hdr); break; } default: hdr = COPYT(hdr); } table_checksum(hdr); xsdt->table_addr[i] = rsdt->table_addr[i] = B2P(hdr); } xsdt->table_addr[i] = rsdt->table_addr[i] = B2P(p); i++; p = build_ivrs(p); rsdt->hdr.length = sizeof(*rsdt) + 4 * i; xsdt->hdr.length = sizeof(*xsdt) + 8 * i; rsdp_checksum(rsdp); table_checksum(rsdt); table_checksum(xsdt); memcpy(map_base, buf_base, p - buf_base); } #ifdef TESTING int main(int argc, char **argv) { int fd; void *base; fd = open(argv[1], O_RDWR); base = mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); fix_acpi_tables(base, 0xe0000); return 0; } #endif