/* * Copyright (C) 2012 Apple Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include "anc_bootrom.h" #include "anc_llb.h" #include "anc_bootrom_private.h" #include "util_boot.h" anc_ppn_device_params_t *anc_geom; #define NUM_DIP 256UL #define NUM_BUS 2UL dip_info_t dip_info[NUM_DIP]; int anc_num_channel; // number of populated channels static bool anc_llb_set_normal_mode(void); static bool anc_llb_set_feature(anc_t *anc, uint16_t feature, const uint8_t *data, uint32_t len); static bool anc_llb_get_geometry(void); static bool anc_llb_read_chip_ids(void); static bool anc_llb_read_chip_id(anc_t *anc, anc_chip_id_t *chip_id); static bool anc_llb_read_device_parameters(anc_t *anc, anc_ppn_device_params_t *params); static void anc_llb_handle_geb(anc_t *anc); int anc_llb_init(void) { unsigned int dip; unsigned int die; unsigned int plane; unsigned int bus; unsigned int cau_per_die; unsigned int numDip; unsigned int numPlane; unsigned int die_per_channel; anc_geom = memalign(NUM_BUS * sizeof(anc_ppn_device_params_t), CPU_CACHELINE_SIZE); if (!anc_geom) panic("Unable to allocate anc_geom"); // Initialize ANC, but don't reset the NANDs - they're already good to go // in low-power mode coming out of SecureROM. if (anc_bootrom_init(false, ANC_BOOT_MODE_RESET_ALL_CONTROLLERS) != 0) { dprintf(DEBUG_CRITICAL, "Failed to init ANC"); return -1; } if (!anc_llb_read_chip_ids()) { dprintf(DEBUG_CRITICAL, "Failed to read chip IDs"); return -1; } if (!anc_llb_set_normal_mode()) { dprintf(DEBUG_CRITICAL, "Failed to enter normal mode"); return -1; } if (!anc_llb_get_geometry()) { dprintf(DEBUG_CRITICAL, "Failed to get geometry"); return -1; } if (anc_geom[0].caus_per_channel > (NUM_DIP / NUM_BUS)) { dprintf(DEBUG_CRITICAL, "caus_per_channel out of range: %d max %ld\n", anc_geom[0].caus_per_channel, NUM_DIP / NUM_BUS); return -1; } die_per_channel = anc_geom[0].dies_per_channel; cau_per_die = (anc_geom[0].caus_per_channel / die_per_channel); dip = 0; die = 0; bus = 0; plane = 0; // PAIRING_SCHEME_TWO_BY_TWO_PAIRING if (4 == cau_per_die && PPN_BLOCK_PAIRING__FOUR_PLANE == anc_geom[0].block_pairing_scheme) { numDip = (anc_geom[0].caus_per_channel * anc_num_channel) / 2; numPlane = cau_per_die / 2; } else { // PAIRING_SCHEME_ODD_EVEN_PAIRING, PAIRING_SCHEME_NO_PAIRING numDip = anc_geom[0].caus_per_channel * anc_num_channel; numPlane = cau_per_die; } while (dip < numDip) { dip_info[dip].cau = plane + (die * cau_per_die); dip_info[dip].bus = bus; dip++; plane++; if (plane >= numPlane) { plane = 0; bus++; if ((int) bus >= anc_num_channel) { bus = 0; die++; } } } if (4 == cau_per_die && PPN_BLOCK_PAIRING__FOUR_PLANE == anc_geom[0].block_pairing_scheme) { die = 0; bus = 0; plane = 2; numDip = anc_geom[0].caus_per_channel * anc_num_channel; numPlane = cau_per_die; while (dip < numDip) { dip_info[dip].cau = plane + (die * cau_per_die); dip_info[dip].bus = bus; dip++; plane++; if (plane >= numPlane) { plane = 2; bus++; if ((int) bus >= anc_num_channel) { bus = 0; die++; } } } } return 0; } uint32_t anc_get_dip(uint32_t bus, uint32_t cau) { uint32_t dip; uint32_t num_dip; num_dip = anc_geom[0].caus_per_channel * anc_num_channel; for (dip = 0; dip < num_dip; dip++) { if (cau == dip_info[dip].cau && bus == dip_info[dip].bus) { break; } } return dip; } uint32_t anc_get_dies_per_channel() { return anc_geom[0].dies_per_channel; } static bool anc_llb_set_normal_mode(void) { int channel; for (channel = 0; channel < anc_num_channel; channel++) { anc_t *anc = &g_boot_anc[channel]; uint32_t power_state = PPN_FEATURE__POWER_STATE__NORMAL_ASYNC; if (!anc_llb_set_feature(anc, PPN_FEATURE__POWER_STATE, (const uint8_t *)&power_state, sizeof(uint32_t))) { return false; } anc_wr(anc, R_ANC_LINK_SDR_DATA_TIMING, 0x04040404); anc_wr(anc, R_ANC_LINK_SDR_TIMING, 0x00000F0F); anc_wr(anc, R_ANC_LINK_COMMAND_ADDRESS_PULSE_TIMING, 0x00000302); anc_wr(anc, R_ANC_LINK_READ_STATUS_CONFIG, 0x00F04040); } return true; } static bool anc_llb_get_geometry(void) { int channel; for (channel = 0; channel < anc_num_channel; channel++) { anc_t *anc = &g_boot_anc[channel]; if (!anc_llb_read_device_parameters(anc, &anc_geom[channel])) { return false; } } return true; } static bool anc_llb_read_chip_ids(void) { int channel; anc_num_channel=0; for (channel = 0; channel < ANC_BOOT_CONTROLLERS; channel++) { anc_t *anc = &g_boot_anc[channel]; if (!anc_llb_read_chip_id(anc, &anc->chip_id)) { return false; } else if (anc->chip_id[0]=='P' && anc->chip_id[1]=='P' && anc->chip_id[2]=='N') { anc_num_channel++; } } return true; } static bool anc_llb_read_chip_id(anc_t *anc, anc_chip_id_t *chip_id) { bool ret = false; const uint32_t int_mask = (M_ANC_CHAN_INT_STATUS_LINK_CMD_FLAG | M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT); uint32_t int_status; anc_boot_put_link_command(anc, LINK_COMMAND__CE(1)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__READ_ID)); anc_boot_put_link_command(anc, LINK_COMMAND__ADDR1(CHIPID_ADDR)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_PIO(ANC_NAND_ID_SIZE, false, false)); anc_boot_put_link_command(anc, LINK_COMMAND__SEND_INTERRUPT(0, 0)); anc_boot_put_link_command(anc, LINK_COMMAND__CE(0)); int_status = anc_boot_wait_interrupt(anc, int_mask); if (!int_status || (int_status & M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT)) { dprintf(DEBUG_CRITICAL, "Timeout waiting for CHAN_INT_STATUS - 0x%08x\n", int_status); return false; } if (G_ANC_CHAN_INT_STATUS_LINK_CMD_FLAG(int_status)) { uint32_t id_word; uint8_t *ptr = (uint8_t *)chip_id; // Chip ID is now available in Link PIO Read FIFO id_word = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); ptr[0] = (id_word >> 0) & 0xFF; ptr[1] = (id_word >> 8) & 0xFF; ptr[2] = (id_word >> 16) & 0xFF; ptr[3] = (id_word >> 24) & 0xFF; id_word = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); ptr[4] = (id_word >> 0) & 0xFF; ptr[5] = (id_word >> 8) & 0xFF; ret = true; } return ret; } static bool anc_llb_set_feature(anc_t *anc, uint16_t feature, const uint8_t *data, uint32_t len) { bool ret; uint32_t i; const uint32_t int_mask = (M_ANC_CHAN_INT_STATUS_LINK_CMD_FLAG | M_ANC_CHAN_INT_STATUS_READ_STATUS_ERR_RESPONSE | M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT); uint32_t int_status; anc_boot_put_link_command(anc, LINK_COMMAND__CE(1)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__SET_FEATURES)); anc_boot_put_link_command(anc, LINK_COMMAND__ADDR2(feature & 0xFF, (feature & 0xFF00) >> 8)); anc_boot_put_link_command(anc, LINK_COMMAND__WRITE_PIO(len, false, false)); for (i = 0; i < len / sizeof(uint32_t); i++) { anc_boot_put_link_command(anc, data[i]); } anc_boot_put_link_command(anc, LINK_COMMAND__CMD3(NAND_CMD__SET_GET_FEATURES_CONFIRM, NAND_CMD__GET_NEXT_OPERATION_STATUS, NAND_CMD__OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_STATUS(0, 0x40, 0x40)); anc_boot_put_link_command(anc, LINK_COMMAND__SEND_INTERRUPT(0, 0)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__GET_NEXT_OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__CE(0)); int_status = anc_boot_wait_interrupt(anc, int_mask); if (!int_status || (int_status & M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT)) { panic("Timeout waiting for CHAN_INT_STATUS - 0x%08x\n", int_status); return false; } if (G_ANC_CHAN_INT_STATUS_LINK_CMD_FLAG(int_status)) { ret = true; } else { ret = false; } return ret; } static bool anc_llb_read_device_parameters(anc_t *anc, anc_ppn_device_params_t *params) { const uint32_t intmask = (M_ANC_CHAN_INT_STATUS_DMA_CMD_FLAG | M_ANC_CHAN_INT_STATUS_DMA_CMD_TIMEOUT | M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT | M_ANC_CHAN_INT_STATUS_READ_STATUS_ERR_RESPONSE); uint32_t intstatus; bool ret; uint64_t paddr = mem_static_map_physical((uintptr_t)params); anc_boot_put_dma_command(anc, DMA_COMMAND_CONFIG(DMA_DIRECTION_N2M, false)); anc_boot_put_link_command(anc, LINK_COMMAND__CE(1)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__READ_DEVICE_PARAMETERS)); anc_boot_put_link_command(anc, LINK_COMMAND__ADDR1(0x00)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD3(NAND_CMD__READ_DEVICE_PARAMETERS_CONFIRM, NAND_CMD__GET_NEXT_OPERATION_STATUS, NAND_CMD__OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_STATUS(0, 0x40, 0x40)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__READ_SERIAL_OUTPUT)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_DMA(sizeof(anc_ppn_device_params_t), false, false)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__GET_NEXT_OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__CE(0)); #if WITH_NON_COHERENT_DMA platform_cache_operation(CACHE_CLEAN, params, sizeof(anc_ppn_device_params_t)); #endif anc_boot_put_dma_command(anc, DMA_COMMAND_BUFDESC(sizeof(anc_ppn_device_params_t), paddr)); anc_boot_put_dma_command(anc, DMA_COMMAND_FLAG(0, 0)); intstatus = anc_boot_wait_interrupt(anc, intmask); if (!intstatus || (intstatus & M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT)) { panic("Timeout waiting for CHAN_INT_STATUS - 0x%08x\n", intstatus); return false; } if (intstatus & M_ANC_CHAN_INT_STATUS_DMA_CMD_TIMEOUT) { dprintf(DEBUG_CRITICAL, "Timeout waiting for DMA to complete"); ret = false; } else if (intstatus & M_ANC_CHAN_INT_STATUS_READ_STATUS_ERR_RESPONSE) { dprintf(DEBUG_INFO, "Unexpected NAND status\n"); ret = false; } else { ret = true; } #if WITH_NON_COHERENT_DMA platform_cache_operation(CACHE_INVALIDATE, params, sizeof(anc_ppn_device_params_t)); #endif return ret; } static uint32_t make_physical_page(uint32_t dip, uint32_t band, uint16_t page) { const unsigned int block_bits = anc_geom[0].block_bits; const unsigned int page_bits = anc_geom[0].page_address_bits; const unsigned int cau_bits = anc_geom[0].cau_bits; unsigned int mode; if((dip_info[dip].cau == 0) && (band == 0)) { mode = 0; } else { if (anc_geom[0].address_bits_bits_per_cell > 1) { // Device supports high-endurance SLC mode = 3; } else { // Device only supports regular SLC/MLC modes. Treat this high-endurance // work as regular SLC mode = 1; } } // LLB will always use SLC (but the PPN spec requires we not set the mode bit on CAU 0, block 0) return (page | (band << page_bits) | (dip_info[dip].cau << (page_bits + block_bits)) | (mode << (page_bits + block_bits + cau_bits))); } uint32_t anc_llb_read_phys_page(uint32_t band, // returns # of valid pages uint32_t dip, uint32_t page, uint32_t num_lbas, void *data, uint32_t *meta) { anc_t *anc = &g_boot_anc[dip_info[dip].bus]; const uint32_t intmask = (M_ANC_CHAN_INT_STATUS_LINK_CMD_FLAG | M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT | M_ANC_CHAN_INT_STATUS_READ_STATUS_ERR_RESPONSE); uint32_t intstatus; unsigned int lba; addr_t paddr; uint32_t ret = num_lbas; uint32_t ppage = make_physical_page(dip, band, page); bool encrypted = false; uint8_t nand_status; paddr = mem_static_map_physical((addr_t)data); if (!paddr) { dprintf(DEBUG_INFO, "No buffer\n"); return 0; } if (!meta) { dprintf(DEBUG_INFO, "No meta buffer\n"); return 0; } if (num_lbas == 0) { dprintf(DEBUG_INFO, "Invalid num_lbas %d\n", num_lbas); return 0; } if ((platform_get_chip_id() == 0x8960) && (platform_get_chip_revision() == 0)) { // On Alcatraz A0, we encrypt everything but dip 0, block 0 (due to rdar://problem/11247422) if ((dip == 0) && (band == 0)) { encrypted = false; } else { encrypted = true; } } else { encrypted = false; } #if WITH_NON_COHERENT_DMA platform_cache_operation(CACHE_CLEAN | CACHE_INVALIDATE, data, num_lbas * NAND_BOOT_LOGICAL_PAGE_SIZE); #endif anc_boot_put_dma_command(anc, DMA_COMMAND_CONFIG(DMA_DIRECTION_N2M, encrypted)); if (encrypted) { anc_boot_put_dma_command(anc, DMA_COMMAND_AES_KEY_IV(DMA_COMMAND_AESKEY_256_BITS, false)); // Key of all zeroes anc_boot_put_dma_command(anc, 0x0000000000000000ULL); anc_boot_put_dma_command(anc, 0x0000000000000000ULL); anc_boot_put_dma_command(anc, 0x0000000000000000ULL); anc_boot_put_dma_command(anc, 0x0000000000000000ULL); // IV of all zeroes anc_boot_put_dma_command(anc, 0x0000000000000000ULL); anc_boot_put_dma_command(anc, 0x0000000000000000ULL); } anc_boot_put_link_command(anc, LINK_COMMAND__CE(1)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__MULTIPAGE_READ_LAST)); if (anc_geom[dip_info[dip].bus].bytes_per_row_address != 4) { anc_boot_put_link_command(anc, LINK_COMMAND__OPCODE(LINK_CMD_OP_ADDR3_OP) | (page & 0xFFFFFF)); } else { anc_boot_put_link_command(anc, LINK_COMMAND__OPCODE(LINK_CMD_OP_ADDR4) | (ppage & 0xFFFFFF)); anc_boot_put_link_command(anc, (ppage & 0xFF000000) >> 24); } anc_boot_put_link_command(anc, LINK_COMMAND__CMD3(NAND_CMD__MULTIPAGE_READ_CONFIRM, NAND_CMD__GET_NEXT_OPERATION_STATUS, NAND_CMD__OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_STATUS(0x00, 0x50, 0x40)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_REGISTER(R_ANC_LINK_NAND_STATUS - A_ANC_LINK_BASE)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__READ_SERIAL_OUTPUT)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); for (lba = 0; lba < num_lbas; lba++) { anc_boot_put_link_command(anc, LINK_COMMAND__READ_PIO(NAND_BOOT_BYTES_PER_META, false, false)); if (lba == (num_lbas - 1)) { anc_boot_put_link_command(anc, LINK_COMMAND__SEND_INTERRUPT(0, 0)); } anc_boot_put_link_command(anc, LINK_COMMAND__READ_DMA(NAND_BOOT_LOGICAL_PAGE_SIZE, false, false)); if (encrypted && (lba != 0)) { anc_boot_put_dma_command(anc, DMA_COMMAND__OPCODE(DMA_COMMAND__OPCODE__AES_IV)); anc_boot_put_dma_command(anc, 0x0000000000000000ULL); anc_boot_put_dma_command(anc, 0x0000000000000000ULL); } anc_boot_put_dma_command(anc, DMA_COMMAND_BUFDESC(NAND_BOOT_LOGICAL_PAGE_SIZE, (uint64_t)paddr + lba * NAND_BOOT_LOGICAL_PAGE_SIZE)); } anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(32)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__GET_NEXT_OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__CE(0)); anc_boot_put_dma_command(anc, DMA_COMMAND_FLAG(0, 0)); intstatus = anc_boot_wait_interrupt(anc, intmask); if (!intstatus) { dprintf(DEBUG_CRITICAL, "Timeout waiting for CHAN_INT_STATUS != 0\n"); return 0; } // We've either timed out or gotten our status by now if (intstatus & M_ANC_CHAN_INT_STATUS_LINK_CMD_TIMEOUT) { // Timeout waiting for status: flush LINK CmdQ and return error dprintf(DEBUG_CRITICAL, "Timeout waiting for NAND status\n"); anc_wr(anc, R_ANC_LINK_CONTROL, M_ANC_LINK_CONTROL_RESET_CMDQ); return 0; } else if (intstatus & M_ANC_CHAN_INT_STATUS_READ_STATUS_ERR_RESPONSE) { uint8_t status = anc_rd(anc, R_ANC_LINK_NAND_STATUS); // Invalid status (probably has error bit set due to a clean or uncorrectable page) if (BOOT_LBA_TOKEN_UNKNOWN != meta[0]) // scanning blocks - may be clean or uecc { dprintf(DEBUG_CRITICAL, "Invalid NAND status 0x%02X\n", status); } anc_wr(anc, R_ANC_LINK_CONTROL, M_ANC_LINK_CONTROL_START); ret = 0; meta[0] = BOOT_ERR_BLANK; } nand_status = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); if (nand_status != 0x40) { if (BOOT_LBA_TOKEN_UNKNOWN != meta[0]) // scanning blocks - may be clean or uecc { dprintf(DEBUG_CRITICAL, "Invalid NAND status: 0x%02X\n", nand_status); } ret = 0; meta[0] = BOOT_ERR_BLANK; } for (lba = 0; lba < num_lbas; lba++) { uint32_t meta_index; for (meta_index = 0; meta_index < (NAND_BOOT_BYTES_PER_META / sizeof(uint32_t)); meta_index++) { uint32_t meta_word = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); if ((meta_index == 0) && (meta_word != meta[0])) { if (BOOT_LBA_TOKEN_UNKNOWN == meta[0]) { meta[0] = meta_word; } else if (BOOT_ERR_BLANK != meta[0]) { if (lba==0) { dprintf(DEBUG_CRITICAL, "Invalid meta: expected 0x%08X got 0x%08x\n", meta[0], meta_word); } ret = lba; } } } } intstatus = anc_boot_wait_interrupt(anc, M_ANC_CHAN_INT_STATUS_DMA_CMD_FLAG | M_ANC_CHAN_INT_STATUS_DMA_CMD_TIMEOUT); if (!intstatus || (intstatus & M_ANC_CHAN_INT_STATUS_DMA_CMD_TIMEOUT)) { dprintf(DEBUG_CRITICAL, "Timeout waiting for DMA to complete - 0x%08x\n", intstatus); ret = 0; } anc_wr(anc, R_ANC_CHAN_INT_STATUS, M_ANC_CHAN_INT_STATUS_DMA_CMD_FLAG); #if WITH_NON_COHERENT_DMA platform_cache_operation(CACHE_INVALIDATE, data, num_lbas * NAND_BOOT_LOGICAL_PAGE_SIZE); #endif return ret; } typedef struct { uint16_t failure_type; uint32_t start_page; uint8_t page_len[3]; uint8_t checksum; } anc_ppn_failure_info_t; static void anc_llb_handle_geb(anc_t *anc) { uint32_t buffer[3]; dprintf(DEBUG_CRITICAL, "General Error Dected on Bus %d, attempting to pull failure info", anc->bus_id); anc_wr(anc, R_ANC_CHAN_INT_ENABLE, 0); anc_wr(anc, R_ANC_DMA_CONTROL, V_ANC_DMA_CONTROL_RESET(1) | V_ANC_DMA_CONTROL_START(1)); anc_wr(anc, R_ANC_LINK_CONTROL, (V_ANC_LINK_CONTROL_RESET_CMDQ(1) | V_ANC_LINK_CONTROL_RESET_PIO_READ(1) | V_ANC_LINK_CONTROL_START(1))); anc_boot_put_link_command(anc, LINK_COMMAND__CE(1)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__GET_DEBUG_DATA)); anc_boot_put_link_command(anc, LINK_COMMAND__ADDR3(0xFF, 0xFF, 0xFF)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__SET_GET_FEATURES_CONFIRM)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(129000)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__OPERATION_STATUS)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(1)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_STATUS(0, 0x40, 0x40)); anc_boot_put_link_command(anc, LINK_COMMAND__CMD1(NAND_CMD__READ_SERIAL_OUTPUT)); anc_boot_put_link_command(anc, LINK_COMMAND__WAIT_TIME(1)); anc_boot_put_link_command(anc, LINK_COMMAND__READ_PIO(10, false, false)); anc_boot_put_link_command(anc, LINK_COMMAND__CE(0)); while (G_ANC_CHAN_LINK_PIO_READ_FIFO_STATUS_LEVEL(anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO_STATUS)) < 3) { ; } buffer[0] = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); buffer[1] = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); buffer[2] = anc_rd(anc, R_ANC_CHAN_LINK_PIO_READ_FIFO); panic("GEB detected failure type 0x%04X start page 0x%08X", ((anc_ppn_failure_info_t *)buffer)->failure_type, ((anc_ppn_failure_info_t *)buffer)->start_page); }