Friday, April 14, 2017

Porting the Linux Kernel to a New ARM Platform

1.Terminology

architecture
Either the CPU-type (as in 'ARM architecture', 'x86 architecture'), or category of system design as in 'footbridge architecture' or 'Intel® SA-1100 architecture'. It is sometimes used to mean the same as 'machine' below too;
device
A hardware item that the kernel accesses using a device-driver;
machine
The particular hardware, determined by assigned machine-ID within kernel;
platform
Same as machine;
sub-architecture
Same as machine;

2.Overview of Files

The ARM-specific files are in linux/arch/arm (code), and linux/include/asm-arm (header files). In a configured kernel tree, the appropriate architecture headers directory (linux/include/asm-arm in our case) appears as linux/include/asm so that it can be easily referred to by other files that don't need to know which architecture they are dealing with.

Device drivers go in the relevant parts of the main tree; usually linux/drivers/, but sometimes linux/fs or /linux/net if there are filesystems, partition types or network protocols specific to your device. Within the ARM-specific directories your new or changed files go in appropriate linux/arch/arm/mach-XXX and linux/include/asm-arm/arch-XXX directories. After configuration your headers directory linux/include/asm-arm/arch-XXX appears as linux/include/asm-arm/arch so that the correct machine header files can be included by using this path.

The other directories in linux/arch/arm/ contain the generic ARM code.
kernel
core kernel code;
mm
memory management code;
lib
ARM-specific or optimized internal library functions (backtrace, memcpy, io functions, bit-twiddling etc);
nwfpe&fastfpe
two different floating-point implementations;
boot
the directory in which the final compiled kernel is left and contains stuff for generating compressed kernels;
tools
has scripts for autogenerating files, such as mach-types;
def-configs
contains the default configuration files for each machine;
The non machine-specific directories in linux/include/asm-arm are:
arch
the link to the configured machine headers sub-directory arch-XXX;
hardware
headers for ARM-specific companion chips or devices;
mach
generic interface definitions for things used by many machines (irq, dma, pci) and the machine description macros;
proc
link to proc-armo or proc-armv appropriate for configured machine;
proc-armo, and proc-armv
26 and 32-bit versions of core processor-related headers (armo is for 26-bit stuff; armv is for 32-bit stuff);

3.Registering a Machine ID

Each device is identified in the kernel tree by a machine ID. These are allocated by the kernel maintainer to keep the huge number of ARM device variants manageable in the source trees.

Then you need to add the info to linux/arch/arm/tools/mach-types with a line like this:
machine_is_xxx
CONFIG_xxx
MACH_TYPE_xxx
machine_ID
lart
SA1100_LART
LART
27
anakin
ARCH_ANAKIN
ANAKIN
57
The above file is needed so that the script linux/arch/arm/tools/gen-mach-types can generate linux/include/asm-arm/machtypes.h which sets the defines and macros mentioned above that are used by much of the source to select the appropriate code. You should always use these macros in your code, and not test machine_arch_type nor __machine_arch_type directly as this will produce less efficient code

4.Config files

Add a new config file in linux/arch/arm/def-configs/ named <machine-name>, containing the default configuration options for your machine. You should also edit linux/arch/arm/config.in so that make config will support your machine. This file specifies the new CONFIG_ symbols for your machine and the dependencies of them on other CONFIG_ symbols.
When you do make <machine-name>_config, e.g. make anakin_config, to build a kernel, and then the file corresponding to the first part of the parameter is copied out of linux/arch/arm/def-configs/ to linux/.config.

5.Kernel Basics

There are a number of basic symbols that you need to know the meanings of to understand the kernel sources. Here is a list of the most important ones.
Your hardware specifications deal in physical memory. One of the fundamental things you need to specify is the mapping between these two. This is contained in the __virt_to_phys() macro in include/asm-arm/arch-XXX/memory.h (along with corresponding reverse mappings). Normally, this macro is simply:
phys = virt - PAGE_OFFSET + PHYS_OFFSET

