README.md
Rendering markdown...
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2018,2019 IBM Corp.
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ast.h"
#include "devmem.h"
#include "ilpc.h"
#include "log.h"
#include "mb.h"
#include "p2a.h"
#include "priv.h"
#include "rev.h"
const uint32_t bmc_dram_sizes[4] = {
[0b00] = 128 << 20,
[0b01] = 256 << 20,
[0b10] = 512 << 20,
[0b11] = 1024 << 20,
};
const uint32_t bmc_vram_sizes[4] = {
[0b00] = 8 << 20,
[0b01] = 16 << 20,
[0b10] = 32 << 20,
[0b11] = 64 << 20,
};
const char *ast_ip_state_desc[4] = {
[ip_state_unknown] = "Unknown",
[ip_state_absent] = "Absent",
[ip_state_enabled] = "Enabled",
[ip_state_disabled] = "Disabled",
};
static int ast_ilpcb_status(struct ahb *ctx, struct ast_cap_lpc *lpc)
{
uint32_t val;
int rc;
rc = ahb_readl(ctx, AST_G5_SCU | SCU_HW_STRAP, &val);
if (rc)
return rc;
lpc->superio = !(val & SCU_HW_STRAP_SIO_DEC) ?
ip_state_enabled : ip_state_disabled;
lpc->ilpc.start = 0;
lpc->ilpc.len = (1ULL << 32);
rc = ahb_readl(ctx, AST_G5_LPC | LPC_HICRB, &val);
if (rc)
return rc;
lpc->ilpc.rw = !(val & LPC_HICRB_ILPC_RO);
return 0;
}
static int ast_pci_status(struct ahb *ctx, struct ast_cap_pci *pci)
{
struct ahb_range *r;
uint32_t val;
uint32_t rev;
int rc;
rc = ahb_readl(ctx, AST_G5_SCU | SCU_PCIE_CONFIG, &val);
if (rc)
return rc;
pci->vga = (val & SCU_PCIE_CONFIG_VGA) ?
ip_state_enabled : ip_state_disabled;
pci->vga_mmio = (val & SCU_PCIE_CONFIG_VGA_MMIO) ?
ip_state_enabled : ip_state_disabled;
pci->vga_xdma = (val & SCU_PCIE_CONFIG_VGA_XDMA) ?
ip_state_enabled : ip_state_disabled;
pci->bmc = (val & SCU_PCIE_CONFIG_BMC) ?
ip_state_enabled : ip_state_disabled;
pci->bmc_mmio = (val & SCU_PCIE_CONFIG_BMC_MMIO) ?
ip_state_enabled : ip_state_disabled;
pci->bmc_xdma = (val & SCU_PCIE_CONFIG_BMC_XDMA) ?
ip_state_enabled : ip_state_disabled;
rc = ahb_readl(ctx, AST_G5_SCU | SCU_MISC, &val);
if (rc)
return rc;
rev = rev_probe(ctx);
if (rev < 0)
return rev;
if (rev_is_generation(rev, ast_g4)) {
r = &pci->ranges[p2ab_fw];
r->name = "Firmware";
r->start = 0;
r->len = 0x18000000;
r->rw = !(val & SCU_MISC_G4_P2A_FMC_RO);
r = &pci->ranges[p2ab_soc];
r->name = "SoC IO";
r->start = 0x18000000;
r->len = 0x08000000;
r->rw = !(val & SCU_MISC_G4_P2A_SOC_RO);
r = &pci->ranges[p2ab_fmc];
r->name = "BMC Flash";
r->start = 0x20000000;
r->len = 0x10000000;
r->rw = !(val & SCU_MISC_G4_P2A_FMC_RO);
r = &pci->ranges[p2ab_spi];
r->name = "Host Flash";
r->start = 0x30000000;
r->len = 0x10000000;
r->rw = !(val & SCU_MISC_G4_P2A_SPI_RO);
r = &pci->ranges[p2ab_dram];
r->name = "DRAM";
r->start = 0x40000000;
r->len = 0x20000000;
r->rw = !(val & SCU_MISC_G4_P2A_DRAM_RO);
r = &pci->ranges[p2ab_lpch];
r->name = "LPC Host";
r->start = 0x60000000;
r->len = 0x20000000;
r->rw = !(val & SCU_MISC_G4_P2A_SOC_RO);
r = &pci->ranges[p2ab_rsvd];
r->name = "Reserved";
r->start = 0x80000000;
r->len = 0x80000000;
r->rw = !(val & SCU_MISC_G4_P2A_SOC_RO);
} else if (rev_is_generation(rev, ast_g5)) {
r = &pci->ranges[p2ab_fw];
r->name = "Firmware";
r->start = 0;
r->len = 0x10000000;
r->rw = !(val & SCU_MISC_G5_P2A_FLASH_RO);
r = &pci->ranges[p2ab_soc];
r->name = "SoC IO";
r->start = 0x10000000;
r->len = 0x10000000;
r->rw = !(val & SCU_MISC_G5_P2A_SOC_RO);
r = &pci->ranges[p2ab_fmc];
r->name = "BMC Flash";
r->start = 0x20000000;
r->len = 0x10000000;
r->rw = !(val & SCU_MISC_G5_P2A_FLASH_RO);
r = &pci->ranges[p2ab_spi];
r->name = "Host Flash";
r->start = 0x30000000;
r->len = 0x10000000;
r->rw = !(val & SCU_MISC_G5_P2A_FLASH_RO);
r = &pci->ranges[p2ab_rsvd];
r->name = "Reserved";
r->start = 0x40000000;
r->len = 0x20000000;
r->rw = !(val & SCU_MISC_G5_P2A_SOC_RO);
r = &pci->ranges[p2ab_lpch];
r->name = "LPC Host";
r->start = 0x60000000;
r->len = 0x20000000;
r->rw = !(val & SCU_MISC_G5_P2A_LPCH_RO);
r = &pci->ranges[p2ab_dram];
r->name = "DRAM";
r->start = 0x80000000;
r->len = 0x80000000;
r->rw = !(val & SCU_MISC_G5_P2A_DRAM_RO);
} else {
loge("No description for the PCIe configuration layout on the %s\n",
rev_name(rev));
}
return 0;
}
static int ast_debug_status(struct ahb *ctx, struct ast_cap_uart *uart)
{
uint32_t val;
uint32_t rev;
int rc;
rev = rev_probe(ctx);
if (rev < 0)
return rev;
if (rev_is_generation(rev, ast_g6))
return 0;
if (rev_is_generation(rev, ast_g4)) {
uart->debug = ip_state_absent;
return 0;
}
rc = ahb_readl(ctx, AST_G5_SCU | SCU_MISC, &val);
if (rc)
return rc;
uart->debug = !(val & SCU_MISC_UART_DBG) ?
ip_state_enabled : ip_state_disabled;
rc = ahb_readl(ctx, AST_G5_SCU | SCU_HW_STRAP, &val);
if (rc)
return rc;
uart->uart = (val & SCU_HW_STRAP_UART_DBG_SEL) ? debug_uart5 : debug_uart1;
return 0;
}
static int ast_kernel_status(struct ahb *ctx, struct ast_cap_kernel *kernel)
{
kernel->have_devmem = (ctx->bridge == ahb_devmem);
return 0;
}
static int ast_xdma_status(struct ahb *ctx, struct ast_cap_xdma *xdma)
{
uint32_t val;
uint32_t rev;
int rc;
rev = rev_probe(ctx);
if (rev < 0)
return rev;
if (rev_is_generation(rev, ast_g6))
return 0;
rc = ahb_readl(ctx, AST_G5_SDMC | SDMC_GMP, &val);
if (rc)
return rc;
if (rev_is_generation(rev, ast_g4)) {
xdma->unconstrained = !(val & SDMC_GMP_G4_XDMA);
} else if (rev_is_generation(rev, ast_g5)) {
xdma->unconstrained = !(val & SDMC_GMP_G5_XDMA);
} else {
return -ENOTSUP;
}
return 0;
}
int ast_ahb_bridge_probe(struct ast_interfaces *state)
{
struct devmem _devmem, *devmem = &_devmem;
struct ilpcb _ilpcb, *ilpcb = &_ilpcb;
struct p2ab _p2ab, *p2ab = &_p2ab;
struct ahb _ahb, *ahb = &_ahb;
int cleanup;
int rc;
if (!priv_am_root())
return -EPERM;
logi("Probing AHB interfaces\n");
rc = devmem_init(devmem);
if (!rc) {
rc = devmem_probe(devmem);
if (rc == 1) {
ahb_use(ahb, ahb_devmem, devmem);
rc = ast_ahb_bridge_discover(ahb, state);
cleanup = devmem_destroy(devmem);
if (cleanup) { errno = -cleanup; perror("devmem_destroy"); }
return rc;
}
cleanup = devmem_destroy(devmem);
if (cleanup) { errno = -cleanup; perror("devmem_destroy"); }
}
rc = p2ab_init(p2ab, AST_PCI_VID, AST_PCI_DID_VGA);
if (!rc) {
rc = p2ab_probe(p2ab);
if (rc == 1) {
ahb_use(ahb, ahb_p2ab, p2ab);
rc = ast_ahb_bridge_discover(ahb, state);
cleanup = p2ab_destroy(p2ab);
if (cleanup) { errno = -cleanup; perror("p2ab_destroy"); }
return rc;
}
cleanup = p2ab_destroy(p2ab);
if (cleanup) { errno = -cleanup; perror("p2ab_destroy"); }
}
rc = ilpcb_init(ilpcb);
if (!rc) {
rc = ilpcb_probe(ilpcb);
if (rc == 1) {
ahb_use(ahb, ahb_ilpcb, ilpcb);
rc = ast_ahb_bridge_discover(ahb, state);
cleanup = ilpcb_destroy(ilpcb);
if (cleanup) { errno = -cleanup; perror("ilpcb_destroy"); }
return rc;
}
cleanup = ilpcb_destroy(ilpcb);
if (cleanup) { errno = -cleanup; perror("ilpcb_destroy"); }
}
return -ENOTSUP;
}
int ast_ahb_bridge_discover(struct ahb *ahb, struct ast_interfaces *state)
{
const char *chip;
int64_t val;
int rc;
logi("Performing interface discovery via %s\n",
ahb_interface_names[ahb->bridge]);
val = rev_probe(ahb);
if (val < 0)
return val;
chip = rev_name(val);
assert(chip);
logi("Detected %s\n", chip);
rc = ast_ilpcb_status(ahb, &state->lpc);
if (rc)
return rc;
rc = ast_pci_status(ahb, &state->pci);
if (rc)
return rc;
rc = ast_debug_status(ahb, &state->uart);
if (rc)
return rc;
rc = ast_kernel_status(ahb, &state->kernel);
if (rc)
return rc;
return ast_xdma_status(ahb, &state->xdma);
}
static int ast_p2ab_enable_writes(struct ahb *ahb)
{
uint32_t val;
uint32_t rev;
int rc;
logi("Disabling %s write filters\n", ahb_interface_names[ahb_p2ab]);
rc = ahb_readl(ahb, AST_G5_SCU | SCU_MISC, &val);
if (rc)
return rc;
rev = rev_probe(ahb);
if (rc < 0)
return rc;
/* Unconditionally turn off all write filters */
if (rev_is_generation(rev, ast_g4)) {
val &= ~(SCU_MISC_G4_P2A_DRAM_RO | SCU_MISC_G4_P2A_SPI_RO |
SCU_MISC_G4_P2A_SOC_RO | SCU_MISC_G4_P2A_FMC_RO);
} else if (rev_is_generation(rev, ast_g5)) {
val &= ~(SCU_MISC_G5_P2A_DRAM_RO | SCU_MISC_G5_P2A_LPCH_RO |
SCU_MISC_G5_P2A_SOC_RO | SCU_MISC_G5_P2A_FLASH_RO);
} else if (rev_is_generation(rev, ast_g6)) {
return 0;
} else {
return -ENOTSUP;
}
rc = ahb_writel(ahb, AST_G5_SCU | SCU_MISC, val);
return rc;
}
int ast_ahb_init(struct ahb *ahb, bool rw)
{
bool have_vga, have_vga_mmio, have_bmc, have_bmc_mmio, have_p2ab;
bool enabled_rw_p2ab;
struct ast_interfaces state;
uint32_t val;
int rc;
enabled_rw_p2ab = false;
rc = ast_ahb_bridge_probe(&state);
if (rc)
return rc;
if (state.kernel.have_devmem) {
logi("Detected devmem interface\n");
return ahb_init(ahb, ahb_devmem);
}
have_vga = state.pci.vga == ip_state_enabled;
have_vga_mmio = state.pci.vga_mmio == ip_state_enabled;
have_bmc = state.pci.bmc == ip_state_enabled;
have_bmc_mmio = state.pci.bmc_mmio == ip_state_enabled;
have_p2ab = (have_vga && have_vga_mmio) || (have_bmc && have_bmc_mmio);
if (!have_p2ab || (rw && !state.pci.ranges[p2ab_soc].rw)) {
if (state.lpc.superio != ip_state_enabled)
return -ENOTSUP;
if (!state.lpc.ilpc.rw && rw)
return -ENOTSUP;
rc = ahb_init(ahb, ahb_ilpcb);
if (rc || !rw)
goto cleanup;
if (!have_p2ab) {
logi("Enabling %s interface via %s\n",
ahb_interface_names[ahb_p2ab], ahb_interface_names[ahb_ilpcb]);
rc = ahb_readl(ahb, AST_G5_SCU | SCU_PCIE_CONFIG, &val);
if (rc)
goto cleanup_ahb;
val |= (SCU_PCIE_CONFIG_VGA | SCU_PCIE_CONFIG_VGA_MMIO);
rc = ahb_writel(ahb, AST_G5_SCU | SCU_PCIE_CONFIG, val);
if (rc)
goto cleanup_ahb;
rc = system("echo 1 > /sys/bus/pci/rescan");
if (rc == -1) {
rc = -errno;
goto cleanup_ahb;
} else if (rc == 127) {
rc = -EIO;
goto cleanup_ahb;
}
usleep(1000);
}
if (rw && !state.pci.ranges[p2ab_soc].rw) {
rc = ast_p2ab_enable_writes(ahb);
if (rc)
goto cleanup_ahb;
enabled_rw_p2ab = true;
}
ahb_destroy(ahb);
}
struct p2ab _p2ab, *p2ab = &_p2ab;
rc = p2ab_init(p2ab, AST_PCI_VID, AST_PCI_DID_VGA);
if (rc)
return rc;
if (p2ab_probe(p2ab) < 1) {
p2ab_destroy(p2ab);
rc = ahb_init(ahb, ahb_l2ab);
if (rc < 0)
goto cleanup;
goto done;
}
rc = ahb_init(ahb, ahb_p2ab);
if (rc)
goto cleanup;
if (!enabled_rw_p2ab) {
rc = ast_p2ab_enable_writes(ahb);
if (rc)
goto cleanup_ahb;
}
if (state.lpc.superio != ip_state_enabled) {
logi("Enabling %s interface via %s\n",
ahb_interface_names[ahb_ilpcb], ahb_interface_names[ahb_p2ab]);
val = rev_probe(ahb);
if (val < 0) { rc = val; goto cleanup_ahb; }
if (rev_is_generation(val, ast_g6)) { rc = 0; goto cleanup_ahb; }
val = SCU_HW_STRAP_SIO_DEC;
rc = ahb_writel(ahb, AST_G5_SCU | SCU_SILICON_REVISION, val);
if (rc)
goto cleanup_ahb;
if (rw && !state.lpc.ilpc.rw) {
rc = ahb_readl(ahb, AST_G5_LPC | LPC_HICRB, &val);
if (rc)
goto cleanup_ahb;
val &= ~LPC_HICRB_ILPC_RO;
rc = ahb_writel(ahb, AST_G5_LPC | LPC_HICRB, val);
if (rc)
goto cleanup_ahb;
}
}
done:
logi("Probed initialisation complete, using %s interface\n",
ahb_interface_names[ahb->bridge]);
return 0;
cleanup_ahb:
ahb_destroy(ahb);
cleanup:
loge("Probed initialisation failed: %d\n", rc);
return rc;
}
int ast_ahb_from_args(struct ahb *ahb, int argc, char *argv[])
{
/* Local interfaces */
if (argc == 0) {
logi("Probing local interfaces\n");
return ast_ahb_init(ahb, true);
}
/* Local debug interface */
if (argc == 1)
return ahb_init(ahb, ahb_debug, argv[0]);
/* Remote debug interface */
assert(argc == 5);
return ahb_init(ahb, ahb_debug, argv[0], argv[1], strtoul(argv[2], NULL, 0),
argv[3], argv[4]);
}
int ast_ahb_access(const char *name, int argc, char *argv[], struct ahb *ahb)
{
uint32_t address, data;
bool action_read;
int rc;
if (argc < 2) {
loge("Not enough arguments for ilpc command\n");
exit(EXIT_FAILURE);
}
if (!strcmp("read", argv[0]))
action_read = true;
else if (!strcmp("write", argv[0]))
action_read = false;
else {
loge("Unknown action: %s\n", argv[0]);
exit(EXIT_FAILURE);
}
address = strtoul(argv[1], NULL, 0);
if (!action_read) {
if (argc < 3) {
loge("Not enough arguments for ilpc write command\n");
exit(EXIT_FAILURE);
}
data = strtoul(argv[2], NULL, 0);
}
if (action_read) {
rc = ahb_readl(ahb, address, &data);
if (rc) {
errno = -rc;
perror("ahb_readl");
exit(EXIT_FAILURE);
}
printf("0x%08x: 0x%08x\n", address, le32toh(data));
} else {
rc = ahb_writel(ahb, address, htole32(data));
if (rc) {
errno = -rc;
perror("ahb_writel");
exit(EXIT_FAILURE);
}
}
return 0;
}