Linux MIPS Porting Guide
This document reflects what I have learned through porting several MIPS machines and other related Linux work.
Hopefully it will help beginners to get started and give the experienced a reference point.
This document goes through all the major steps
to port Linux to a MIPS machine. The focus can perhaps be called "MIPS
machine abstraction layer", i.e., the interface between
machine-specific code and, mostly, MIPS common code. Another useful
document focuses on "Linux hardware abstraction layer", i.e., the
interface between Linux common code and architecture-specific code. The document is written by C Hanish Menon (www.hanishkvc.com).
There are some notations used in this document.
- TODO
- Reminder for incomplete part
- HELP
- Really need your help on this
- DEBATE
- Here is my opinion. What do you think?
- THANKS
- Thanks to the person who pointed this out
Prerequisites:
It is also highly recommanded to read through the Linux MIPS HOWTO by Ralf Bächle, ralf@gnu.org. By the way, as part of the pre-requisite, you should also remember Ralf's name. :-)
Kernel source trees
The common MIPS tree is the CVS tree at linux-mips.org. See the
instructions in "Anonymous CVS servers" section in Linux/MIPS HOWTO.
The current kernel version as of 2004/01/26 is 2.6.1. You can always
check out earlier stable revisions by using "linux_2_4" or even
"linux_2_2" branch tag.
Kernel patches
For various reasons, a kernel tree may leave bugs there for a quite
long time before a suitable fix is checked in. There are various places
to get patches. Here are some of the more common ones:
Jun Sun patches
Linux/MIPS FTP archive
Maciej W. Rozyki patches
Brad LaRonde's patches
Cross-compilation and toolchains
More than likely your MIPS box does not run Linux yet (why would you bother otherwise?).
Therefore you will need another machine to build the kernel image.
Once the image is built, you need to download this image to your MIPS machine and let it run your MIPS kernel.
This is called cross-development.
Your MIPS box is often called the target machine and the machines used to build the kernel image is called the host machine.
Cross-development is common for developing on embedded targets,
because usually embedded targets do not have enough power or the
peripherals to develop natively.
The most common host machine is probably Linux on i386/PCs.
You need to have cross-development tools setup on your host before
you can start. While you can find instructions, such as in Linux/MIPS
HOWTO, to build cross-compilation tools, your best bet is probably to
get some ready-made ones.
MontaVista used to offer free Journeyman edition, which includes a
full featured toolchain. Unfortunately, it does not offer that anymore.
Instead you can download the preview kit, which includes a "slim"
version of toolchain. You can get the kit from http://www.mvista.com/previewkit/index.html
The following are links to pre-build toolchains, instructions
to build your own toolchain and finally pre-compiled distributions for
MIPS boards:
Brad LaRonde's cross toolchain for Linux
Steve Hill's toolchains for glibc and uClibc
MIPS free toolchains
Distributions for MIPS
Overall porting steps :
Depending on your specific cases, some of the following steps can be skipped.
- Hello World! - Get board setup, serial porting working, and print out "Hello, world!" through the serial port.
- Add your own code.
- Get early printk working - Make the first MIPS image and see the printk output from kernel.
- Serial driver and serial console - Get the real printk working with the serial console.
- KGDB - KGDB can be enormously helpful in your development. It is highly recommended and it is not that difficult to set up.
- CPU support - If your MIPS CPU is not currently supported, you need to add new code that supports it.
- Board specific support - Create your board-specific directory. Setup interrupt routing/handling and kernel timer services.
- PCI subsystems - If your machine has PCI, you need to get the PCI subsystem working before you can use PCI devices.
- Ethernet drivers - You should already have the serial port
working before attempting this. Usually the next driver you want is the
ethernet driver. With ethernet driver working, you can set up a NFS
root file system which gives you a fully working Linux userland.
- ROMFS root file system - Alternatively you can create a userland file system as a ROMFS image stored in a ramdisk.
In cross development, the serial port is usually the most important interface:
That is where you can see anything happening!
It might be worthwhile to make sure you get serial porting work before you even start playing with Linux.
You can find the sample code or gzipped tar ball
of a stand-alone program that can do printf. Such a program can even be
useful in later debugging staging, e.g., printing out hardware register
values.
Before you rush to type 'make', check and modify the following configurations:
- The sample code assumes R4K style CP0 structure. It should apply
to most CPUs named above number 4000 and the recent MIPS32/MIPS64.
- Check if you have 1MB RAM size. (You really should have at
1MB to run Linux at all.) It is recommanded you have 8MB RAM or more.
- Is your serial port standard UART type? If yes, modify the
serial code and parameters. If not, you will have to supply your own a
couple of functions to make
- What is your cross-tool name and path? Modify the Makefile accordingly.
Now, fire your "make" command.
Depending on your downloader on your MIPS box, you may need to generate ELF image, binary image or a SREC image.
Download the barebone image to your target and give it a run! Connect
the serial port to your host machine. Start minicom and hopefully you
can see the "Hello, world!" message.
Trouble shootings:
- Make sure your download downloads the image to uncached KSEG1
segment. If your downloader downloads to cached area, you may want to
run the image in cached KSEG0 area too.
- If your downloader has already initialized the serial port, you may want to skip your own initialization.
- Did you set up minicom correctly? Test it with other machines.
- Hopefully it is not the cross-compiling problem.
Let us add some code to the tree and make a
Linux image. For conveninence sake, let us say we are porting Linux to
a MIPS board called Aloha.
Your code for a new board can be classified into board-support code (or board-specific code) and drivers.
Driver code should be placed under drivers/ directory and board specific code should be placed under arch/mips directory.
The easiest choice is to create a directory called arch/mips/aloha.
However, a couple of other considerations might make it slightly complicated.
In the past people have created directories
based on the board manufacturer's name, such as "mips-boards". This
generally is not a good idea. It is almost certain that some of these
boards do not share anything common at all. Simply grouping together is
a like forced marriage.
To make things worse, sometimes boards made
by different companies use the same chipset or SOC. Now what are you
going to do? Are you going to duplicate the common code? Or are you
going stick one company's board under another company's name?
For header files, you usually create
similar directory or header files under include/asm-mips. [DEBATE] For
board specific header files, I would encourage people to place them
under the corresponding arch/mips directories if possible.
In our exmaple, we will create arch/mips/aloha directory.
Let us write some code for Aloha board
which can generate a complete Linux image without getting complained
about missing symbols.
Go to this directory to browse arch/mips/aloha directory. Or download the gzipped file of the directory.
Obviously the code is not complete yet, but if you follow the
following steps and everything is correct, you should be able to
generate a Linux/MIPS kernel image of your very own!
Hook up your code with the Linux tree
Most of the steps are fairly straightforward:
- include/asm-mip/bootinfo.h - Add your machine group ID, machine group name and Aloha machine ID.
- arch/mips/kernel/setup.c - Add 'aloha_setup' function declaration and invocation code.
- arch/mips/Makefile - Add a section that links your Aloha code in.
#
# Hawaii Aloha board
#
ifdef CONFIG_ALOHA
SUBDIRS += arch/mips/aloha
LIBS += arch/mips/aloha/aloha.o
LOADADDR += 0x80002000
endif
LOADADDR is the starting address for your linux image when it is loaded into RAM.
Note that the first 0x200 bytes are used by the exception vectors on most CPUs.
Some CPUs take bigger space.
Make sure LOADADDR is higher than that space.
Due to the linking limit, the starting address is aligned along 8K boundary.
So setting your LOADADDR to 0x80002000 seems to be reasonable.
- arch/mips/config.in - Add necessary config information for Aloha board.
- Add the following to 'Machine selection'.
dep_bool 'Support for Hawaii Aloha board (EXPERIMENTAL)' CONFIG_ALOHA $CONFIG_EXPERIMENTAL
- Add a set of default configs for the board, which depends on the features and drivers that Linux port will supports.
Here is a very simple example for our minimum Aloha board configurations.
if [ "$CONFIG_ALOHA" = "y" ]; then
define_bool CONFIG_CPU_R4X00 y
define_bool CONFIG_CPU_LITTLE_ENDIAN y
define_bool CONFIG_SERIAL y
define_bool CONFIG_SERIAL_MANY_PORTS y
define_bool CONFIG_NEW_IRQ y
define_bool CONFIG_NEW_TIME_C y
define_bool CONFIG_SCSI n
fi
There are two kinds of config options here.
The first kind are those you can not select interactively during 'make config' or 'make menuconfig' or 'make xconfig'.
Examples are CONFIG_NEW_IRQ and CONFIG_NEW_TIME_C.
You must put them here, or else they won't get selected or defined.
The second kind of macros are those you can select them interactively, such as CONFIG_CPU_R4X00 and CONFIG_SERIAL.
However you may also put them here if you know which selection is right for the board.
This way people will make fewer mistakes when they configure for the board.
For instant gratification, you can find a complete patch for adding the Aloha board support to OSS CVS tree date on Sept 28, 2001.
Configure and build a kernel image
Now you are ready to run your favorite configuration tool. Since we
don't have much code added yet, don't be too greedy. Just pick a couple
of simple options such as serial and serial console.
- If you denote the Aloha board support to be EXPERIMENTAL, select
'Prompt for development and/or incomplete code/drivers' under 'Code
maturity level options'.
- Select Aloha board and unselect all other machines.
- Select the right CPU. If there is no exact CPU that matches
your board, you will need to add support to the new CPU. Most recent
CPUs can generally run to some degree with CPU_R4X00.
- Under 'Character devices', select 'Standard/generic
(8250/16550 and compatible UARTs) serial support' and 'Support for
console on serial port (NEW)'. Unselect virtual terminal option.
- Under "Kernel hacking" option, select 'Are you using a crosscompiler'.
- For other options either take the default or select 'no'.
Here is a sample minimum config for our Aloha board.
Before you type 'make', double-check 'arch/mips/Makefile' and make
sure the cross-toolchain programs are correct and in your execution
path i.e. your PATH environment variable.
Now type 'make dep' and 'make'. Then wait for miracles to happen!
Assuming you are lucky and actually
generate an image from the last chapter, don't bother running it
because you won't see anything. This is not strange because all our
board-specific code is empty and we have not told Linux kernel anything
about our serial port or any IO devices yet.
The sign of a live Linux kernel comes
from the output of printk, which is routed to the first console. Since
we have configured a serial console, we should be able to see something
on the serial wire if we have set it correctly.
Unfortunately setting up serial console happens much later during the kernel startup process.
(See Appendix A for a chart of the kernel start-up sequence).
Chances are your new kernel probably dies even before that.
That is where the early printk patch comes to be handy.
It allows you to see printk as early as the first line of C code.
By the way the first line of C code for Linux MIPS is the first line of code of init_arch() function in arch/mips/setup.c file.
For kernel version earlier than 2.4.10, you can find the early printk patch here for boards with standard UART serial ports. Starting from 2.4.10 and beyond, a new printk patch
is needed. If you have already got the standard alone "hello, world!"
program running, the early printk should be up running easily, and you
should the printk output from Linux very soon.
While early printk is rather useful, you still need to get the real serial driver working.
Assuming you have the standard serial port, there are two styles to add serial support: static defines and run-time setup.
With static defines, you modify
include/asm/serial.h file. Take a look of it, and it is not difficult
to figure out how you should add support for your board.
When more boards are added to Linux/MIPS, the
serial.h is necessarily getting crowded. One potential solution is to
do the run-time serial setup. Sometimes run-time serial setup is
necessary if any of the parameters can only be detected at the
run-time.
There are two elements in run-time setup:
Serial parameters
Most of the parameter settings are rather obvious. Here is a list of some less obvious ones:
- line
- Only used in run-time serial setting. It is the index in the rs_table[] array.
- io_type
- io_type determines how your serial registers are accessed. Two common types are SERIAL_IO_PORT and SERIAL_IO_MEM. For SERIAL_IO_PORT driver uses inb/outb macros to access registers whose base address is at port. In other words, if you specify SERIAL_IO_PORT as the io_type, you should also specify port parameter.
For SERIAL_IO_MEM, the driver uses readb/writeb macros to access regsiters whose address is at iomem_base
plus shifted offset. The number of digits shifted is specified by
iomem_reg_shift. For example, all the serial registers are placed at
4-byte boundary, then you have iomem_reg_shift of 2.
Generally SERIAL_IO_PORT is for serial ports on ISA bus and SERIAL_IO_MEM is for memory-mapped serial ports.
There are also SERIAL_IO_HUB6 and SERIAL_IO_GSC. [HELP:what are they for?]
Non-standard serial ports
If you have a non-standard serial port, you will have to write your own serial driver.
Some people derived their code from the standard serial driver.
Unfortunately this is a very daunting task: serial.c file has over 6000 lines of code!
Fortunately there is an alternative [THANKS: Fillod Stephane].
There is a generic serial driver, drivers/char/generic_serial.c. This
file provides a set of serial routines that are hardware independent.
Then your task is to provide only the routines that are hardware
dependent such as interrupt handler routine.
There are plenty of examples. Just look inside the
drivers/char/Makefile and look for drivers that link with
generic_serial.o file.
[HELP: would appreciate if you can share your experience of
writing proprietary serial driver code or using the generic_serial.c
file.]
For many Linux kernel developers, kgdb is a life-saving tool. With kgdb, you
can debug the kernel while it runs! You can set breakpoints or do single stepping at source code level.
To do this, you will need a dedicated serial
port on your target, and use
a cross-cable to connect it to your development host. If you are also
using serial console, this implies you will need two serial ports on
your target. It is possible to do both kernel debug and serial console
through a single serial port. This will be mentioned later in this
chapter.
When you configure the kernel, turn on CONFIG_REMOTE_DEBUG option, which is listed under "kernel hacking".
Try to make a new image. You will soon discover two missing symbols in the final linking stage:
int putDebugChar(uint8 byte)
uint8 getDebugChar(void)
You need to supply those two functions for your own boards. As an example, here is the dbg_io.c for DDB5476 board. DDB5476 uses a standard UART serial port to implement those two functions.
After supplying those two functions, you are ready to debug kernel.
Run the new kernel image. If you also use the early printk patch, you
should be able to see something like this on your console:
Wait for gdb client connection ...
Assuming you have already connected a cross-over serial cable
between the dedicated serial port on the target and a serial port on
your host (say, com0), you can then set the appropriate baud rate and
fire the cross gdb on your host:
stty -F /dev/ttyS0 38400
mips_fp_le-gdb vmlinux
At the gdb prompt, type
target remote /dev/ttyS0
And, whola, you should be talking to the kernel through kgdb - if you are lucky enough!
A couple of tips on using kgdb:
- Any functions labelled __init does not break very well on
breakpoints. Sometimes it also screw up the line numbers of other
functions in the same file. I usually redefine __init to nothing in
include/linux/init.h file. Refer to the patch. [HELP: Anybody wants to investigate why this is the case?]
- kgdb will try to dereferencing any 'char *' variables. This
can be a problem especially at the beginning of a function where the
function argument may have *not* been assigned properly. To work around
this, set a breakpoint a little later in the function rather right at
the entrance of the function. [HELP: I think there is some work to get
around this problem. Please update me if you find the problem is
already gone.]
- Sometimes if you break on a function, you cannot see the
correct value in variables and cannot do back-tracing. This is probably
because certain registers are still not initialized [HELP: because
kernel is compiled with -O2 flag?]. Step into the function a couple of
lines, and you should see the variable and back-tracing fine.
What if the board only has one serial port?
Some boards only have one serial port. If you use it as serial
console, you cannot really use it for kgdb - unless you do some tricks
to it.
There are two solutions. One is kgdb console, and the other is to use kgdb demuxing script.
It is easy to use kgdb console. When you select "Remote GDB kernel
debugging" under "Kernel hacking" sub-menu, you are also prompted for
"Console output to GDB". Simply selecting that choice will work! In
fact, this option is so easy to use you might to use it even if you
have a second serial port.
However, this option has a limit. When the kernel goes to userland,
the console stopped working. [HELP: please confirm this] I suspect the
reason is that the kgdb console driver is not fully developed to handle
other requestions such as interrupts and etc.. [HELP: any volunteers?]
The second option uses a script called kgdb_demux written by Brian
Moyle. It creates two virtual ports, typically ttya0 and ttya1. It then
listens to the real serial port (such as ttyS0). It will forward
console traffic to ttya0 and kgdb traffic to ttya1. All you have to do
then is to start minicom on /dev/ttya0 (port setting does not matter)
and kgdb on /dev/ttya1.
You can download the tarball here. A couple of usage tips.
- Untar the file to some place.
- Copy kgdb_demux script to your execution path, and modify it properly.
- Set the port parameters properly before you start kgdb_demux.
[TODO]
It you followed the previous steps,
most likely you will see kernel hangs at the BogusMIPS calibration
step. The reason is simple: The interrupt code is not there and jiffies
are never updated. Therefore the calibration can never be done.
Before you start writing interrupt code, it
really pays to study the hardware first. Pay particular attention to
identify all interrupt sources, their corresponding controllers and
their routings.
Then you need to come up with a strategy, which typically includes:
To complete an interrupt service, four different parts of code work together.
- IRQ detection/dispatching
- This is typically an assembly
code in a file called int_handler.S. Some times there are also a
secondary level dispatching code written in C for complicated IRQ
detections. The end result is that we identify and select a single IRQ
source, represented by an integer, and then pass it to function
do_IRQ().
- do_IRQ()
- do_IRQ() is provided in
arch/mips/kernel/irq.c file. It provides a common framework of IRQ
handling. It invokes individual IRQ controller code to enable/disable a
particular interrupt. It calls the driver supplied interrupt handling
routine that does the real processing.
- hw_irq_handler
- It is a structure associated
with each IRQ source. The structure is a collection of function
pointers, which tells do_IRQ() how it should deal with this particular
IRQ.
- driver interrupt handling code
- The code that does the real job.
Obviously for our porting purpose we need
to write IRQ detection/disptaching code and the hw_irq_handler code for
any new IRQ controller used in the system. In addition, there is also
IRQ setup and initialization code.
Most R4K-compatible MIPS CPUs have the
ability to support eight interrupt sources. The first two are software
interrupts, and the last one is typically used for CPU counter
interrupt. Therefore you can have up to five external interrupt
sources.
Luckily if the CPU is the only IRQ
controller in the system, you are done! The hw_irq_handler code is
written in arch/mips/kernel/irq_cpu.c file. All you have to do is to
define "CONFIG_IRQ_CPU" for your machine. And in your irq setup routine
make sure you call mips_cpu_irq_init(0). This call will initialize the interrupt descriptor and set up the IRQ numbers to range from 0 to 7.
You can also find a matching interrupt dispatching code in this file.
Set up cascading interrupts
More than likely you will have more interrupt sources than those
directly connected to CPU IP pins. A secondary, or even a third-level
interrupt controller may connect to one or more of those CPU IP pins.
In that case, you have cascading interrupts.
There are plenty of examples of how cascading interrupt works, such as ddb5477, korva, and osprey. Here is a short of summary:
- Assign blocks of IRQ numbers to various interrupt controllers in
the whole system. For example, in case of osprey, CPU interrupts occupy
the IRQ 0 to 7. Vr4181 system interrupts occupy IRQ 8 to 39, and GPIO
interrupts occupy 40 to 56. In most cases, the actual IRQ numbers do
not matter, as long as the driver knows which IRQ number it should use.
However, if you have i8259 interrupt controller and a ISA bus, you
should try to assign IRQ number 0 to 16 to i8259 interrupts, because it
will make the old PC drivers happy. (Please note before ~12/08/2001,
linux-2.4.16, i8259.c file sets the base vector to be 0x20. If you use
IRQ acknowledgement cycle to obtain interrupt vector, you will get IRQ
number from 0x20 to 0x2f. You will then need to substract 0x20 from the
return value to get the correct IRQ number.)
- Write the hw_irq_controller member functinos for your
specific controllers. Note that CPU and i8259 already have their code
written. You just need to define appropriate CONFIG options for your
board. See next sub-section for more details about writing
hw_irq_controller member functions.
- In your IRQ setup routine, initialize all the controllers, usually by calling <interrupt_controller>_init() function.
- In your IRQ setup routine, setup the cascading IRQ. This
setup will enable interrupt at the upper interrupt controller so that
the lower-level interrupts can go through once they are enabled. A
typical way of doing this is to have a dummy irqaction struct and setup
as follows:
static struct irqaction cascade =
{ no_action, SA_INTERRUPT, 0, "cascade", NULL, NULL };
extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
void __init <my>_irq_init(void)
{
....
setup_irq(CPU_IP3, &cascade);
}
- You need to expand your interrupt dispatching code properly
to identify the added interrupt sources. If the code is simple enough,
you can do it in the same int_handler.S file (such as Korva). If it is
more complicated, you may do it in a separate C function (such as
DDB5476 board).
The hw_irq_controller struct
The hw_irq_controller structure is a defined in include/linux/irq.h file as alias for hw_interrupt_type structure.
struct hw_interrupt_type {
const char * typename;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*end)(unsigned int irq);
void (*set_affinity)(unsigned int irq, unsigned long mask);
};
The irq_cpu.c is a good sample code to write hw_irq_controller
member functions. Here are some more programming notes for each of
these functions.
- const char * typename;
- Controller name. Will be displayed under /proc/interrupts.
- unsigned int (*startup)(unsigned int irq);
- Invoked when request_irq() or setup_irq are
called. You need to enable this interrupt here. Other than that you may
also want to do some IRQ-specific initialization (such as turning on
power for this interrupt, perhaps).
- void (*shutdown)(unsigned int irq);
- Invoked when free_irq() is called. You need to disable this interrupt and perhaps some other IRQ-specific cleanup.
- void (*enable)(unsigned int irq) and void (*disable)(unsigned int irq)
- They are used to implement enable_irq(), disable_irq() and disable_irq_nosync(), which in turn are used by driver code.
- void (*ack)(unsigned int irq)
- ack() is invoked at the beginning of do_IRQ() when we want to
acknoledge an interrupt. I think you need also to disable this
interrupt here so that you don't get recursive interrupts on the same
interrupt source. [HELP: can someone confirm?]
- void (*end)(unsigned int irq)
- This is called by do_IRQ() after it has handled this
interrupt. If you disabled interrupt in ack() function, you should
enable it here. [HELP: generally what else we should do here?]
- void (*set_affinity)(unsigned int irq, unsigned long mask)
- This is used in SMP machine to set up interrupt handling affinity with certain CPUs. [TODO] [HELP]
The IRQ initialization code
The IRQ initialization is init_IRQ(). Currently it is supplied by
each individual board. In the future, it will probably be a MIPS common
routine, which will further invoke a board-specific function,
board_irq_init(). board_irq_init will be a function pointer that
<my_board>_setup() function needs to assign propoer value.
In any case, the following is a skeleton code for a normal init_IRQ() routine.
extern asmlinkage void vr4181_handle_irq(void);
extern void breakpoint(void);
extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
extern void mips_cpu_irq_init(u32 irq_base);
extern void init_generic_irq(void);
static struct irqaction cascade =
{ no_action, SA_INTERRUPT, 0, "cascade", NULL, NULL };
static struct irqaction reserved =
{ no_action, SA_INTERRUPT, 0, "reserved", NULL, NULL };
static struct irqaction error_irq =
{ error_action, SA_INTERRUPT, 0, "error", NULL, NULL };
void __init init_IRQ(void)
{
int i;
extern irq_desc_t irq_desc[];
/* hardware initialization code */
....
/* this is the routine defined in int_handler.S file */
set_except_vector(0, my_irq_dispatcher);
/* setup the default irq descriptor */
init_generic_irq();
/* init all interrupt controllers */
mips_cpu_irq_init(CPU_IRQ_BASE);
....
/* set up cascading IRQ */
setup_irq(CPU_IRQ_IP3, &cascade);
/* set up reserved IRQ so that others can not mistakingly request
* it later.
*/
setup_irq(CPU_IRQ_IP4, &reserved);
#ifdef CONFIG_DEBUG
/* setup debug IRQ so that if that interrupt happens, we can
* capture it.
*/
setup_irq(CPU_IRQ_IP4, &error_irq);
#endif
#ifdef CONFIG_REMOTE_DEBUG
printk("Setting debug traps - please connect the remote debugger.\n");
set_debug_traps();
breakpoint();
#endif
}
Final notes
What is described in this chapter is what is now called new style
interrupt handling. This is probably the only valid way to write
interrupt handling code in the future. You need to define
CONFIG_NEW_IRQ for your board to work this way.
A few board uses arch/mips/kernel/old-irq.c file, where they have
CONFIG_ROTTEN_IRQ option defined. old-irq.c is very PC-centric, and
grossly out-dated. Don't either bother yourself of looking at it.
Many other boards don't have either IRQ config option defined. They
use their own IRQ routines. In the future, they should converge to the
new style IRQ routines.
Linux relies on an RTC device to obtain the real
calendar data and time when it boots up. It relies on a system timer to
advance a tick count, jiffies. If you don't provide proper
time and timer code, Linux won't run. In fact it will stuck in
calibrate_delay() during the startup process because jiffies is never
incremented. (See Appendix B for more details about Linux/MIPS startup
sequence)
There is an excellent document (I becomes a
little shameless. :-0) under Linux/Documentations/mips/time.README. It
is a must read.
Here are some comments on implementing time and timer services
PCI subsystem is perhaps the most complex code
you have to deal with during the porting process. The key of making PCI
subsystem working is a good understanding of PCI bus itself and the
code layout and execution flow in Linux. Like many other part of
porting, you will see in the end your actual code writing is really
minimum.
Pete Popov wrote a fine PCI README file for Linux
MIPS. It is under linux/Documentation/mips/pci/pci.README. It is highly
recommanded.
For those who want to know more about PCI bus itself, I recommand the book, PCI System Architecture published by MindShare Inc..
We summarize some facts of the PCI bus.
On all IBM PC-compatible machines, BAR's are assigned by BIOS. Linux simply scans through the bus and records the BAR values.
Some MIPS boards adopt similar approaches, where
BAR's are assigned by firmware. However, the quality of BAR assignment
of the firmwares vary quite a bit.
Some firmware simply assigns BAR's to onboard PCI devices and ignores
all add-on PCI cards.
In that case, Linux cannot solely rely on firmware's assignment.
There is another danger of depending on the
firmware assignment: you need to stick with address range setup by
firmware. In other words, if firmware assigns PCI memory space from
0x10000000 to 0x14000000, you really cannot move it to some place else
in Linux.
There about three ways to fix this in Linux.
The first way is to fix the BAR
assignment mannually in your board setup routine.
This only works if your board does not have a PCI slot to plug in an
arbitrary PCI card. You need to carefully examine the existing PCI
resource assignemnt done by firmware so that you don't assign the
overlapping address ranges.
The second way is to do a completely PCI resource
assignment *before* Linux starts PCI bus scanning. In other words, we
discard any PCI resource assignment done firmware, if there is any, and
do a new assignment by ourself. This approach gives us a complete
control over the address range and resource allocation. With the
CONFIG_PCI_AUTO option and arch/mips/kernel/pci_auto.c file, it turns
out quite easy to do so. This appraoch is the focus of this chapter.
Another approach is to call
pci_assign_unassigned_resources() function, which is defined in
drivers/pci/setup-bus.c file in recent 2.4.x kernels *after* Linux
completes the PCI bus scan.
With earlier versions of this function, Linux will assign resources to
PCI devices whose BAR's have *not* been properly assigned.
With the recent versions (that have "optimal" resource assignment based
on sizes), this PCI routine apparently does a complete resource
re-assignment. In other words, it does almost exactly the same as what
pci_auto.c file does.
[HELP: Can someone confirm this?]
[DEBATE] Each of pci_auto approach
and assign_unassigned_resource apprach has its own advantage and
disadvantages. Ideally, the whole PCI subsystem should be completely
re-written so that several things can be taken into consideration which
are not right now:
- do_basic_setup() calls pci_init(), which is defined in drivers/pci/pci.c
- pci_init() first calls
pcibios_init(). If you enable CONFIG_NEW_PCI, pcibios_init() is
implemented in arch/mips/kernel/pci.c file. Otherwise you need to
provide it in your own board-dependent code.
- Optionally, pcibios_init() may call pciauto_assign_resources() to do a complete PCI resource assignment.
- somewhere inside pcibios_init(),
pci_scan_bus() is called. If a machine has multiple host-PCI
controllers, pci_scan_bus() should be called for each of the top-level
PCI buses. Apparently, bus numbers should have already been setup
before pci_scan_bus() can properly run through.
- Optionally, after pci_scan_bus()
is called, pcibios_init() may choose to call
pci_assign_unassigned_resources() to do a complete PCI resource
assignment.
- pcibios_init() will do some more fixups (resources, irqs, etc)
- returning from pcibios_init(), pci_init() will do a final round of device-based fixup.
In this chapter, we will focus on the approach where both CONFIG_NEW_PCI and CONFIG_PCI_AUTO are enabled.
All the fuss about PCI is to eventually setup a
strcuture where all PCI device drivers can run happily. Knowing how PCI
device drivers access PCI resources can greatly help you understand how
you should do PCI initialization and setup.
As we can see from the above discussion, we
need to set up the host-PCI controller such that 1) it has 1:1 mapping
between PCI memory space and CPU physical address space and 2) it maps
beginning part of PCI IO space into a address block in physical address
space.
Host-PCI controller usually allows
you to map PCI memory space into a window in physical address space.
Let us say the base address is ba_mem and size is sz_mem. It usually allows you to translate that address into another one with a fixed offset, say off. (Note off can be both positive or negative). So if driver access the address ba_mem+x (0 <= x < sz_mem), host-PCI controller will intercept the command and translate into a PCI memory access at address ba_mem+off+x.
To maintain 1:1 mapping, it implies we must set up such that off is 0. Also note that with this setup, we cannot access the PCI memory range [0x0, ba_mem] and [ba_mem + sz_mem, 0xffffffff].
Additionaly, we must also make system
RAM visiable on PCI memory bus at address 0x0 (assuming that is the
address in physical address space) in order for PCI devices to do DMA
transfer.
The beginning part of PCI IO space is usually mapped into another window in physical address space, say [ba_io, ba_io + sz_io]. In other words, a range [0, sz_io] in PCI IO space corresponds to the range [ba_io, ba_io + sz_io]. Obviously, mips_io_port_base should be set to ba_io.
The above setup is typically done in the board-specific setup routine (i.e., <board>_setup()). You typically also setup ioport_resource and iomem_resource as well:
ioport_resource.start = 0x0;
ioport_resource.end = sz_io;
iomem_resource.start = 0x0;
iomem_resource.end = sz_mem;
These variables are the roots of resources. For simplicity you can also set the end to be 0xffffffff.
Here is a list of board-specific functions you
must implement. Again, I assume this board has CONFIG_NEW_PCI and
CONFIG_PCI_AUTO.
- struct pci_ops my_pci_ops
You
implement six functions to fill into this structure, which is needed by
pci_scan_bus() and pciauto_assign_resources(). Note that you need to
dintinguish type 0 or type 1 configuration in those functions. You can
typically check that by checking whether the bus's parent (dev->bus->parent) is NULL.
- mips_pci_channels[]
You need to define this array in order to use pciauto resource
assignment. For each top-level PCI bus, you need to supply an element
data to this array. The array ends with an all-NULL element. Each
element is a structure that consists a pci_ops, pci_io_resource, and
pci_mem_resource, which usually represents a top-level PCI bus
connected to CPU. pci_ops defines the functions access the PCI bus's
config space. pci_io_resource and pci_mem_resource specifies the
address range that pciauto will use to assign to the BARs of PCI
devices. For pci_io_resource, it starts with 0x0 (or 0x1000 to leave
some room for legacy ISA devices), and it ends at sz_io. For pci_mem_resource, it starts at ba_mem and ends at ba_mem + sz_mem.
Note these addresses are in PCI IO and PCI memory space respectively.
However, since we maintain 1:1 mapping between PCI memory space and CPU
physical address space, pci_mem_resource also represents the PCI memory
space window in CPU physical address space.
- pcibios_fixup_resources()
This
routine is passed to pci_scan_bus() and invoked right after when the
PCI device is discovered. This is place where you can do some
device-specific fixup (BARs, pci_dev structure, etc). Note you can do
the same fixup in pcibios_fixup(). I recommand leave this function
empty unless you have some specific need that requires immediate fixup.
- pcibios_fixup()
This
function is invoked after pci_scan_bus() is done, i.e., all PCI bridges
and devices are discovered by Linux. Here you can enumerate through PCI
devices and do device based fixup. Or you can do bus or contoller
related fixups.
- pcibios_fixup_irqs()
This
is the place to fix up PCI related IRQs. It is invoked after
pci_scan_bus() is done and next to pcibios_fixup(). A typical strategy
is to assign irq based on the slot number and possibly bus number if
there are more than top-level buses in the system. Note that you can do
the fixup in pcibios_fixup() as well and leave this function empty.
- pcibios_assign_all_busses()
Return
1 to indicate that all bus numbers have been assigned by pciauto.
[TODO:Should this function be included in pciauto by default?][TODO:
the driver/pci/pci.c file seems to have a typo when it calls this
function. The logic is inversed.]
[TODO]
[TODO]
Although it is the last chapter, it is
probably most read one. In this chapter, I will list some commonly used
debugging tips and tricks.
ksymoops is a program that
deciphers all the secret numbers in call stack and trace. However, as
far as I know there is no working version today for MIPS. [HELP:
Correct me if I am wrong.]
I normally use a script, call2sym, written by Phil Hollenback. You just cut and past the call trace part to feed the script and it will display a possible
function call stack at the time crash. Note it is likely some symbols
are bogus, but the real ones should be displayed. You will have to use
your own judgement.
kernel_entry() - arch/mips/kernel/head.S
set stack;
prepare argc/argp/envp;
jal init_arch - arch/mips/kernel/setup.c
cpu_probe() -
prom_init(...) - arch/mips/ddb5074/prom.c
loadmmu()
start_kernel() - init/main.c
setup_arch(&commaind_line); - arch/mips/kernel/setup.c
ddb_setup() - arch/mips/ddb5074/setup.c
parse_options(command_line);
trap_init();
init_IRQ(); - arch/mips/kernel/irq.c
sched_init();
time_init();
if (board_time_init) board_time_init();
set xtime by calling rtc_get_time();
pick appropriate do_gettimeoffset()
board_timer_setup(&timer_irqaction);
softirq_init();
console_init();
init_modules();
kmem_cache_init();
sti(); /* interrupt first open */
calibrate_delay();
mem_init();
kmeme_cache_sizes_init();
fork_init();
filescahe_init();
dcache_init();
vma_init();
buffer_init();
page_cache_init()
page_cache_init();
kiobuf_setup();
signals_init();
bdev_init();
inode_init();
file_table_init();
ipc_init();
check_bugs();
smp_init();
kernel_thread(init, ...)
cpu_idle();
init() - init/main.c
- lock_kernel();
do_basic_setup();
- [MTRR] mtrr_init();
[SYSCTL] sysctl_init();
[PCI] pci_init();
[SBUS] sbus_init();
[PPC] ppc_init();
[MCA] mca_init();
[ARCH_ACORN] ecard_init();
[ZORRO] zorro_init();
[DIO] dio_init();
[MAC] nubus_init();
[ISAPNP] isapnp_init();
[TC] tc_init();
sock_init();
[BLK_DEV_INITRD] <set up variables>
do_initcalls();
filesystem_setup();
[CONFIG_IRDA] irda_device_init();
[PCMCIA] init_pcmcia_ds();
[HOTPLUG] net_notifier_init();
mount_root();
mount_devfs_fs ();
free_initmem();
unlock_kernel();
if (open("/dev/console", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console.\n");
(void) dup(0);
(void) dup(0);
if (execute_command)
execve(execute_command,argv_init,envp_init);
execve("/sbin/init",argv_init,envp_init);
execve("/etc/init",argv_init,envp_init);
execve("/bin/init",argv_init,envp_init);
execve("/bin/sh",argv_init,envp_init);
panic("No init found. Try passing init= option to kernel.");
People in the following list have generously given
their feedbacks to me. In spite of my effort to keep the list as
complete as possible, I am afraid many people are still missing here.
Dirk Behme <dirk.behme@de.bosch.com>
Fillod Stephane <FillodS@thmulti.com>
Geoffrey Espin <espin@idiom.com>
Gerald Champagne <gerald.champagne@esstech.com>
Henri Girard <khgirard@broadbandnetdevices.com>
Neal Crook <ncrook@micron.com>
Steven J. Hill <sjhill@realitydiluted.com>
TAKANO Ryousei <takano@axe-inc.co.jp>
- 2002/12/11
- Incorporate comments from Neal Crook.
- 2002/08/23
- Add change log. Make external links the _top frame. Update tools (thanks to Henri Girard)
- 2004/01/26
- Included initial batch of changes
and updates from Steven Hill (Big thanks!). External links are now
shown in a separate window.