SCOM registers are responsible for configuration of many platform settings. To port hostboot into coreboot it is necessery to be able to access these registers.
According to information given on the OpenPower-firmware mailing list,
SCOM
can be accessed through XSCOM
with base address 0x000603FC00000000
.
There are a few different methods to access SCOM registers depending on what
device its on and what point of the boot you are in.
The easiest way to access a scom register is from the primary processor over
something we call xscom. XSCOM essentially boils down to accessing an MMIO
address offset by the xscom bar passed to us by the SBE via some struct.
The default value for group0's XSCOM base addr is 0x000603FC00000000 which
is a pretty safe bet if you are having a hard time finding the bootloader
structure. Now this base address can change if the SBE sets it to something
else, or we end up in some memory swapping scenario, and its going to be
different when talking to secondary processors.
To read register at offset 0xF000F:
uint64_t buffer;
asm volatile("ldcix %0, %1, %2" : "=r"(buffer) : "b"(0x800603FC00000000), "r"(0xF000F << 3));
eieio();
printk(BIOS_EMERG, "SCOM:? %llX\n", buffer);
- 0x800603FC00000000 is the
XSCOM
base address. - 0xF000F is the register address. Refer to POWER9_Registers_vol1\2\3 for more information
ldcix
performs cache inhibited read
To write SCOM register at offset 0xF0008:
printk(BIOS_EMERG, "hw trying to write 0xF0008\n");
uint64_t buffer;
asm volatile("ldcix %0, %1, %2" : "=r"(buffer) : "b"(0x800603FC00000000), "r"(0xF0008 << 3));
eieio();
printk(BIOS_EMERG, "SCOM before: %llX\n", buffer);
asm volatile("stdcix %0, %1, %2" :: "b"(0xAAAAAAAAAAAAAAAA), "b"(0x800603FC00000000), "r"(0xF0008 << 3));
eieio();
printk(BIOS_EMERG, "just wrote new value\n");
asm volatile("ldcix %0, %1, %2" : "=r"(buffer) : "b"(0x800603FC00000000), "r"(0xF0008 << 3));
eieio();
printk(BIOS_EMERG, "SCOM after: %llX\n", buffer);
All attempts were made in the coreboot
bootblock code.
It was tested on QEMU version QEMU emulator version 5.2.50 (v5.2.0-463-g657ee88ef3ec)
using following command line
./qemu-system-ppc64 -M powernv,hb-mode=on --cpu power9 --bios 'open-power/coreboot/build/coreboot.rom' -d unimp,guest_errors -serial stdio
HW tests were made on Talos II
machine using BMC.
Tested code:
long long unsigned int *reg = (void *)(0x800603FC00000000 | (0xF000F << 3));
printk(BIOS_EMERG, "SCOM:? %llX\n", *reg);
Talos II:
resets
qemu:
SCOM:? 220D104900008000
Tested code:
long long unsigned int *reg = (void *)(0x800603FC00000000 | (0xF000F << 3));
printk(BIOS_EMERG, "SCOM:? %llX\n", *reg);
Talos II:
resets
qemu:
Invalid access at addr 0x3FC000F0008, size 8, region '(null)', reason: rejected
Invalid access at addr 0x3FC000F0010, size 8, region '(null)', reason: rejected
Tested code:
long long unsigned int *reg = (void *)(0x800603FC00000000 | 0xF0010 << 3);
printk(BIOS_EMERG, "SCOM:? %llX\n", *reg);
Talos II:
resets
qemu:
Invalid access at addr <address>, size 8, region '(null)', reason: rejected
XSCOM read failed at @<address> pcba=0x0001e001
Invalid access at addr <address>, size 8, region '(null)', reason: rejected
XSCOM read failed at @<address> pcba=0x0001e002
ldcix
- Load Doubleword Caching Inhibited Indexed - loads doubleword from memory avoiding loading to cache.
0x800623FC00000000ull
base address is not working because it tagets processor with id 2. Base address should be 0x800603FC00000000
.
Tested code:
#include <arch/io.h>
uint64_t buffer;
asm volatile("ldcix %0, %1, %2" : "=r"(buffer) : "b"(0x800623FC00000000ull), "r"(0xF000F));
eieio();
printk(BIOS_EMERG, "SCOM:? %llX\n", buffer);
Talos II:
resets
qemu:
Invalid access at addr 0x623FC000F0008, size 8, region '(null)', reason: rejected
Invalid access at addr 0x623FC000F0010, size 8, region '(null)', reason: rejected
Tested code:
#include <arch/io.h>
uint64_t buffer;
asm volatile("ldcix %0, %1, %2" : "=r"(buffer) : "b"(0x800623FC00000000ull), "r"(0xF000F << 3));
eieio();
printk(BIOS_EMERG, "SCOM:? %llX\n", buffer);
Talos II:
resets
qemu:
Invalid access at addr 0x623FC00780078, size 8, region '(null)', reason: rejected
Code behavior was unchanged
Talos II: resets
qemu: Outputs information about invalid access to stdout
To access SCOM
putScom()
or getScom()
is used.
Early analysis of call chain resulted with following:
putScom()
-> platPutScom()
-> deviceWrite()
-> Singleton::instance().performOp()
-> call procedure returned by findDeviceRoute()
before findDeviceRoute(), procedure has to be registerd using Associator.registerRoute()
Call chain to register XSCOM procedure:
Associator.registerRoute()
<- DeviceFW_deviceRegisterRoute()
<- alias deviceRegisterRoute
<- macro DEVICE_REGISTER_ROUTE
<- DEVICE_REGISTER_ROUTE()
<- called on xscomPerformOp()
Analysis assumptions:
#undef __HOSTBOOT_RUNTIME
- secure boot checks aren't interesting
// src/usr/scom/scom.C
errlHndl_t checkIndirectAndDoScom(DeviceFW::OperationType i_opType,
TARGETING::Target* i_target,
void* io_buffer,
size_t& io_buflen,
int64_t i_accessType,
uint64_t i_addr)
{
bool l_runIndirectLogic = (i_addr & 0x8000000000000000);
if (!l_runIndirectLogic) {
return doScomOp(i_opType,
i_target,
io_buffer,
io_buflen,
i_accessType,
i_addr);
}
//----------------------------------------------
//--- Below here is the indirect scom logic ---
// Bits 0:3 of the address hold the indirect and form bits
// We shift out 60 bits to read the form bit here
uint8_t form = (i_addr >> 60) & 1;
// If the form is 0, we are using the "old" indirect scom method
if (form == 0) {
return doForm0IndirectScom(i_opType,
i_target,
io_buffer,
io_buflen,
i_accessType,
i_addr);
if (l_err) break;
// If form is equal to 1, we are using new FBC method
} else {
return doForm1IndirectScom(i_opType,
i_target,
io_buffer,
io_buflen,
i_accessType,
i_addr);
}
}
errlHndl_t doScomOp(DeviceFW::OperationType i_opType,
TARGETING::Target* i_target,
void* io_buffer,
size_t& io_buflen,
int64_t i_accessType,
uint64_t i_addr);
errlHndl_t doForm0IndirectScom(DeviceFW::OperationType i_opType,
TARGETING::Target* i_target,
void* io_buffer,
size_t& io_buflen,
int64_t i_accessType,
uint64_t i_addr);
/*
* data |= (addr(20:31) << 20);
* addr = addr(32:63);
*/
errlHndl_t doForm1IndirectScom(DeviceFW::OperationType i_opType,
TARGETING::Target* i_target,
void* io_buffer,
size_t& io_buflen,
int64_t i_accessType,
uint64_t i_addr)
{
uint64_t l_io_buffer = 0;
uint64_t temp_scomAddr = 0;
uint64_t l_data_from_addr = 0;
memcpy(&l_io_buffer, io_buffer, 8);
memcpy(&temp_scomAddr, &i_addr, 8);
if (i_opType == DeviceFW::READ)
die("Indirect Scom Form 1 does not support read op");
// We want to make sure the user inputted data bits 0:11 are zero
// so we can push addr(20:31) in it.
if ((l_io_buffer & 0xFFF0000000000000) != 0)
die("Data(0:11) is not zero: data out of range!");
// Set up Address reg
// cmdreg = addr(32:63)
temp_scomAddr = i_addr & 0x00000000FFFFFFFF;
// Set up data regs
// data(0:11) = addr(20:31)
l_data_from_addr = i_addr & 0x00000FFF00000000;
// Do some bit shifting so things line up nicely
l_data_from_addr <<= 20;
// data(12:63) = data(12:63)
// Set Data reg
l_io_buffer |= l_data_from_addr;
// Now perform the op requested using the
// local io_buffer with the indirect addr imbedded.
return doScomOp(i_opType,
i_target,
&l_io_buffer,
io_buflen,
i_accessType,
temp_scomAddr);
}