5.1 Decompressor Symbols
ZTEXTADDR
Start address of decompressor. As the MMU is off at the time this code is called the addresses are physical. You normally call the kernel at this address to start it booting. This doesn't have to be located in RAM; it can be in flash or other read-only or read-write addressable medium.
ZBSSADDR
Start address of zero-initialised work area for the decompressor. This must be pointing at RAM. The decompressor will zero initialise this for you. Again, the MMU will be off.
ZRELADDR
This is the address where the decompressed kernel will be written, and eventually executed. The following constraint must be valid:
__virt_to_phys(TEXTADDR) == ZRELADDR
The initial part of the kernel is carefully coded to be position independent.
INITRD_PHYS
Physical address to place the initial RAM disk. Only relevant if you are using the bootpImage stuff (which only works on the older struct param_struct style of passing the kernel boot information).
INITRD_VIRT
Virtual address of the initial RAM disk. The following constraint must be valid:
__virt_to_phys(INITRD_VIRT) == INITRD_PHYS
PARAMS_PHYS
Physical address of the struct param_struct or tag list, giving the kernel various parameters about its execution environment.

5.2 Kernel Symbols
PHYS_OFFSET
Physical start address of the first bank of RAM.
PAGE_OFFSET
Virtual start address of the first bank of RAM. During the kernel boot phase, virtual address PAGE_OFFSET will be mapped to physical address PHYS_OFFSET, along with any other mappings you supply. This should be the same value as TASK_SIZE.
TASK_SIZE
The maximum size of a user process in bytes. Since user space always starts at zero, this is the maximum address that a user process can access. The user space stack grows down from this address.
Any virtual address below TASK_SIZE is deemed to be user process area, and therefore managed dynamically on a process by process basis by the kernel. This is referred to as the 'user segment'.
Anything above TASK_SIZE is common to all processes. This is referred to as the 'kernel segment'.
Note that this means that you can't put IO mappings below TASK_SIZE, and hence PAGE_OFFSET.
TEXTADDR
Virtual start address of kernel, normally PAGE_OFFSET+0x8000. This is where the kernel image ends up. With the latest kernels, it must be located at 32768 bytes into a 128MB region. Previous kernels just required it to be in the first 256MB region.
DATAADDR
Virtual address for the kernel data segment. Must not be defined when using the decompressor.
VMALLOC_START, VMALLOC_END
Virtual addresses bounding the vmalloc() area. There must not be any static mappings in this area; vmalloc will overwrite them. The addresses must also be in the kernel segment (see above). Normally, the vmalloc() area starts VMALLOC_OFFSET bytes above the last virtual RAM address (found using variable high_memory).
VMALLOC_OFFSET
Offset normally incorporated into VMALLOC_START to provide a hole between virtual RAM and the vmalloc area. We do this to allow out of bounds memory accesses (eg, something writing off the end of the mapped memory map) to be caught. Normally set to 8MB.

