iBoot/drivers/apple/anc/anc_llb.c

660 lines
23 KiB
C

/*
* 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 <debug.h>
#include <drivers/anc_boot.h>
#include <platform.h>
#include <stdint.h>
#include <stdio.h>
#include <sys.h>
#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);
}