-
Notifications
You must be signed in to change notification settings - Fork 22
Adding Architectures
This is a step-by-step guide to add support for a new target architecture. If you add an architecture, you do not have to implement everything described here, but the library may offer less features and/or worse performance for your architecture.
This step is a pre-requisite for everything else.
First, choose a name for the translation format(s). Use a generic name based on the name of the ISA rather than a specific implementation. Add suffixes to distinguish between multiple translation variants if necessary.
Second, write a test case for the target address translation. Of course, the
test will fail at this point, but you can use it to track your progress. More
importantly, you can use this test case to make sure that already implemented
features do not break as you write more code. Name your test case like
tests/addrxlat-newarch-variant
. Start with this dummy template:
#! /bin/sh
pf="newarch:12,9,9"
list="0:0" # dummy
. "$srcdir"/addrxlat-common
Don't forget to make the script executable and add it to the test_scripts
variable in test/Makefile.am
. If you run the test suite now, the new test
should fail like this:
Checking 0... Unknown PTE format: newarch:12,9,9
ERROR
Cannot translate 0
Note: You can run a single test case from the test suite with a command like
srcdir=. ./addrxlat-newarch-variant
from the tests
directory. However, if
you take this shortcut, remember to rebuild the library and tests/addrxlat
after every change, e.g. with (cd .. && make) && make addrxlat
.
Next, add an identifier for your architecture's translation format to the
addrxlat_pte_format_t
enum in include/libkdumpfile/addrxlat.h.in
. Add a
string representation of the translation format to the pte_format_names
array in src/addrxlat/step.c
. The output of your test case should change to:
Checking 0... Address translation failed: Unknown PTE format
FAILED
You can start implementing actual address translation now. If your architecture offers various formats of a page table entry, you may want to start with the simplest variant and gradually expand your code.
Always start by adding a test case. Change the dummy page table and list of
addresses in tests/addrxlat-newarch-variant
to add the expected page
translation result, e.g.:
ptes="-e 0x000:0x1000" # PGD[0] -> 1000
ptes="$ptes -e 0x1000:0xa000" # PGD[0] -> PTE[0] -> a000
list="0x123:0xa123" # PGT[0] -> PTE[0]
Next, create a new file src/addrxlat/newarch.c
. Assuming your target
architecture does nothing fancy, you can modify this very minimal stub:
#include "addrxlat-priv.h"
/** Page mask. */
#define PAGE_MASK ADDR_MASK(12)
/** New architecture page table step function.
* @param step Current step state.
* @returns Error status.
*/
addrxlat_status
pgt_newarch(addrxlat_step_t *step)
{
addrxlat_pte_t pte;
addrxlat_status status;
status = read_pte32(step, &pte);
if (status != ADDRXLAT_OK)
return status;
step->base.as = step->meth->target_as;
step->base.addr = pte & ~PAGE_MASK;
if (step->remain == 1)
step->elemsz = 1;
return ADDRXLAT_OK;
}
Note that these are just the bare bones of a step functions. The function does not even check a present (or valid) bit, required by all architectures known to me.
Add the above file to libaddrxlat_la_SOURCES
in src/addrxlat/Makefile.am
.
Add the declaration of your page table step function to
src/addrxlat/addrxlat-priv.h
(sorted alphabetically):
INTERNAL_DECL(addrxlat_next_step_fn, pgt_newarch, );
To use the generic page table translation helpers, let the library know the
size of a single page table entry by extending pteval_shift()
in
src/addrxlat/addrxlat-priv.h
. Make it return 2 for 32-bit entries and 3 for
64-bit entries.
Next, extend the switches in first_step_pgt()
and next_step_pgt()
. The
latter should obviously call pgt_newarch(step)
, but the former may be less
clear:
- Call
first_step_pgt_generic(step, addr)
if your architecture uses all bits of the translation input address, or if unused bits are ignored. - Call
first_step_uaddr(step, addr)
if unused bits must be zero. - Call
first_step_saddr(step, addr)
if unused bits must be a copy of the highest used bit (the sign bit). - Write a new helper if none of the above applies.
When you're done with this part, make a commit. These commits are titled like Add newarch page table translation.
This section also deals with the generic OS handling. If you intend to support only a non-Linux operating system, you will also need to implement the generic infrastructure described here.
First, start with a test case. The generic base name follows the pattern of
xlat-linux-newarch-version-variant
, where -variant
may be missing. Each
test case uses the following files:
- script (no suffix),
- expected results (
.expect
suffix), - (optional) symbolic values (
.sym
suffix), - (optional) memory content (
.data
suffix).
Add the script to the test_scripts
variable and the other file names to the
dist_check_DATA
variable in tests/Makefile.am
.
The script file defines options for addrxlat_sys_os_init()
. A minimal base
for Linux would be:
#! /bin/bash
opts=(
arch=newarch
ostype=linux
)
. "$srcdir"/xlat-os-common
Note: You can run a single test case from the test suite with a command like
srcdir=. ./xlat-linux-newarch-version
from the tests
directory. However,
if you take this shortcut, remember to rebuild the library and tests/xlat-os
after every change, e.g. with (cd .. && make) && make xlat-os
.
Initially, this test should fail with:
Checking... OS map failed: Unsupported architecture
ERROR
Good. Let's add the base infrastructure. Add a stub initialization function to
src/addrxlat/newarch.c
:
/** Initialize a translation map for a newarch OS.
* @param ctl Initialization data.
* @returns Error status.
*/
addrxlat_status
sys_newarch(struct os_init_data *ctl)
{
switch (ctl->os_type) {
default:
return set_error(ctl->ctx, ADDRXLAT_ERR_NOTIMPL,
"OS type not implemented");
}
}
Also put a declaration in src/addrxlat/addrxlat-priv.h
:
INTERNAL_DECL(sys_arch_fn, sys_newarch, );
Finally, add a check to the conditional in addrxlat_sys_os_init()
at
src/addrxlat/sys.c
. FIXME: This should be somehow merged into the
pte_format_names
array.
Verify that the new function is used by the test case. The failure message should change to:
Checking... OS map failed: OS type not implemented
ERROR
Last, implement the actual logic. This depends on how address translation is
set up in the kernel for your specific architecture. The goal is to initialize
a working translation system. This usually means setting up page table
translation using the initial page table hierarchy (swapper_pg_dir
). If you
also set up the reverse method for 1:1 direct mapping, the translation system
can be used to translate from physical addresses to virtual addresses. This is
useful if your target format can handle only virtual addresses.
Do not forget to set up the translation between machine physical and kernel
physical addresses. In most cases, this is simply an identity mapping covering
all physical addresses, and there is a helper for that: sys_set_physmaps()
.
The .expect
file for the test case is a dump of the resulting translation
system. It contains:
- translation methods (prefixed with
@
) - translation mappings (input AS
->
output AS)
If symbol values are required for the test, provide them in a .syms
file. If
dump memory content is required, provide it in a .data
file.
Have a look at some of the already implemented architectures for inspiration.
CPU registers are usually stored in an ELF note, which may be embedded in different file formats. The library core takes care of extracting the PRSTATUS note content, but the interpretation of the data must be implemented for each new architecture.
First, write a test case. This is a bit ugly, because the test utilities can
deal only with raw binary content, but here we go. The test should be called
elf-prstatus-newarch
. Since most fields of the ELF PRSTATUS note are common
to all architectures, you can start by copying the script and data file for an
existing architecture with the same word size. In elf-prstatus-newarch.data
,
remove the existing register values and add values for the new architecture.
By convention, the values correspond to register names. In the script itself
(elf-prstatus-newarch
), change:
- the
e_machine
field to the correspondingEM_NEWARCH
value according to the ELF specification, -
cpu.0.PRSTATUS
to match your data blob, - all register values (
cpu.0.reg.*
).
The test case should complain about missing attributes.
Note: You can run a single test case from the test suite with a command like
srcdir=. ./elf-prstatus-newarch
from the tests
directory. However, if you
take this shortcut, remember to rebuild the library and tests/checkattr
after every change, e.g. with (cd .. && make) && make checkattr
.
First, add an enumerator for your architecture to enum kdump_arch
in
src/kdumpfile/kdumpfile-priv.h
and add it to the canon_arch_names
array in
src/kdumpfile/util.c
. Update some utility functions in the same file:
arch_ptr_size()
machine_arch_name()
default_page_shift()
Extend mach2arch()
in src/kdumpfile/elfdump.c
to recognize ELF dumps from
the new architecture and devmem_probe()
in src/kdumpfile/devmem.c
to
initialize live memory source.
Next, create a new file src/kdumpfile/newarch.c
and add it to
libkdumpfile_la_SOURCES
in src/kdumpfile/Makefile.am
. Define struct elf_prstatus
for the new architecture like this (for a 64-bit architecture):
#include "kdumpfile-priv.h"
#define ELF_NGREG 16
struct elf_siginfo
{
int32_t si_signo;
int32_t si_code;
int32_t si_errno;
} __attribute__((packed));
struct elf_prstatus
{
struct elf_siginfo pr_info;
int16_t pr_cursig;
char _pad1[2];
uint64_t pr_sigpend;
uint64_t pr_sighold;
int32_t pr_pid;
int32_t pr_ppid;
int32_t pr_pgrp;
int32_t pr_sid;
struct timeval_64 pr_utime;
struct timeval_64 pr_stime;
struct timeval_64 pr_cutime;
struct timeval_64 pr_cstime;
uint64_t pr_reg[ELF_NGREG];
/* optional UNUSED fields may follow */
} __attribute__((packed));
Then add register definitions like this:
#define REG(name, field) \
DERIVED_NUMBER(#name, 1, struct elf_prstatus, field)
static struct derived_attr_def newarch_reg_attrs[] = {
REG(r0, pr_reg[0]),
REG(r1, pr_reg[1]),
REG(r2, pr_reg[2]),
REG(r3, pr_reg[3]),
REG(r4, pr_reg[4]),
REG(r5, pr_reg[5]),
REG(r6, pr_reg[6]),
REG(r7, pr_reg[7]),
REG(r8, pr_reg[8]),
REG(r9, pr_reg[9]),
REG(r10, pr_reg[10]),
REG(r11, pr_reg[11]),
REG(r12, pr_reg[12]),
REG(r13, pr_reg[13]),
REG(r14, pr_reg[14]),
REG(r15, pr_reg[15]),
DERIVED_NUMBER("pid", 0, struct elf_prstatus, pr_pid),
};
Then write the process_prstatus
arch-op for your architecture, e.g.:
static kdump_status
process_newarch_prstatus(kdump_ctx_t *ctx, const void *data, size_t size)
{
unsigned cpu;
kdump_status status;
cpu = get_num_cpus(ctx);
set_num_cpus(ctx, cpu + 1);
status = init_cpu_prstatus(ctx, cpu, data, size);
if (status != KDUMP_OK)
return set_error(ctx, status, "Cannot set CPU %u %s",
cpu, "PRSTATUS");
if (size < sizeof(struct elf_prstatus))
return set_error(ctx, KDUMP_ERR_CORRUPT,
"Wrong PRSTATUS size: %zu", size);
status = create_cpu_regs(
ctx, cpu, newarch_reg_attrs, ARRAY_SIZE(newarch_reg_attrs));
return status;
}
const struct arch_ops newarch_ops = {
.process_prstatus = process_newarch_prstatus,
};
Add a declaration to src/kdumpfile/kdumpfile-priv.h
:
INTERNAL_DECL(extern const struct arch_ops, newarch_ops, );
Last, modify arch_ops
in src/kdumpfile/util.c
to return these arch-ops for
your architecture.