5.3 Architecture Specific Macros
BOOT_MEM(pram, pio, vio)
`pram' specifies the physical start address of RAM. Must always be present, and should be the same as PHYS_OFFSET.
`pio' is the physical address of an 8MB region containing IO for use with the debugging macros in arch/arm/kernel/debug-armv.S.
`vio' is the virtual address of the 8MB debugging region. It is expected that the debugging region will be re-initialized by the architecture specific code later in the code (via the MAPIO function).
BOOT_PARAMS
Same as, and see PARAMS_PHYS.
FIXUP(func)
Machine specific fixups, run before memory subsystems have been initialized.
MAPIO(func)
Machine specific function to map IO areas (including debug region above).
INITIRQ(func)
Machine specific function to initialize interrupts

6.Kernel Porting


arch/arm/Makefile
Insert the following to this file (replace XXX with your machine name):
ifeq ((CONFIG_ARCH_XXX),y)
MACHINE = xxx
endif
arch/arm/boot/Makefile
Here you specify ZTEXTADDR, the start address of the kernel decompressor. This should have the same value as the address to which Linux is uploaded in your boot loader. This is normally 32K (0x8000) above the base of RAM. The space between the start of RAM and the kernel is used for page tables.
ifeq ((CONFIG_ARCH_XXX),y)
ZTEXTADDR = 0xXXXX8000
endif
arch/arm/kernel/entry-armv.S
Machine-specific IRQ functions. You provide the assembly macros disable_fiq, get_irqnr_and_base, and irq_prio_table here. disable_fiq and irq_prio_table is usually empty, but get_irqnr_and_base must be implemented carefully: you should use the zero flag to indicate the presence of interrupts, and put the correct IRQ number in irqnr.
arch/arm/kernel/debug-armv.S
These are the low-level debug functions, which talk to a serial port without relying on interrupts or any other kernel functionality. You'll need to use these functions if it won't boot. The functions you need to implement are addruart, senduart and waituart, using ARM assembly. They give you the address of the debug UART, send a byte to the debug UART, and wait for the debug UART, respectively.
arch/arm/mach-XXX/Makefile
You need to add a target for your machine, listing the object files in this directory. That will be at least the following:
+O_TARGET := MACHINE.o
+obj-y := arch.o irq.o mm.o
arch/arm/mach-XXX/irq.c
You should provide the XXX_init_irq function here. This sets up the interrupt controller. Interrupt mask and unmask functions go here too.
arch/arm/mach-XXX/mm.c
This file is now deprecated. Its content (IO initialisation) has moved into arch/arm/mach-XXX/arch.c
include/asm/arch/dma.h
Defines for DMA channels, and DMA-able areas of memory. For machines without DMA, you can just declare 0 DMA channels as follows:
#define MAX_DMA_ADDRESS 0xffffffff
#define MAX_DMA_CHANNELS 0
arch/arm/mach-XXX/arch.c
This should contain the architecture-specific fix ups and IO initialisations. (The latter used to go in arch/arm/mach-XXX/mm.c but that file is now deprecated). For the meaning of the symbols, refer to Kernel Basics.
The setup for your machine is done with a set of macros, starting with MACHINE_START. The parameters you give are filled in to a data structure machine_desc describing the machine.
One of the items is the fixup function which, if specified, will be called to fill in or adjust entries dynamically at boot time.
This is useful for detecting optional items needed at boot-time (e.g.VRAM in a Risc PC). Note that it should not be used to do main memory size detection, which is the job of the bootloader.
static void __init fixup_XXX(struct machine_desc *desc, struct param_struct *params, char **cmdline, struct meminfo *mi){
ROOT_DEV = MKDEV(RAMDISK_MAJOR, 0);
setup_ramdisk(1, 0, 0, CONFIG_BLK_DEV_RAM_SIZE);
setup_initrd(0xc0800000, X * 1024 * 1024);
}
MACHINE_START(ANAKIN, "XXX")
MAINTAINER("Acunia N.V.")
BOOT_MEM(XXX, YYY, ZZZ)
VIDEO(VVV, WWW)
FIXUP(fixup_XXX)
MAPIO(XXX_map_io)
INITIRQ(genarch_init_irq)
MACHINE_END
static struct map_desc XXX_io_desc[] __initdata ={
IO BASE, IO START, IO SIZE, DOMAIN IO, 0,1,0,0,
LAST_DESC
};
void __init XXX_map_io(void) {
iotable_init(XXX_io_desc);
}
include/asm/arch/hardware.h
In this file, you need to define the memory addresses, IO addresses, and so on, according to your hardware specifications (memory map and IO map). The _START addresses are the physical addresses, the _BASE addresses are the virtual addresses to which each memory or IO region will be mapped.
include/asm/arch/io.h
Here you define the macros IO_SPACE_LIMIT (as 0xffffffff), __io(addr), __arch_getw(addr), __arch_putw(data,addr), and other related macros, according to your CPU. For CPUs that already have an implementation, you can just copy it across and/or reuse the existing io.h for that CPU.
include/asm/arch/irq.h
Here you need to provide the fixup_irq macro. In almost all cases, this is just a direct mapping: #define fixup_irq(i)  i
include/asm/arch/irqs.h
include/asm/arch/keyboard.h
In this file you will define all your IRQ numbers. For example:
#define IRQ_UART0 0
This file is typically here to cheat the VT driver into thinking that there is a null keyboard. Most ARM devices don't have a real one. If you do this is where to put the keyboard IO defines and structs.
include/asm/arch/memory.h
Unless you have an exotic memory-map this is platform-invariant and you can copy this from other implementations.
include/asm/arch/param.h
This is included by asm/param.h. Here you can redefine HZ (default 100), NGROUPS (default -1), and MAXHOSTNAMELEN (default 64). If you are okay with the above defaults, you still need to create this file but you can make it an empty file (like the Anakin port).
include/asm/arch/vmalloc.h
This file is largely invariant across platforms, so you can just copy it from other ARM architectures without worrying too much.
include/asm/arch/time.h
Here you have to supply your timer interrupt handler and related functions. See the template below:
/*
* XXX_gettimeoffset is not mandatory. For example, anakin has
* not yet implemented it. dummy_gettimeoffset (defined in
* arch/arm/kernel/time.c) is the default handler, if you omit
* XXX_gettimeoffset.
*/
static unsigned long XXX_gettimeoffset(void)
{
/* Return number of microseconds since last interrupt*/
}
static void XXX_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/* Add hardware specific stuffs, if applicable */
do_timer(regs);
do_profile (regs);
}
extern inline void setup_timer(void){
gettimeoffset = XXX_gettimeoffset;
timer_irq.handler = XXX_timer_interrupt;
setup_arm_irq(IRQ_XXX, &timer_irq);
/* Other hardware specific setups */
}
The do_profile() function is there to allow kernel profiling.
include/asm/arch/timex.h
This file is included by include/asm/timex.h, which is in turn included by include/linux/timex.h. Basically you need to define your clock rate here. For example, for Anakin it is 1/8ms:
#define CLOCK_TICK_RATE 1000 / 8
include/asm/arch/uncompress.h
This file is included by arch/arm/boot/compressed/misc.c (which, among other things, outputs the Uncompressing Linux message). You are required to provide two functions, arch_decomp_setup to setup the UART, and puts for outputting the decompression message to the UART:
static void_puts(char char *s)
/*
* Hardware-specific routine to put a string to the debug
* UART, converting "\n" to "\n\r" on the way.
*/
static inline void arch_decomp_setup(void)
/*
* Hardware-specific routine to put a string to setup the
* UART mentioned above.
*/
/* Watchdog is not used for most ARM Linux implementations */
#define arch_decomp_wdog()
include/asm/arch/system.h
This file is included by arch/arm/kernel/process.c. You are required to define arch_idle() and arch_reset() functions. arch_idle() is called whenever the machine has no other process to run - i.e. it's time to sleep. arch_reset() is called on system reset. The actual implementation of these functions depends on your specific hardware, and there are some subtleties associated with arch_idle(). This function will normally put the hardware of your specific device into a low-power mode and then call the generic cpu function cpu_do_idle to do the same thing for the cpu. A typical implementation would be as in the listing below, however in Anakin's case this won't work, because the interrupt controller is in the ASIC, and that is clocked by the processor's mclk. Stopping the CPU stops the ASIC as well, which means that a wake-up interrupt will never get generated, so calling cpu_do_idle just hangs forever. You need to know about this sort of hardware detail to get a successful port.
static inline void arch_idle(void){
/*
* Please check include/asm/proc-fns.h, include/asm/cpu-*.h
* and arch/arm/mm/proc-*.S. In particular, cpu_do_idle is
* a macro expanding into cpu_XXX_do_idle, where XXX is the
* CPU configuration, e.g. arm920, sa110, xs80200, etc.
*/
cpu_do_idle(IDLE_WAIT_SLOW);
}
static inline void arch_reset(char mode){
switch (mode) {
case 's':
cpu_reset(0); /* Software reset (jump to address 0) */
break;
case 'h':
/* TODO: hardware reset */
}
}

Thursday, April 13, 2017

GNU-AS

as [-a[cdhlns][=file]] [-D] [–defsym sym=val] [-I dir] [-o objs] [-R] [-W] [–warn] [target-options]
[-mthumb] [-EB|-EL] [-mapcs-32|-mapcs-26|-mapcs-float|-mapcs-reentrant] [-mthumb-interwork] [-moabi] [-k]
-a[cdhlmns]
-ac
omit false conditionals
-ah
include high-level source
-ad
omit debugging directives
-al
include assembly
-an
omit forms processing
-am
include macro expansions


-as
include symbols



--defsym sym=value
Define the symbol sym to be value before assembling the input file. value must be an integer constant
-I dir
Add directory dir to the search list for .include directives.
-o objfile
Name the object-file output from as objfile
--no-warn/-W
Suppress warning messages.
--fatal-warnings
Treat warnings as errors.
--warn
Don’t suppress warning messages or treat them as errors.
-mcpu=processor[+extension...]
specifies the target processor: arm7tdmi, arm8, strongarm, arm9, arm9tdmi, arm10t, xscale.
-march=architecture[+extension...]
specifies the target architecture: armv1, armv2, armv3, armv4, armv5, armv5te, armv5texp iwmmxt and xscale.
-mfpu=floating-point-format
specifies the floating point format: softfpa, fpe, fpa, fsoftvfp, softvfp+vfp, vfp, vfpxd, arm1020e.
-mapcs [26|32]
specifies that the output generated by the assembler should be marked as supporting the indicated version of the Arm Procedure Calling Standard.

internal preprocessor
removes extra whitespace: leaves one space or tab before the keywords on a line, and turns any other whitespace on the line into a single space. Whitespace is one or more blanks or tabs.
removes all comments, replaced with a single space, or an appropriate number of newlines.
/* comment*/;  line comment character ,‘@’ on the ARM; ‘#’ on the i386
converts character constants into the appropriate numeric values.

symbol
one or more characters chosen from the set of all letters, digits and the three characters ‘_.$’. On most machines, you can also use $ in symbol names.
‘.’ refers to the current address. Expression ‘melvin: .long .’ defines melvin to contain its own address.
statement
ends at a newline (‘\n’) or line separator (‘;’). A statement begins with zero or more labels. If the symbol begins with a dot ‘.’ then the statement is an assembler directive. If a letter the statement is an assembly language instruction.
label
A label is a symbol immediately followed by a colon (:). It represents the current value of the active location counter.
constant
a number, written so that its value is known by inspection(Either ‘#’ or ‘$’ can be used to indicate immediate operands.). Like this:
.byte 74, 0112, 092, 0x4A, 0X4a, ’J, ’\J           # All the same value.
.ascii "Ring the bell\7"                        # A string constant.
.octa 0x123456789abcdef0123456789ABCDEF0   # A bignum.
.float 0f-314159265358979323846264338327\
95028841971.693993751E-40                 # - pi, a flonum.

assembler directives
.abort
stops the assembly immediately
.err
it will print an error message and, it will not generate an object file.
.end
marks the end of the assembly file. It does not process anything in the file past the .end directive.
.print “string”
print string on the standard output during assembly.
.align abs-expr,
abs-expr, abs-expr
Pad location counter to a particular storage boundary.
The first expression is the alignment required.
The second expression gives the fill value to be stored in the padding bytes. If it is omitted, the padding bytes are normally zero.
The third expression , if it is present, it is the maximum number of bytes that should be skipped by this alignment directive.


.data subsection
Indicate the following statements onto the end of the data subsection subsection (absolute expression). If omitted, it defaults to zero.
.byte expressions
.byte expects zero or more expressions, separated by commas. Each expression is assembled into the next byte.
.ascii "string"
expects zero or more string separated by commas. It assembles each string (with no automatic trailing zero byte) into consecutive addresses.
.int expressions
Expect zero or more expressions, of any section, separated by commas.
.long expressions
the same as ‘.int’
.float flonums
expects zero or more flonums, separated by commas.
.double flonums
expects zero or more flonums, separated by commas.


.equ symbol, expr
sets the value of symbol to expression. It is synonymous with ‘.set’.
.global symbol,
.globl symbol
makes the symbol visible to ld. If symbol defined in partial program, its value is made available to other partial programs that are linked with it.
.comm
symbol ,
length
declares a common symbol named symbol. When linking, if ld does not see a definition for the symbol then it will allocate length bytes of uninitialized memory. If ld sees multiple common symbols with the same name, it will allocate space using the largest size.
.func name[,label]
.func emits debugging information to denote function name, and is ignored unless the file is assembled with debugging enabled. The function must be terminated with .endfunc.
.macro
.macro and .endm define macros that generate assembly output.
.include "file"
include supporting files at specified points in your source program.
.org new-lc , fill
Advance location counter of current section to new-lc. new-lc is either an absolute expr or an expr with same section as the current subsection.
.if absolute expr
marks the beginning of a section of code assembled if argument is nonzero. It ends with .endif
.ifeq absolute expr
Assembles the following section of code if the argument is zero.
.ifdef symbol
Assembles the following section of code if symbol has been defined.

ARM Machine Directives
name .req regName
This creates an alias for register name called name.( foo .req r0)
.code [16|32]
.thumb .arm
This directive selects the instruction set being generated. The value 16 selects Thumb, with the value 32 selecting ARM.
nop
evaluate to a legal ARM instruction that does nothing(MOV r0, r0)
ldr <register> , = <expr>
If expr evaluates to a numeric constant then a MOV or MVN instruction will be used in place of the LDR instruction.
If the constant can be generated by either of these instructions. Otherwise the constant will be placed into the nearest literal pool and a PC relative LDR instruction will be generated.
adr <register> <label>
Load the address of label into the indicated register. It will evaluate to a PC relative ADD or SUB instruction depending upon where the label is located.
adrl <register> <label>
Load the address of label into the indicated register. It will evaluate to one or two PC relative ADD or SUB instructions depending upon where the label is located. If a second instruction is not needed a NOP will be in its place, so that this instruction is always 8 bytes long.



section
A section is a range of addresses, with no gaps; all data “in” those addresses is treated the same for some particular purpose.
The linker LD reads many object files (partial programs) and combines their contents to form a runnable program.
When AS emits an object file, the partial program is assumed to start at address 0. LD assigns the final addresses for the partial program, so that different partial programs do not overlap. LD moves blocks of bytes of the program to their run-time addresses. These blocks slide to their run-time addresses as rigid units; their length does not change and neither does the order of bytes within them. Such a rigid unit is called a section. Assigning runtime addresses to sections is called relocation. It includes the task of adjusting mentions of object-file addresses so they refer to the proper run-time addresses.
An object file written by AS has at least three sections, any of which may be empty. These are named text, data and bss sections. When it generates COFF or ELF output, AS can also generate whatever other named sections you specify using the ‘.section’ directive. Within the object file, the text section starts at address 0, the data section follows, and the bss section follows the data section.
Any address whose section is unknown at assembly time is by definition rendered {undefined U}—where U is filled in later. The only way to generate an undefined address is to mention an undefined symbol.
relocation
• Where in the object file is the beginning of this reference to an address?
• How long (in bytes) is this reference?
• Which section does the address refer to? What is the numeric value of (address) − (start-address of section)?
• Is the reference to an address “Program-Counter relative”? In fact, every address as ever uses is expressed as (section) + (offset into section)
It use the notation {secname N} to mean “offset N into section secname.”
Addresses in the absolute section remain unchanged. address {absolute 0} is “relocated” to run-time address 0 by ld.
bss section
Contains zeroed bytes when your program begins running. It is used to hold uninitialized variables or common storage.


Sub-Sections
Assembled bytes conventionally fall into two sections: text and data. You may have separate groups of data in named sections that you want to end up near to each other in the object file, even though they are not contiguous in the assembler source. AS allows you to use subsections for this purpose. Within each section, there can be numbered subsections with values from 0 to 8192. Objects assembled into the same subsection go into the object file together with other objects in the same subsection. For example, a compiler might want to store constants in the text section, but might not want to have them interspersed with the program being assembled. In this case, the compiler could issue a ‘.text 0’ before each section of code being output, and a ‘.text 1’ before each group of constants being output.
bss Section

The bss section is used for local common variable storage. You may allocate address space in the bss section, but you may not dictate data to load into it before your program executes. When your program starts running, all the contents of the bss section are zeroed bytes.