| // Copyright 2019 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include "arch/arm64/dap.h" |
| |
| #include <bits.h> |
| #include <inttypes.h> |
| #include <lib/arch/intrin.h> |
| #include <lib/boot-options/boot-options.h> |
| #include <lib/console.h> |
| #include <platform.h> |
| #include <stdio.h> |
| #include <trace.h> |
| #include <zircon/time.h> |
| |
| #include <arch/arm64/mp.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/array.h> |
| #include <kernel/auto_preempt_disabler.h> |
| #include <kernel/cpu.h> |
| #include <ktl/iterator.h> |
| #include <lk/init.h> |
| #include <vm/vm_aspace.h> |
| |
| #include <ktl/enforce.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| namespace { |
| |
| struct dap_aperture { |
| paddr_t base; |
| size_t size; |
| uint cpu_base; |
| void *virt; |
| }; |
| |
| struct debug_port { |
| bool initialized; |
| cpu_num_t cpu_num; |
| volatile uint32_t *dap; // pointer to the DAP register window |
| volatile uint32_t *cti; // pointer to the CTI register window |
| }; |
| |
| fbl::Array<dap_aperture> dap_apertures; |
| fbl::Array<debug_port> daps; |
| |
| void arm_dap_init(uint level) { |
| LTRACE_ENTRY; |
| |
| enum { |
| None, |
| t931g, |
| s905d2, |
| s905d3g, |
| } soc = None; |
| |
| // Parse the valid options out of the kernel command line |
| // kernel.arm64.debug.dap-rom-soc |
| if (strcmp(gBootOptions->arm64_debug_dap_rom_soc.data(), "amlogic-t931g") == 0) { |
| soc = t931g; |
| } else if (strcmp(gBootOptions->arm64_debug_dap_rom_soc.data(), "amlogic-s905d2") == 0) { |
| soc = s905d2; |
| } else if (strcmp(gBootOptions->arm64_debug_dap_rom_soc.data(), "amlogic-s905d3g") == 0) { |
| soc = s905d3g; |
| } else if (strcmp(gBootOptions->arm64_debug_dap_rom_soc.data(), "") != 0) { |
| dprintf(INFO, "ARM DAP: unrecognized non-empty option passed '%s'\n", |
| gBootOptions->arm64_debug_dap_rom_soc.data()); |
| } |
| if (soc == None) { |
| return; |
| } |
| |
| // Set the ROM table locations to search in for DAP apertures for each cpu |
| { |
| DEBUG_ASSERT(soc != None); |
| |
| // Pre-canned values for the debug rom table base, taken from |
| // the manuals for these particular SOCs. |
| // TODO: read this from ZBI as well |
| // clang-format off |
| static dap_aperture t931g_aperture[] = { |
| { .base = 0xf580'0000, // A53_BASE |
| .size = 0x80'0000, |
| .cpu_base = 0, |
| }, |
| { .base = 0xf500'0000, // A73_BASE |
| .size = 0x80'0000, |
| .cpu_base = 2, |
| } |
| }; |
| static dap_aperture s905_aperture[] = { |
| { .base = 0xf580'0000, // A53_BASE |
| .size = 0x80'0000, |
| .cpu_base = 0, |
| }, |
| }; |
| // clang-format on |
| |
| switch (soc) { |
| case t931g: |
| // two dap register windows for T931G |
| dap_apertures = {t931g_aperture, ktl::size(t931g_aperture)}; |
| break; |
| case s905d2: |
| case s905d3g: |
| // s905d2 and s905d3g have the same aperture |
| dap_apertures = {s905_aperture, ktl::size(s905_aperture)}; |
| break; |
| case None:; |
| } |
| } |
| |
| // allocate a list of parsed dap structures, to be filled in by each cpu as they run their init |
| // hook |
| { |
| fbl::AllocChecker ac; |
| |
| debug_port *dp = new (&ac) debug_port[arch_max_num_cpus()]{}; |
| if (!ac.check()) { |
| return; |
| } |
| daps = {dp, arch_max_num_cpus()}; |
| } |
| |
| dprintf(INFO, "DAP: enabling dap for %s\n", gBootOptions->arm64_debug_dap_rom_soc.data()); |
| |
| // map the dap base into the kernel |
| for (auto &da : dap_apertures) { |
| LTRACEF("mapping aperture: base %#lx size %#zx cpu base %u\n", da.base, da.size, da.cpu_base); |
| |
| zx_status_t err = VmAspace::kernel_aspace()->AllocPhysical( |
| "arm dap", |
| da.size, // size |
| &da.virt, // requested virtual vaddress |
| PAGE_SIZE_SHIFT, // alignment log2 |
| da.base, // physical vaddress |
| 0, // vmm flags |
| ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | |
| ARCH_MMU_FLAG_UNCACHED_DEVICE); // arch mmu flags |
| if (err != ZX_OK) { |
| printf("failed to map dap address\n"); |
| return; |
| } |
| |
| LTRACEF("dap address %p\n", da.virt); |
| } |
| } |
| |
| LK_INIT_HOOK(arm_dap, arm_dap_init, LK_INIT_LEVEL_ARCH) |
| |
| // identify if this is a component according to coresight spec, and return the class id |
| bool is_component(volatile void *_regs, uint32_t *class_out) { |
| volatile uint32_t *regs = static_cast<volatile uint32_t *>(_regs); |
| |
| uint32_t cidr[4]; |
| cidr[0] = regs[0xff0 / 4]; |
| cidr[1] = regs[0xff4 / 4]; |
| cidr[2] = regs[0xff8 / 4]; |
| cidr[3] = regs[0xffc / 4]; |
| |
| LTRACEF("cidr %#x %#x %#x %#x\n", cidr[0], cidr[1], cidr[2], cidr[3]); |
| |
| // does it have a coresight component signature |
| if (BITS(cidr[0], 7, 0) != 0xd || BITS(cidr[1], 3, 0) != 0x0 || // type 1 rom table |
| BITS(cidr[2], 7, 0) != 0x5 || BITS(cidr[3], 7, 0) != 0xb1) { |
| return false; |
| } |
| |
| // read the class nibble |
| *class_out = BITS_SHIFT(cidr[1], 7, 4); |
| return true; |
| } |
| |
| zx_status_t parse_coresight_debug_component(volatile void *_regs, bool dump = false) { |
| volatile uint32_t *regs = static_cast<volatile uint32_t *>(_regs); |
| |
| // are we a coresight component? |
| uint32_t class_id; |
| if (!is_component(regs, &class_id)) { |
| if (dump) { |
| printf("not a coresight component\n"); |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| if (class_id != 0x9) { |
| // not a coresight class |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // read common coresight debug regs |
| uint32_t devtype = regs[0xfcc / 4]; |
| uint32_t major = BITS(devtype, 3, 0); |
| uint32_t minor = BITS_SHIFT(devtype, 7, 4); |
| uint64_t devaff = regs[0xfac / 4]; |
| devaff = (devaff << 32) | regs[0xfa8 / 4]; |
| |
| if (dump) { |
| printf("coresight debug devtype %#x: major %#x minor %#x\n", devtype, major, minor); |
| printf("devaff %#lx, cpu num %u\n", devaff, arm64_mpidr_to_cpu_num(devaff)); |
| } |
| |
| // is this dap for me? |
| uint64_t mpidr = __arm_rsr64("mpidr_el1"); |
| if (mpidr != devaff) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| auto curr_cpu = arch_curr_cpu_num(); |
| |
| #define MAJOR_MINOR(x, y) (((y) << 4 | (x) << 0)) |
| switch (BITS(devtype, 7, 0)) { |
| case MAJOR_MINOR(3, 1): // trace source, ETM |
| // TODO: add CTI |
| break; |
| case MAJOR_MINOR(4, 1): { // trigger matrix, CTI |
| // TODO: add CTI |
| if (dump) { |
| printf("CTI for me: cpu %u base %p\n", curr_cpu, regs); |
| } |
| if (!dump) { |
| debug_port *da = &daps[curr_cpu]; |
| da->cpu_num = curr_cpu; |
| da->cti = regs; |
| if (da->cti && da->dap) { |
| da->initialized = true; |
| } |
| } |
| break; |
| } |
| case MAJOR_MINOR(5, 1): { // DAP per processor core |
| // TODO: add dap |
| if (dump) { |
| printf("DAP for me: cpu %u base %p\n", curr_cpu, regs); |
| } |
| if (!dump) { |
| debug_port *da = &daps[curr_cpu]; |
| da->cpu_num = curr_cpu; |
| da->dap = regs; |
| if (da->cti && da->dap) { |
| da->initialized = true; |
| } |
| } |
| break; |
| } |
| case MAJOR_MINOR(6, 1): // performance monitor, PMU |
| // TODO: add dap |
| break; |
| default:; |
| } |
| #undef MAJOR_MINOR |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t parse_rom_table(volatile void *_rom, bool dump = false) { |
| // start parsing the rom table |
| volatile uint32_t *rom = static_cast<volatile uint32_t *>(_rom); |
| |
| if (dump) { |
| printf("parsing rom table at %p\n", rom); |
| } |
| |
| // is this a rom table? |
| uint32_t table_class_id; |
| if (!is_component(rom, &table_class_id)) { |
| if (dump) { |
| printf("not a coresight component\n"); |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| if (table_class_id != 1) { |
| // not a type 1 rom table |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // walk through the rom table until the last possible index or a terminal entry |
| for (uint index = 0; index < 0xfd0 / 4; index++) { |
| if (rom[index] == 0) { |
| // terminal entry |
| break; |
| } |
| if (dump) { |
| printf("entry %u: %#x\n", index, rom[index]); |
| } |
| |
| if (BITS(rom[index], 1, 0) != 0x3) { |
| // not present or not 32bit |
| continue; |
| } |
| |
| // recurse, seeing if this is a component |
| size_t offset = rom[index] & 0xffff'f000; // mask off bits 11:0 |
| volatile void *component_virt = (volatile uint8_t *)_rom + offset; |
| uint32_t class_id; |
| if (!is_component(component_virt, &class_id)) { |
| continue; |
| } |
| |
| if (dump) { |
| printf("found component at offset %#zx, class %#x\n", offset, class_id); |
| } |
| |
| // see if we want to recurse |
| switch (class_id) { |
| case 0x1: // another type 1 table, recurse |
| parse_rom_table(component_virt, dump); |
| break; |
| case 0x9: // Coresight Debug component |
| parse_coresight_debug_component(component_virt, dump); |
| break; |
| default: |
| if (dump) { |
| printf("unhandled component class %#x\n", class_id); |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| // per cpu walk the dap rom tables, looking for debug components associated with this cpu |
| void arm_dap_init_percpu(uint level) { |
| LTRACE_ENTRY; |
| |
| LTRACEF("mdrar %#lx\n", __arm_rsr64("mdrar_el1")); |
| LTRACEF("dbgauthstatus %#lx\n", __arm_rsr64("dbgauthstatus_el1")); |
| |
| if (dap_apertures) { |
| for (auto &da : dap_apertures) { |
| if (!da.virt) { |
| continue; |
| } |
| |
| // start parsing the rom table |
| volatile uint32_t *rom = static_cast<volatile uint32_t *>(da.virt); |
| |
| parse_rom_table(rom); |
| } |
| } |
| } |
| |
| LK_INIT_HOOK_FLAGS(arm_dap_percpu, arm_dap_init_percpu, LK_INIT_LEVEL_ARCH + 1, |
| LK_INIT_FLAG_ALL_CPUS) |
| |
| // helper class to access registers within a memory block |
| template <typename T> |
| class RegBlock { |
| public: |
| explicit RegBlock(volatile uint32_t *regs) : regs_(regs) {} |
| |
| void Write(T reg_offset, uint32_t val) { |
| regs_[static_cast<size_t>(reg_offset) / 4u] = val; |
| arch::DeviceMemoryBarrier(); |
| } |
| |
| uint32_t Read(T reg_offset) { |
| uint32_t val = regs_[static_cast<size_t>(reg_offset) / 4u]; |
| return val; |
| } |
| |
| zx_status_t WaitFor(T reg_offset, uint32_t mask, uint32_t val, |
| zx_duration_t timeout = ZX_MSEC(250)) { |
| zx_time_t t; |
| if (timeout != ZX_TIME_INFINITE) { |
| t = current_time(); |
| } |
| |
| uint32_t temp; |
| do { |
| temp = Read(reg_offset); |
| if (timeout != ZX_TIME_INFINITE && (zx_time_sub_time(current_time(), t) >= timeout)) { |
| TRACEF("timed out, val %#x\n", temp); |
| return ZX_ERR_TIMED_OUT; |
| } |
| } while ((temp & mask) != val); |
| |
| return ZX_OK; |
| } |
| |
| private: |
| volatile uint32_t *regs_; |
| }; |
| |
| // cti registers (move to top) |
| enum class cti_regs { |
| CTICONTROL = 0x0, |
| CTIINTACK = 0x10, |
| CTIAPPPULSE = 0x1c, |
| CTIOUTEN0 = 0xa0, |
| CTIGATE = 0x140, |
| CTILAR = 0xfb0, |
| CTILSR = 0xfb4, |
| }; |
| |
| enum class dap_regs { |
| DBGDTRRX = 0x80, |
| EDITR = 0x84, |
| EDSCR = 0x88, |
| DBGDTRTX = 0x8c, |
| EDRCR = 0x90, |
| EDPRSR = 0x314, |
| EDLAR = 0xfb0, |
| EDLSR = 0xfb4, |
| DBGAUTHSTATUS = 0xfb8 |
| }; |
| |
| // some pre-canned arm instructions |
| constexpr uint32_t arm64_nop = 0xd503201f; // nop |
| constexpr uint32_t arm64_msr_dbgdtr = 0xd5130400; // msr dbgdtr_el0, x0 -- write x0 to dbgdtr |
| constexpr uint32_t arm64_mov_sp = 0x910003e0; // mov x0, sp |
| constexpr uint32_t arm64_mrs_dlr = 0xd53b4520; // mrs x0, dlr_el0 -- write dlr to x0 |
| constexpr uint32_t arm64_mrs_dspsr = 0xd53b4500; // mrs x0, dspsr_el0 -- write dspsr to x0 |
| constexpr uint32_t arm64_mrs_esr_el1 = 0xd5385200; // mrs x0, esr_el1 -- write esr_el1 to x0 |
| constexpr uint32_t arm64_mrs_esr_el2 = 0xd53c5200; // mrs x0, esr_el2 -- write esr_el2 to x0 |
| constexpr uint32_t arm64_mrs_far_el1 = 0xd5386000; // mrs x0, far_el1 -- write far_el1 to x0 |
| constexpr uint32_t arm64_mrs_far_el2 = 0xd53c6000; // mrs x0, far_el2 -- write far_el2 to x0 |
| constexpr uint32_t arm64_mrs_elr_el1 = 0xd5384020; // mrs x0, elr_el1 -- write elr_el1 to x0 |
| constexpr uint32_t arm64_mrs_elr_el2 = 0xd53c4020; // mrs x0, elr_el2 -- write elr_el2 to x0 |
| |
| zx_status_t run_instruction(RegBlock<dap_regs> &dap, uint32_t instruction, bool trace = false) { |
| zx_status_t err; |
| |
| if (trace) { |
| printf("DAP: running instruction %#x\n", instruction); |
| } |
| dap.Write(dap_regs::EDRCR, (1 << 3)); // clear the EDSCR.PipeAdv bit |
| |
| // wait for EDSCR.PipeAdv == 0 and EDSCR.ITE == 1 |
| err = dap.WaitFor(dap_regs::EDSCR, (1 << 25) | (1 << 24), (1 << 24)); |
| if (err != ZX_OK) |
| return err; |
| |
| // write the instruction |
| dap.Write(dap_regs::EDITR, instruction); |
| |
| // wait for EDSCR.PipeAdv == 1 and EDSCR.ITE == 1 |
| // TODO: figure out why pipeadv doesn't always set |
| // err = dap.WaitFor(dap_regs::EDSCR, (1<<25) | (1<<24), (1<<25) | (1<<24)); |
| // if (err != ZX_OK) return err; |
| |
| if (trace) { |
| printf("DAP: done running instruction %#x\n", instruction); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t read_dcc(RegBlock<dap_regs> &dap, uint64_t *val) { |
| auto err = dap.WaitFor(dap_regs::EDSCR, (1 << 29), (1 << 29)); // wait for TXFull |
| if (err != ZX_OK) |
| return err; |
| |
| *val = ((uint64_t)dap.Read(dap_regs::DBGDTRRX) << 32) | dap.Read(dap_regs::DBGDTRTX); |
| |
| return ZX_OK; |
| } |
| |
| // Fetch a register from the target processor. |
| // |
| // We do this by executing on the remote processor a given instruction that |
| // is expected to write the target register into x0. This register is then |
| // written to DBGDTR on the remote processor, and then read locally. |
| zx_status_t fetch_remote_register(RegBlock<dap_regs> &dap, uint32_t reg_read_instruction, |
| uint64_t *result) { |
| // Fetch register to x0. |
| zx_status_t err = run_instruction(dap, reg_read_instruction); |
| if (err != ZX_OK) { |
| return err; |
| } |
| |
| // Write to DBGDTR. |
| err = run_instruction(dap, arm64_msr_dbgdtr); |
| if (err != ZX_OK) { |
| return err; |
| } |
| |
| // Read the result. |
| err = read_dcc(dap, result); |
| if (err != ZX_OK) { |
| return err; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t read_processor_state(RegBlock<dap_regs> &dap, arm64_dap_processor_state *state) { |
| zx_status_t err; |
| |
| // Clear out state. |
| *state = arm64_dap_processor_state{}; |
| |
| // save a copy of the EDSCR which has EL level and other things |
| state->edscr = dap.Read(dap_regs::EDSCR); |
| uint8_t el_level = state->get_el_level(); |
| |
| // read x0 - x30 |
| // mov xN -> dbgdtr, read out of our end of the DCC |
| for (uint32_t i = 0; i <= 30; i++) { |
| err = run_instruction(dap, arm64_msr_dbgdtr | i); |
| if (err != ZX_OK) |
| return err; |
| |
| err = read_dcc(dap, &state->r[i]); |
| if (err != ZX_OK) |
| return err; |
| } |
| |
| // Read the PC (saved in the DLR_EL0 register), SP, and CPSR (saved in DSPSR_EL0). |
| struct Register { |
| uint32_t instruction; |
| uint64_t *dest; |
| }; |
| for (const auto ® : { |
| Register{.instruction = arm64_mrs_dlr, .dest = &state->pc}, |
| Register{.instruction = arm64_mov_sp, .dest = &state->sp}, |
| Register{.instruction = arm64_mrs_dspsr, .dest = &state->cpsr}, |
| }) { |
| err = fetch_remote_register(dap, reg.instruction, reg.dest); |
| if (err != ZX_OK) { |
| return err; |
| } |
| } |
| |
| // If running in EL1 or above, fetch EL1 exception state. |
| if (el_level >= 1) { |
| for (const auto ® : { |
| Register{.instruction = arm64_mrs_esr_el1, .dest = &state->esr_el1}, |
| Register{.instruction = arm64_mrs_far_el1, .dest = &state->far_el1}, |
| Register{.instruction = arm64_mrs_elr_el1, .dest = &state->elr_el1}, |
| }) { |
| err = fetch_remote_register(dap, reg.instruction, reg.dest); |
| if (err != ZX_OK) { |
| return err; |
| } |
| } |
| } |
| |
| // If running in EL2 or above, fetch EL2 exception state. |
| if (el_level >= 2) { |
| for (const auto ® : { |
| Register{.instruction = arm64_mrs_esr_el2, .dest = &state->esr_el2}, |
| Register{.instruction = arm64_mrs_far_el2, .dest = &state->far_el2}, |
| Register{.instruction = arm64_mrs_elr_el2, .dest = &state->elr_el2}, |
| }) { |
| err = fetch_remote_register(dap, reg.instruction, reg.dest); |
| if (err != ZX_OK) { |
| return err; |
| } |
| } |
| } |
| |
| // TODO: put x0 back so the cpu could be restarted |
| |
| return ZX_OK; |
| } |
| |
| #if LK_DEBUGLEVEL > 0 |
| void cpu_debug_command(cpu_num_t cpu) { |
| printf("attempting to debug cpu %u\n", cpu); |
| |
| if (cpu == 0 || cpu >= arch_max_num_cpus()) { |
| printf("invalid cpu, cannot be 0 or out of bounds\n"); |
| return; |
| } |
| |
| // body of the debug logic, to run on cpu 0 |
| auto dap_debug_thread = [](void *arg) -> int { |
| AutoPreemptDisabler pd; |
| |
| cpu_num_t cpu = (cpu_num_t)(uintptr_t)(arg); |
| printf("victim cpu %u\n", cpu); |
| |
| arm64_dap_processor_state state = {}; |
| auto err = arm64_dap_read_processor_state(cpu, &state); |
| if (err != ZX_OK) { |
| printf("failed to read processor state, err %d\n", err); |
| return err; |
| } |
| |
| state.Dump(); |
| |
| return ZX_OK; |
| }; |
| |
| // create a thread to run on cpu 0 to run this |
| auto thread = |
| Thread::Create("dap debug", dap_debug_thread, (void *)(uintptr_t)cpu, DEFAULT_PRIORITY); |
| thread->SetCpuAffinity(cpu_num_to_mask(0)); |
| thread->DetachAndResume(); |
| } |
| |
| void dump() { |
| printf("mdrar %#lx\n", __arm_rsr64("mdrar_el1")); |
| printf("dbgauthstatus %#lx\n", __arm_rsr64("dbgauthstatus_el1")); |
| |
| if (!dap_apertures || !daps) { |
| printf("DAP not detected\n"); |
| return; |
| } |
| |
| for (auto &da : dap_apertures) { |
| printf("DAP aperture at %p, length %#zx\n", da.virt, da.size); |
| } |
| |
| for (auto &dap : daps) { |
| printf("cpu %u DAP %p CTI %p initialized %d\n", dap.cpu_num, dap.dap, dap.cti, dap.initialized); |
| } |
| } |
| |
| void dump_rom_table() { |
| if (!dap_apertures) { |
| printf("DAP not detected\n"); |
| return; |
| } |
| |
| for (auto &da : dap_apertures) { |
| // start parsing the rom table |
| volatile uint32_t *rom = static_cast<volatile uint32_t *>(da.virt); |
| |
| parse_rom_table(rom, true); |
| } |
| } |
| |
| int cmd_dap(int argc, const cmd_args *argv, uint32_t flags) { |
| if (argc < 2) { |
| notenoughargs: |
| printf("not enough arguments\n"); |
| usage: |
| printf("usage:\n"); |
| printf("%s dump\n", argv[0].str); |
| printf("%s dump_rom_table\n", argv[0].str); |
| printf("%s cpu_debug <n>\n", argv[0].str); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| if (!strcmp(argv[1].str, "dump")) { |
| dump(); |
| } else if (!strcmp(argv[1].str, "dump_rom_table")) { |
| dump_rom_table(); |
| } else if (!strcmp(argv[1].str, "cpu_debug")) { |
| if (argc < 3) { |
| goto notenoughargs; |
| } |
| cpu_debug_command(static_cast<cpu_num_t>(argv[2].u)); |
| } else { |
| printf("unknown command\n"); |
| goto usage; |
| } |
| |
| return ZX_OK; |
| } |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND("dap", "arm debug port", &cmd_dap) |
| STATIC_COMMAND_END(dap) |
| #endif |
| |
| } // namespace |
| |
| // External routines |
| |
| // top level entry point, try to drop the victim cpu into debug state and dump the register state |
| zx_status_t arm64_dap_read_processor_state(cpu_num_t victim, arm64_dap_processor_state *state) { |
| // pin ourselves to the current cpu |
| AutoPreemptDisabler pd; |
| |
| if (!arm64_dap_is_enabled()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // find the CTI for this cpu |
| auto ctir = daps[victim].cti; |
| auto dapr = daps[victim].dap; |
| |
| RegBlock<cti_regs> cti(ctir); |
| RegBlock<dap_regs> dap(dapr); |
| LTRACEF("dbgauthstatus %#x\n", dap.Read(dap_regs::DBGAUTHSTATUS)); |
| |
| // try to unlock the dap |
| LTRACEF("edlsr %#x\n", dap.Read(dap_regs::EDLSR)); |
| dap.Write(dap_regs::EDLAR, 0xC5ACCE55); |
| LTRACEF("edlsr %#x\n", dap.Read(dap_regs::EDLSR)); |
| |
| // unlock the cti |
| LTRACEF("ctilsr %#x\n", cti.Read(cti_regs::CTILSR)); |
| cti.Write(cti_regs::CTILAR, 0xC5ACCE55); |
| LTRACEF("ctilsr %#x\n", cti.Read(cti_regs::CTILSR)); |
| |
| // enable the cti |
| LTRACEF("cticontrol %#x\n", cti.Read(cti_regs::CTICONTROL)); |
| cti.Write(cti_regs::CTICONTROL, 1); // make sure CTI is enabled |
| |
| // try to put the victim cpu in debug mode |
| LTRACEF("ctigate %#x\n", cti.Read(cti_regs::CTIGATE)); |
| cti.Write(cti_regs::CTIGATE, 0); // mask off all internal channels |
| LTRACEF("ctigate %#x\n", cti.Read(cti_regs::CTIGATE)); |
| cti.Write(cti_regs::CTIOUTEN0, 1); // generate input event to channel 0 debug request |
| cti.Write(cti_regs::CTIAPPPULSE, 1); // generate debug event |
| |
| // read the status register of the processor |
| // TODO: add timeout |
| zx_status_t err = dap.WaitFor(dap_regs::EDPRSR, (1 << 4), (1 << 4)); |
| if (err != ZX_OK) { |
| printf("DAP: failed to drop cpu %u into debug mode, error %d\n", victim, err); |
| return err; |
| } |
| |
| // cpu is stopped |
| printf("DAP: cpu %u is in debug state\n", victim); |
| |
| // ack the CTI |
| cti.Write(cti_regs::CTIINTACK, 1); |
| |
| // shove a nop down the hole to see if it works |
| err = run_instruction(dap, arm64_nop); |
| if (err != ZX_OK) { |
| printf("DAP: failed to run first instruction on cpu, error %d\n", err); |
| return err; |
| } |
| |
| // load the full state of the cpu |
| err = read_processor_state(dap, state); |
| if (err != ZX_OK) { |
| printf("DAP: failed to read processor state, error %d\n", err); |
| return err; |
| } |
| |
| // TODO: restart the cpu |
| |
| return ZX_OK; |
| } |
| |
| bool arm64_dap_is_enabled() { |
| if (!dap_apertures || !daps) { |
| return false; |
| } |
| |
| // see if we've detected and initialized DAP ports for all the cpus |
| for (const auto &dap : daps) { |
| if (!dap.initialized) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void arm64_dap_processor_state::Dump(FILE *fp) { |
| fprintf(fp, "x0 %#18" PRIx64 " x1 %#18" PRIx64 " x2 %#18" PRIx64 " x3 %#18" PRIx64 "\n", // |
| r[0], r[1], r[2], r[3]); |
| fprintf(fp, "x4 %#18" PRIx64 " x5 %#18" PRIx64 " x6 %#18" PRIx64 " x7 %#18" PRIx64 "\n", // |
| r[4], r[5], r[6], r[7]); |
| fprintf(fp, "x8 %#18" PRIx64 " x9 %#18" PRIx64 " x10 %#18" PRIx64 " x11 %#18" PRIx64 "\n", // |
| r[8], r[9], r[10], r[11]); |
| fprintf(fp, "x12 %#18" PRIx64 " x13 %#18" PRIx64 " x14 %#18" PRIx64 " x15 %#18" PRIx64 "\n", // |
| r[12], r[13], r[14], r[15]); |
| fprintf(fp, "x16 %#18" PRIx64 " x17 %#18" PRIx64 " x18 %#18" PRIx64 " x19 %#18" PRIx64 "\n", // |
| r[16], r[17], r[18], r[19]); |
| fprintf(fp, "x20 %#18" PRIx64 " x21 %#18" PRIx64 " x22 %#18" PRIx64 " x23 %#18" PRIx64 "\n", // |
| r[20], r[21], r[22], r[23]); |
| fprintf(fp, "x24 %#18" PRIx64 " x25 %#18" PRIx64 " x26 %#18" PRIx64 " x27 %#18" PRIx64 "\n", // |
| r[24], r[25], r[26], r[27]); |
| fprintf(fp, "x28 %#18" PRIx64 " x29 %#18" PRIx64 " lr %#18" PRIx64 " sp %#18" PRIx64 "\n", // |
| r[28], r[29], r[30], sp); |
| fprintf(fp, "\n"); |
| fprintf(fp, "pc %#18" PRIx64 "\n", pc); |
| fprintf(fp, "cpsr %#18" PRIx64 "\n", cpsr); |
| fprintf(fp, "edscr %#18" PRIx32 ": EL %u\n", edscr, get_el_level()); |
| fprintf(fp, "\n"); |
| if (get_el_level() >= 1) { |
| fprintf(fp, "elr_el1 %#18" PRIx64 " far_el1 %#18" PRIx64 " esr_el1 %#18" PRIx64 "\n", // |
| elr_el1, far_el1, esr_el1); |
| } |
| if (get_el_level() >= 2) { |
| fprintf(fp, "elr_el2 %#18" PRIx64 " far_el2 %#18" PRIx64 " esr_el2 %#18" PRIx64 "\n", // |
| elr_el2, far_el2, esr_el2); |
| } |
| } |