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 */
}
}
|
No comments:
Post a Comment