Kernel Hacking

Porting the Linux Kernel to an unsupported SH (or other) board. (A thought process map.)

The SH3 development board that (A Logic Product Development SH-3 7727 Board) I bought was pretty poorly supported under Linux by the manufacturer. Of course, there was no way of knowing before I bought it, because 1) their BSP packages were hidden before purchase and 2) they flat out lied about their level of support for Linux. (It happens.)

So I set about writing some of the device drivers myself, and in the process started keeping this running log of insights into kernel hacking that I picked up along the way. This page serves as a guide for the uninitiated. If you've never leapt into the Linux kernel before, hopefully it helps you along. I'll do my best to highlight key points that saved me time or helped me greatly.

(TODO: highlight and separate out keypoints at points).

A set of somewhat old cross-compiler tools built for Cygwin was provided on a CD, and a simple "hello world" program, but no kernel, and no kernel config. The "hello world" program was a simple program running on the bare hardware that echoed text directly to the serial port, and little else. It did give me some important initial hints, however.

readelf

If you run the command 'readelf -h' on an Executable Loadable Format (ELF) file, it will give you a host of information / tell you something like this:

    [vilimpoc@vilimpoc src]$ ls
    Makefile      arch     liblolo.a  readme                   sample_LSH7727_20_RAM
    Makefile.inc  include  main       sample_LSH7727_20_FLASH
    [vilimpoc@vilimpoc src]$ sh3-unknown-linux-gnu-readelf -h sample_LSH7727_20_RAM
    ELF Header:
        Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
        Class:                             ELF32
        Data:                              2's complement, little endian
        Version:                           1 (current)
        OS/ABI:                            UNIX - System V
        ABI Version:                       0
        Type:                              EXEC (Executable file)
        Machine:                           Renesas / SuperH SH
        Version:                           0x1
        Entry point address:               0xac0c0000
        Start of program headers:          52 (bytes into file)
        Start of section headers:          68160 (bytes into file)
        Flags:                             0x1
        Size of this header:               52 (bytes)
        Size of program headers:           32 (bytes)
        Number of program headers:         1
        Size of section headers:           40 (bytes)
        Number of section headers:         15
        Section header string table index: 12
    [vilimpoc@vilimpoc src]$ 

Now, the above is information contained in the ELF header of the sample program. Note the entry point address. If we look at the memory map of the development board, we see that the sample program's entry point address is at the first free memory location exactly adjacent to the bootloader program.

[insert SH7727 Memory Map diagram here] [insert bolo memory map here]

What we need to do, then, to get our Linux kernel to do its thing, is to configure our kernel so that the entry point address matches exactly (or close to exactly) the known working entry address of the sample program. In other words, we want to stick the kernel into free memory, and we've got an example of a program running properly at that address. There is one other thing to note about the memory address, but I will explain that after we build the kernel.**need to explain the caching difference.**

Setting up the kernel directories.

So hopefully you've got a nice kernel development tree set up under a non-privileged user account on your system. I have mine at /opt/kerneldev/linux-2.6.x with full permissions for my user. Early on, one of the kernel source maintainers told me to make sure that anything I develop, be developed against the latest stable kernel. Things change *very* quickly and this is good advice.

cvs -d:pserver:anonymous@cvs.sf.net:/cvsroot/linuxsh -z3 co linux
scripts/treelink.sh ../linux /usr/src/linux-2.4.20
http://linuxsh.sourceforge.net/docs/getting-source.php3#CVS will anonymously and read-only check out the LinuxSH CVS HEAD.
cvs -d:pserver:anonymous@cvs.sf.net:/cvsroot/linuxsh -z3 update linux
will make sure all your local code is up to date. It will not overwrite any code you've been working on (unless you want it to).

Kernel configuration

One good way to start is by examining existing default configurations. These are usually stored in the arch/yourarch/configs/ directory. See if you can find a kernel config that best approximates your processor, memory, and peripheral config. Try compiling that config using 'make [name_defconfig]' and running the readelf command on it. Check the entry point address. If it's close, it may at least boot.

I was lucky. A stripped down version of the HP Jornada kernel booted on my board and halted at the failure of the VFS (virtual file system) to load the root disk / initial RAM disk (initrd). But the fact that the kernel worked, and that I was getting console output across the serial interface (SCI/F), meant that kernel hacking could begin.

Some of the more important config options are on the "System type" tab.

     Linux Kernel v2.6.7-sh Configuration
     ------------------------------------------------------------------------------
      +------------------------------ System type ------------------------------+
      |  Arrow keys navigate the menu.  <Enter> selects submenus --->.          |
      |  Highlighted letters are hotkeys.  Pressing <Y> includes, <N> excludes, |
      |  <M> modularizes features.  Press <Esc><Esc> to exit, <?> for Help.     |
      |  Legend: [*] built-in  [ ] excluded  <M> module  < > module capable     |
      | +---------------------------------------------------------------------+ |
      | |    SuperH system type (LPD7727)  --->                               | |
      | |    Processor family (SH-3)  --->                                    | |
      | |    Processor subtype (SH7709)  --->                                 | |
      | |[*] Support for memory management hardware                           | |
      | |[*] Default bootloader kernel arguments                              | |
      | |(console=ttySC1,115200 initcall_debug mem=32M) Initial kernel command| |
      | |(0x0c000000) Physical memory start address                           | |
      | |(0x02000000) Physical memory size                                    | |
      | |[ ] Override default load address and memory size                    | |
      | |[ ] DSP support                                                      | |
      | |[*] ADC support                                                      | |
      | |(0x00001000) Zero page offset                                        | |
      | |(0x000c0000) Link address offset for booting                         | |
      | |[*] Little Endian                                                    | |
      | |[ ] Preemptible Kernel (EXPERIMENTAL)                                | |
      | |[ ] Wakeup UBC on startup                                            | |
      | |[ ] Use write-through caching                                        | |
      | |[ ] Operand Cache RAM (OCRAM) support                                | |
      | |[ ] Symmetric multi-processing support                               | |
      | |(25800000) Peripheral clock frequency (in Hz)                        | |
      | |    CPU Frequency scaling  --->                                      | |
      | |    DMA support  --->                                                | |
      | |    Companion Chips  --->                                            | |
      | +---------------------------------------------------------------------+ |
      +-------------------------------------------------------------------------+
      |                   < Select >   < Exit >    < Help >                     |
      +-------------------------------------------------------------------------+

To fix our memory issue, we switch the "Physical memory start address" 0x0c000000, per the memory map, and then we switch the "Link address offset for booting" to 0x000c0000, which will point us to the first free address exactly adjacent to the bootloader. Also a good idea is to set "default bootloader kernel arguments", to send console output to the serial port.

I generally configure the minimal kernel possible before compiling, a) to save time and b) to reduce chance of error at this early stage.

Now compile and load the kernel onto the board. I use minicom under Linux, to talk to the bootloader, and then I use the dd command to transfer the kernel over the null modem cable.

A final note about the memory map. The sample program had a program entry point of:

Entry point address: 0xac0c0000

Looking at the readelf output, our new kernel has an entry point address of:

[vilimpoc@vilimpoc compressed]$ pwd /opt/kerneldev/linux-2.6.7/arch/sh/boot/compressed [vilimpoc@vilimpoc compressed]$ sh3-unknown-linux-gnu-readelf -h vmlinux ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Renesas / SuperH SH Version: 0x1 Entry point address: 0x8c0c0000 Start of program headers: 52 (bytes into file) Start of section headers: 603624 (bytes into file) Flags: 0x3 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 40 (bytes) Number of section headers: 23 Section header string table index: 20 [vilimpoc@vilimpoc compressed]$ file vmlinux vmlinux: ELF 32-bit LSB executable, Hitachi SH, version 1 (SYSV), statically linked, not stripped [vilimpoc@vilimpoc compressed]$

In our case, the most-significant nibble (bits 28-31) of the address simply affects whether or not the program accessed cached or uncached memory. We want our kernel to use cached memory, but in all other respects, 0x8c0c0000 and 0xac0c0000 address the same memory location.

Sometimes this doesn't work. I was having problems with getting the kernel to boot from the address 0x8c0c0000. At one point, I had the kernel booting at 0x8c800000, but then you're skipping 8MB of memory, so I bumped it down to 0x8c100000, and it worked. (e.g. it boots starting at the 1st megabyte memory location.

Building the root filesystem RAM disk image (initrd)
See: http://www.uwsg.iu.edu/hypermail/linux/kernel/0210.3/0988.html
RAM root file system
From: juanba (juanba at suinsa.com)
Date: Mon Oct 28 2002 - 07:06:15 EST

I am trying to build a boot image which contains itself the kernel and
the root file system. In a first approach i will use lilo as loader
cause i am using my latop as test-host. I am not planning to pass
parametters to the kernel, the stuff will be deeply embbeded in the
future. So all the parameters setup should be pre-made with rdev an so
on.

Which approaches could be taken?
Should i use the initrd stuff? Is it mandatory?

I am following the Linux Bootdisk but i can not find my attached crfs
image. I have seen i have printked in the rd.c module the values of my.
This is the proccess that i have followed

- compressed-kernel-image
[root@tau root-fs]# ls -s bzImage-rt3.1-nodiskbzImage-rt3.1
 996 bzImage-rt3.1
- i will left 50 free blocks so the file will have the crfs at the block
1046
- to build the crfs i think that a raw 8Mbytes file is enough
[root@tau root-fs]# dd if=/dev/zero of=e2rfs_test.img bs=1k count=8192
[root@tau root-fs]# mke2fs e2rfs_test.img
[root@tau root-fs]# mount -o loop e2rfs_test.img mount/

And fill the file structure /sbin, /bin, ans so forth

[root@tau root-fs]# ummount /mount
[root@tau root-fs]# gzip -v9 e2rfs_test.img

- Kernel/Image configuration
The magic number for redev 17430 = 1046 + 2^14
[root@tau root-fs]# rdev -r bzImage-rt3.1-nodisk 17430
[root@tau root-fs]# rdev bzImage-rt3.1-nodisk /dev/ram0

- Join Kernel/customized crfs
[root@tau root-fs]# cp bzImage-rt3.1-nodisk boot-rt3.1-nodisk.img
[root@tau root-fs]# dd if=e2rfs_test.img.gz of=boot-rt3.1-nodisk.img
bs=1k seek=1046

- So boot-rt3.1-nodisk.img have all the stuff
- The Lilo stuff
/etc/lilo.conf extract

image=/boot/boot-rt3.1-nodisk.img
        label=linux-rt3.1-nodisk
        append=" devfs=mount"
        read-only

[root@tau root-fs]# cp boot-rt3.1-nodisk.img /boot
[root@tau root-fs]# lilo

and all seems ok, lilo downloads the info to my master boot record
reboot and ..

cramfs:wrong magic
Kernel Panic: VFS: Unable to mount root fs on 01:00

which corresponds with /dev/ram0

Any Hints??

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/ 

Here's how I did it.

root=/dev/ram0 init=/linuxrc real_root=/dev/hdax

Linux from Scratch: Section III, Chapter 6 http://lfs.oregonstate.edu/lfs/view/5.1.1/

First step, I sifted through the hardware reference materials supplied to me by the board maker. Important information that you need to look for in this documentation is the layout of physical memory at program load time. This tells you where SDRAM is located, where memory-mapped I/O devices are located and other important information that you will need as you write the hardware initialization routines.

So I built the initial RAM disk, but I get this problem when I boot (in addition to having problems getting the bootloader to even start running my program at location 0x8c100000. I switch the entry point address to 0x8c800000 (that should leave me 24MB to play with, so I'm still good.)

The boot + root image still doesn't see the initial RAM disk image, checking the kernel output, it looks like this:

losh> jump
jumping to: 8c800000...
Linux version 2.6.7-sh (vilimpoc@vilimpoc.local) (gcc version 3.3.2) #13 Mon Jun 21 21:18:06 EDT 2004
initrd extends beyond end of memory (0x60f89ed9 > 0x0e000000)
disabling initrd
On node 0 totalpages: 8192
  DMA zone: 8192 pages, LIFO batch:2
  Normal zone: 0 pages, LIFO batch:1
  HighMem zone: 0 pages, LIFO batch:1
Built 1 zonelists
Kernel command line: console=ttySC1,115200 initcall_debug mem=32M
PID hash table entries: 16 (order 4: 128 bytes)
CPU clock: 309.60MHz
Bus clock: 51.60MHz
Module clock: 51.60MHz
Interval = 129000
Console: colour dummy device 80x25
Memory: 30528k/32768k available (777k kernel code, 2192k reserved, 997k data, 40k init)
Calibrating delay loop... 77.20 BogoMIPS
Dentry cache hash table entries: 4096 (order: 2, 16384 bytes)
Inode-cache hash table entries: 2048 (order: 1, 8192 bytes)
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
CPU: SH7729
Linux NoNET1.0 for Linux 2.6
SH Virtual Bus initialized
HD64461 configured at 0xb0000000 on irq 36(mapped into 80 to 95)
devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au)
devfs: devfs_debug: 0x0
devfs: boot_options: 0x1
SuperH SCI(F) driver initialized
ttySC0 at MMIO 0xfffffe80 (irq = 25) is a sci
ttySC1 at MMIO 0xa4000150 (irq = 59) is a scif
ttySC2 at MMIO 0xa4000140 (irq = 55) is a irda
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
mice: PS/2 mouse device common for all mice
Kernel panic: VFS: Unable to mount root fs on ram0

Why is the initrd getting disabled?

Track it down in the source:

[root@vilimpoc sh]# grep -r -i 'initrd extends beyond end of memory' *
Binary file boot/compressed/vmlinux.bin matches
kernel/setup.c:                 printk("initrd extends beyond end of memory "
kernel/.orig/setup.c:                   printk("initrd extends beyond end of memory "
Binary file kernel/setup.o matches
Binary file kernel/built-in.o matches
[root@vilimpoc sh]# 

Check kernel/setup.c to see why this is breaking... with line numbers from the 2.6.7 version.

     33 extern void * __rd_start, * __rd_end;
[...]
    364 #ifdef CONFIG_BLK_DEV_INITRD
    365         ROOT_DEV = MKDEV(RAMDISK_MAJOR, 0);
    366         if (&__rd_start != &__rd_end) {
    367                 initrd_start = (unsigned long)&__rd_start;
    368                 initrd_end = (unsigned long)&__rd_end;
    369         }
    370  
    371         if (LOADER_TYPE && INITRD_START) {
    372                 if (INITRD_START + INITRD_SIZE <= (max_low_pfn << PAGE_SHIFT)) {
    373                         reserve_bootmem_node(NODE_DATA(0), INITRD_START+__MEMORY_STAR
    373 T, INITRD_SIZE);
    374                         initrd_start =
    375                                 INITRD_START ? INITRD_START + PAGE_OFFSET + __MEMORY_
    375 START : 0;
    376                         initrd_end = initrd_start + INITRD_SIZE;
    377                 } else {
    378                         printk("initrd extends beyond end of memory "
    379                             "(0x%08lx > 0x%08lx)\ndisabling initrd\n",
    380                                     INITRD_START + INITRD_SIZE,
    381                                     max_low_pfn << PAGE_SHIFT);
    382                         initrd_start = 0;
    383                 }
    384         }
    385 #endif

So the question is, why does this code output this crazy value:

initrd extends beyond end of memory (0x60f89ed9 > 0x0e000000)

    378                         printk("initrd extends beyond end of memory "
    379                             "(0x%08lx > 0x%08lx)\ndisabling initrd\n",
    380                                     INITRD_START + INITRD_SIZE,
    381                                     max_low_pfn << PAGE_SHIFT);

We need to figure out why INITRD_START + INITRD_SIZE == 0x60f89ed9, and we need to figure out how to change these values to get the initial RAM disk working. More grepping

grep -r -i INITRD_START *
grep -r -i PARAM *
Documentation/i386/boot.txt:                    ramdisk_size = ;

Then we poke through setup.c and add a bunch of printk's to figure out initial values in the code above:

                printk("initrd_start: 0x%08lx\n", initrd_start);
                initrd_end = (unsigned long)&__rd_end;
                printk("initrd_end: 0x%08lx\n", initrd_end);
        }

        printk("initrd_size: %0x08lx\n", INITRD_SIZE);
        printk("memory_start: %0x08lx\n", __MEMORY_START);
        printk("max_low_pfn: %0x08lx\n", max_low_pfn);

Then rebuild the kernel and see what we can see on the next boot.

    initrd_start: 0x8c0e0000
    initrd_end: 0x8c1bc000
    initrd_size: 0xe1006ef3
    memory_start: 0x0c000000
    max_low_pfn: 0x0000e000
    initrd extends beyond end of memory (0x60f89ed9 > 0x0e000000)

So our initrd_size is all screwed up.

Where is INITRD_SIZE defined? grep.

Read Documentation/kernel-parameters.txt, which tells you about the ramdisk_size parameter. Try adding it to:

      lqqqqqqqqqqqqqqqqqqqqq Initial kernel command string qqqqqqqqqqqqqqqqqqqqqk
      x  Please enter a string value. Use the  key to move from the input  x
      x  field to the buttons below it.                                         x
      x lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk x
      x xconsole=ttySC1,115200 initcall_debug mem=32M ramdisk_size=2048       x x
      x mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj x
      x                                                                         x
      tqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu
      x                         <  Ok  >      < Help >                          x
      mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj

    926  
    927         ramdisk_size=   [RAM] Sizes of RAM disks in kilobytes
    928                         New name for the ramdisk parameter.
    929                         See Documentation/ramdisk.txt.
    930 

This doesn't work. I get too many crashes. Try some new settings. Eliminate ramdisk_size. Getting closer. New config and new boot.

Linux version 2.6.7-sh (vilimpoc@vilimpoc.local) (gcc version 3.3.2) #19 Tue Jun 22 23:49:06 EDT 2004
initrd_start: 0x00000000
initrd_end: 0x00000000
initrd_size: 0x000dc000
memory_start: 0x0c000000
max_low_pfn: 0x0000e000
On node 0 totalpages: 8192
  DMA zone: 8192 pages, LIFO batch:2
  Normal zone: 0 pages, LIFO batch:1
  HighMem zone: 0 pages, LIFO batch:1
Built 1 zonelists
Kernel command line: console=ttySC1,115200 initcall_debug mem=32M
PID hash table entries: 16 (order 4: 128 bytes)
CPU clock: 309.60MHz
Bus clock: 51.60MHz
Module clock: 51.60MHz
Interval = 129000
Console: colour dummy device 80x25
Memory: 30528k/32768k available (777k kernel code, 2192k reserved, 997k data, 40k init)
Calibrating delay loop... 77.20 BogoMIPS
Dentry cache hash table entries: 4096 (order: 2, 16384 bytes)
Inode-cache hash table entries: 2048 (order: 1, 8192 bytes)
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
CPU: SH7729
checking if image is initramfs...it isn't (ungzip failed); looks like an initrd
Freeing initrd memory: 880k freed
Linux NoNET1.0 for Linux 2.6
SH Virtual Bus initialized
HD64461 configured at 0xb0000000 on irq 36(mapped into 80 to 95)
devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au)
devfs: devfs_debug: 0x0
devfs: boot_options: 0x1
SuperH SCI(F) driver initialized
ttySC0 at MMIO 0xfffffe80 (irq = 25) is a sci
ttySC1 at MMIO 0xa4000150 (irq = 59) is a scif
ttySC2 at MMIO 0xa4000140 (irq = 55) is a irda
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
mice: PS/2 mouse device common for all mice
RAMDISK: Couldn't find valid RAM disk image starting at 9.
Kernel panic: VFS: Unable to mount root fs on ram0

Note that initrd_start and initrd_end addresses are futzed back to 0x00000000, does this have something to do with the ungzip failure? I think it might, so I add the settings back into setup.c (where they were taken out by the patch from Doyu-san).

Still getting problems.

    checking initrd addresses from init/initramfs.c
    initrd_start: 0x8c0e0000
    initrd_end: 0x8c1bc000
    checking if image is initramfs...it isn't (ungzip failed); looks like an initrd
    Freeing initrd memory: 880k freed

    [...]
    
    RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
    mice: PS/2 mouse device common for all mice
    RAMDISK: Couldn't find valid RAM disk image starting at 9.
    Kernel panic: VFS: Unable to mount root fs on ram0

WTF.

More problems.

A friend sends me a copy of his ext2 ramdisk image. Works once and then fails.

Why?

hexdump -C vmlinux.bin > vmlinux.bin.hexdump
grep through it. 
What do gzip headers normally look like?
Where is our ramdisk image located in memory and what's going on there?
less vmlinux.bin.hexdump
search for magic gzip bytes "1f 8b 08 08". They are located at mem location

[vilimpoc@vilimpoc compressed]$ grep "1f 8b 08 08" vmlinux.bin.hexdump
000de000  1f 8b 08 08 81 51 b5 40  02 03 72 61 6d 64 69 73  |.....Q.@..ramdis|
[vilimpoc@vilimpoc compressed]$ 

But we set the zero page location to:
 (0x00000000) Zero page offset  
so why is:
    283 initrd_start: 0x8c0df000
when we boot up?

Alright, let's try this. Basically, most of the kernel functionality is present. The big problem seems to be simply getting the right gzip information to ungzip(). If we can get the header to the decompressor, things should go smoothly from there. But where is the header in loaded memory? How can we find the magic bytes "1f 8b 08 08" They exist in the vmlinux.bin file, so they should exist in the memory map when the compressed vmlinux is loaded, right?

Try this.

Create a static unsigned int value = 0x1f8b0808 in initramfs.c

Then, create an (unsigned int *) set to an address near where we think the gzip header is. write a for loop to loop through the bytes and see if it can find the damn header. Since we're int privileged mode, we can access any mem location we want with a simple pointer deref.

If the value matches, we have a winner, print out the address. If not, try the next address. I'm not sure if this will work on non-byte aligned accesses, but we will find out shortly. Be careful with pointer arithmetic.

    static void __init find_gzip_magic(void)
    {
            unsigned long memptr  = 0x8c0dc000;
            unsigned long memstop = 0x8c19f000;
                    
            const unsigned long gzip_magic = 0x1f8b0808;
            unsigned long col = 0;
                                    
            for ( ; memptr != memstop; memptr++) {
                    if (*(unsigned long *)memptr == gzip_magic)
                    {
                            printk("found gzip magic header 01 8b 08 08 at: 0x%08lx\n", memptr);
                            break;
                    }
                    else
                    {
                            printk(".");
                            (col % 80) ? col++ : printk("\n");
                    }
            }
    
            printk("mem locations searched: %ld\n", col);
    }

So can we find the address we need?

The line "Entry point address" is important. It tells you the memory address where the operating system can expect to find the entry point into your program. Or another way of looking at it, it represents the memory address where the operating system enters your program by jumping to that address.

First steps -- figure out what is supported in the current kernel. The easiest way to do this is to find the closest possible kernel configuration that produces a boot on your board. You can work from there.

Then figure out what's not supported in current kernels.

In this case, the Renesas SH3 chip architecture is supported using a GCC cross-compiler.

However, many of the specialized add-ons to the specific SH7727 chip were not yet supported by the stock Linux kernel. The subsystems not yet supported are: the LCD controller module, the SMSC 91C111 ethernet chip, the Philips UDA1345 audio chip, support for the memory-mode CompactFlash interface, the USB host and function adapters, and a few chip-specific features such as the ADC and DAC converters.

Next, prioritize what you want to work on first.

If you have an example kernel that worked upon boot, you should copy the requisite source and header files to new directories under the arch/sh/boards directory and include/asm/ directory.

To determine what makes the working configuration work, a good command to run in these directories is 'grep -i include *', this will return the includes that are included in a working kernel and help to limit down the wild goose chase of figuring out what calls go where.

First stop: arch/sh/boards/lpd7727/ Here I've placed copies of the io.c, mach.c, and setup.c used by a working kernel config. Now it's time to dive into these files and figure out how they interact with our hardware.

This is where open-source really shines. First, we create a new board configuration, then we snatch existing code as a base for our modifications. Since we don't want the new board configuration to depend too heavily on an existing board, and because know that the board is likely sufficiently different, we fork new include/asm/ and arch/sh/boards/ directories to keep our new code separate. Now we can begin to make our modifications.

First, we fix the #includes to point to our forked include files.

arch/sh/boards/lpd7727/:
grep -i include *.c

returns:

io.c:#include 
io.c:#include 
io.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:/* #include  */ /* comment out the DAC for now. */
As the reader can see, the machine dependent bits of setup.c have been shifted to a local copy of asm/lpd7727/hd64461.h and which we will be modifying to fit our needs. If we look at asm/hp6xx/io.h we see this:
#ifndef __ASM_SH_HP6XX_IO_H
#define __ASM_SH_HP6XX_IO_H

/*
 * Nothing special here.. just use the generic cchip io routines.
 */
#include 

#endif /* __ASM_SH_HP6XX_IO_H */
So we copy asm/hd64461/io.h to asm/lpd7727/io.h we don't know what kind of changes we'll be making to that file for the new board. We know it currently works, but we're not sure if it will keep working, so we should use our own copy. So when we're done, we re-run grep to make sure our board-specific includes point to our board like so:
io.c:#include 
io.c:#include 
io.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
mach.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:#include 
setup.c:/* #include  */ /* comment out the DAC for now. */
Check the header files with grep to see if there are any board-specific #includes there and fix in the same way. Next, we have to add our board to the kernel build system. Paul Mundt explains this process for the SH platform in the file Documentation/new-machine.txt and on the Web at http://linuxsh.sourceforge.net/docs/new-board.php3 Since we are used an established machine setup and machine vector, we skip to section 3, which explains how to hook into the build system.
The first thing to do is to add an entry to arch/sh/Kconfig, under the
"System type" menu:

config SH_LPD7727
	bool "LPD7727"
	help
	  Select LPD7727 if configuring for a Logic Product Design SH7727 dev board.

3. Hooking into the Build System
================================

Now that we have the corresponding directories setup, and all of the
board-specific code is in place, it's time to look at how to get the
whole mess to fit into the build system.

Large portions of the build system are now entirely dynamic, and merely
require the proper entry here and there in order to get things done.

The first thing to do is to add an entry to arch/sh/Kconfig, under the
"System type" menu:

config SH_VAPOR
	bool "Vapor"
	help
	  select Vapor if configuring for a FooTech Vaporboard.

next, this has to be added into arch/sh/Makefile. All boards require a
machdir-y entry in order to be built. This entry needs to be the name of
the board directory as it appears in arch/sh/boards, even if it is in a
sub-directory (in which case, all parent directories below arch/sh/boards/
need to be listed). For our new board, this entry can look like:

machdir-$(CONFIG_SH_VAPOR)	+= vapor

provided that we've placed everything in the arch/sh/boards/vapor/ directory.

Next, the build system assumes that your include/asm-sh directory will also
be named the same. If this is not the case (as is the case with multiple
boards belonging to a common family), then the directory name needs to be
implicitly appended to incdir-y. The existing code manages this for the
Solution Engine and hp6xx boards, so see these for an example.

Once that is taken care of, it's time to add an entry for the mach type.
This is done by adding an entry to the end of the arch/sh/tools/mach-types
list. The method for doing this is self explanatory, and so we won't waste
space restating it here. After this is done, you will be able to use
implicit checks for your board if you need this somewhere throughout the
common code, such as:

	/* Make sure we're on the FooTech Vaporboard */
	if (!mach_is_vapor())
		return -ENODEV;

also note that the mach_is_boardname() check will be implicitly forced to
lowercase, regardless of the fact that the mach-types entries are all
uppercase. You can read the script if you really care, but it's pretty ugly,
so you probably don't want to do that.

Now all that's left to do is providing a defconfig for your new board. This
way, other people who end up with this board can simply use this config
for reference instead of trying to guess what settings are supposed to be
used on it.

Also, as soon as you have copied over a sample .config for your new board
(assume arch/sh/configs/defconfig-vapor), you can also use this directly as a
build target, and it will be implicitly listed as such in the help text.

Looking at the 'make help' output, you should now see something like:

Architecture specific targets (sh):
  zImage                  - Compressed kernel image (arch/sh/boot/zImage)
  defconfig-adx           - Build for adx
  defconfig-cqreek        - Build for cqreek
  defconfig-dreamcast     - Build for dreamcast
...
  defconfig-vapor         - Build for vapor

which then allows you to do:

$ make ARCH=sh CROSS_COMPILE=sh4-linux- defconfig-vapor vmlinux

which will in turn copy the defconfig for this board, run it through
oldconfig (prompting you for any new options since the time of creation),
and start you on your way to having a functional kernel for your new
board.

**Once you have the board added, and headers switched, and code to a generally stable spot, make sure you re-run the scripts/treelink.sh to link your drop-in tree code over the stock kernel components.**

R....
elf file type : 0x0002
machine type: 002a  version: 1
prog start addr : 0x88800000
num prog headers: 1
num sect headers: 23
offset : 0x00010000  disk length: 0x0007c000  mem len: 0x00088434
phyaddr: 0xa8800000  vaddr      : 0x88800000  dl addr: 0x88800000
ignoring rest of file... 34655 bytes. done
running md5sum on the _loaded_ portion of the file:
2e3e351356413308e16e01abb02a281f - addr: 88800000 len: 0007c000
Distributed compiling.

If you have multiple computers that you can use, it is a good idea to set up a copy of distcc, the front-end for a distributed gcc compiler. It's even easier if you're cross-compiling, as all you need to do is to put a copy of the cross-compiler in a standard location on the three machines.

Then, to reference the cross-compiler when building the kernel you can use an alias like so:

alias smake='make -j8 ARCH=sh CROSS_COMPILE="distcc /opt/crosstool/sh3-unknown-linux-gnu/gcc-3.3.2-glibc-2.3.2/bin/sh3-unknown-linux-gnu-"

distcc will automatically call the proper compiler command when necessary, e.g. it appends one of {gcc, as, ld, objcopy} and so forth, to the end of what was passed in the CROSS_COMPILE variable.

Building the framebuffer display driver.

From the Renesas hardware reference manual, we know that the SH-3 7727 has a built in LCD controller capable of operating on up to 1024x1024 pixels using system memory. Not bad for an embedded chipset. We also see that the device is a memory-mapped I/O device and should be pretty simple to write a framebuffer driver.

First step, look at someone else's example. At the time of this writing, the Linux 2.6.7 kernel included three existing framebuffer drivers, hitfb.c for the Hitachi 64461 framebuffer companion chip, epson1355fb.c for the Epson 1355 companion chip, and pvr2fb.c for the PowerVR2 chipset used in the Sega Dreamcast. After taking a quick look at the three examples, the one that seems to fit our bill the best, offering a useful framebuffer console, is the Epson chip.

But before you go a step further and fork that driver, try compiling it by selecting it in the kernel configuration and then running:

         make   dir/file.[ois]  - Build specified target only
    e.g. make   drivers/video/epson1355.o

I saw the following output:

    [vilimpoc@vilimpoc linux-2.6.7]$ mmake drivers/video/epson1355fb.o 
      CC      drivers/video/epson1355fb.o
    drivers/video/epson1355fb.c:34:30: video/fbcon-cfb8.h: No such file or directory
    drivers/video/epson1355fb.c:35:29: video/fbcon-mfb.h: No such file or directory
    drivers/video/epson1355fb.c:36:25: video/fbcon.h: No such file or directory
    drivers/video/epson1355fb.c:77: error: field `gen' has incomplete type
    drivers/video/epson1355fb.c: In function `e1355_set_disp':
    drivers/video/epson1355fb.c:435: error: dereferencing pointer to incomplete type
    drivers/video/epson1355fb.c:437: error: dereferencing pointer to incomplete type
    drivers/video/epson1355fb.c:450: error: dereferencing pointer to incomplete type
    drivers/video/epson1355fb.c: At top level:
    drivers/video/epson1355fb.c:461: error: variable `e1355_switch' has initializer but incomplete type
    drivers/video/epson1355fb.c:462: error: unknown field `detect' specified in initializer
    drivers/video/epson1355fb.c:462: warning: excess elements in struct initializer
    drivers/video/epson1355fb.c:462: warning: (near initialization for `e1355_switch')
    drivers/video/epson1355fb.c:463: error: unknown field `encode_fix' specified in initializer
    drivers/video/epson1355fb.c:463: warning: excess elements in struct initializer
    drivers/video/epson1355fb.c:463: warning: (near initialization for `e1355_switch')
    drivers/video/epson1355fb.c:464: error: unknown field `decode_var' specified in initializer
    drivers/video/epson1355fb.c:464: warning: excess elements in struct initializer
    drivers/video/epson1355fb.c:464: warning: (near initialization for `e1355_switch')
    drivers/video/epson1355fb.c:465: error: unknown field `encode_var' specified in initializer
    drivers/video/epson1355fb.c:465: warning: excess elements in struct initializer
    drivers/video/epson1355fb.c:465: warning: (near initialization for `e1355_switch')
    [...]
    [vilimpoc@vilimpoc linux-2.6.7]$ find | grep -i fbcon*
    ./drivers/video/console/fbcon.c
    ./drivers/video/console/fbcon.h
    ./drivers/video/cfbcopyarea.c
    [vilimpoc@vilimpoc linux-2.6.7]$ 

As it turns out, the Epson 1355 driver is too out of date to serve as a good driver model to learn from. The includes are screwed up, it doesn't compile cleanly and is not maintained. In addition, an old driver may be a bad example to learn from, since changes may have occurred to the kernel API. So let's take a look at the Hitachi 64461 framebuffer driver in drivers/video/hitfb.c, which compiles properly under Linux 2.6.7.

So we copy the file, and start our new framebuffer chip fork. First on the menu, register definitions. From the reference manual, we get the following memory addresses that control the chip:
[insert graphic from PDF]

    /*
       Memory-mapped register defines.  See mnemonic names in Section 25, 
       pages 719/962 (801/1047 of the PDF) of the Renesas SH7727 Hardware Manual 
       revision 4, dated January 2003.
    */
    
    #define SH7727_LDICKR   0xA4000C00    /* 16 */
    #define SH7727_LDMTR    0xA4000C02    /* 16 */
    #define SH7727_LDDFR    0xA4000C04    /* 16 */
    #define SH7727_LDSMR    0xA4000C06    /* 16 */
    #define SH7727_LDSARU   0xA4000C08                /* 32 */
    #define SH7727_LDSARL   0xA4000C0C                /* 32 */
    #define SH7727_LDLAOR   0xA4000C10    /* 16 */
    #define SH7727_LDPALCR  0xA4000C12    /* 16 */
    #define SH7727_LDHCNR   0xA4000C14    /* 16 */
    #define SH7727_LDHSYNR  0xA4000C16    /* 16 */
    #define SH7727_LDVDLNR  0xA4000C18    /* 16 */
    #define SH7727_LDVTLNR  0xA4000C1A    /* 16 */
    #define SH7727_LDVSYNR  0xA4000C1C    /* 16 */
    #define SH7727_LDACLNR  0xA4000C1E    /* 16 */
    #define SH7727_LDINTR   0xA4000C20    /* 16 */
    #define SH7727_LDPMMR   0xA4000C24    /* 16 */
    #define SH7727_LDPSPR   0xA4000C26    /* 16 */
    #define SH7727_LDCNTR   0xA4000C28    /* 16 */
    
    /* LDPR00 - FF are the 32-bit RGBA(?) palette registers */
    /* todo: add check that 0 <= x < 256 */
    
    #define SH7727_LDPALREG(x)  0xA4000800 + ((x) << 2)

Next, I re-wrote the baseline E1355 chip register reading/writing functions. Since all our memory-mapped registers are 16 or 32 bits in size, we call the SuperH specific ctrl_inw and ctrl_inl functions. These are defined in . (This may not be the best practice, as there are a uniform set of functions prefixed fb_read{b,w,l,q} and fb_write{b,w,l,q} defined in <linux/fb.h> which map to machine vector specific I/O primitives.) Nonetheless, for the sake of directness to the hardware, our code looks like this:

    /* most common reg size = 16bits */
    static inline u16 sh7727fb_read_reg(unsigned long reg)
    {
        return ctrl_inw(reg);
    }
    
    static inline void sh7727fb_write_reg(unsigned int val, unsigned long reg)
    {
        ctrl_outw(val, reg);
    }
    
    /* unsigned int = 32bits on SH-3 */
    static inline unsigned int sh7727fb_read_reg32(unsigned long reg)
    {
        return ctrl_inl(reg);
    }
    
    static inline void sh7727fb_write_reg32(unsigned int val, unsigned long reg)
    {
        ctrl_outl(val, reg);
    }

These four functions handle all of the memory reading and writing we need to do.

Next up, we need to define a version of static struct fb_fix_screeninfo (defined in <linux/fb.h>) which holds information about our framebuffer which shouldn't going to change. It looks like this:

    struct fb_fix_screeninfo {
    	char id[16];			/* identification string eg "TT Builtin" */
    	unsigned long smem_start;	/* Start of frame buffer mem */
    					/* (physical address) */
    	__u32 smem_len;			/* Length of frame buffer mem */
    	__u32 type;			/* see FB_TYPE_*		*/
    	__u32 type_aux;			/* Interleave for interleaved Planes */
    	__u32 visual;			/* see FB_VISUAL_*		*/ 
    	__u16 xpanstep;			/* zero if no hardware panning  */
    	__u16 ypanstep;			/* zero if no hardware panning  */
    	__u16 ywrapstep;		/* zero if no hardware ywrap    */
    	__u32 line_length;		/* length of a line in bytes    */
    	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
    					/* (physical address) */
    	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
    	__u32 accel;			/* Type of acceleration available */
    	__u16 reserved[3];		/* Reserved for future compatibility */
    };

In our case, the frame buffer memory length is variable, so we fill this in at init time. (I think?) Our structure looks roughly like this.

Once you've got some of your own code entered and have made some changes, you'll probably want to do some incremental compiles on the code you've written. This is pretty easy to do. First, you have to add the driver module to the appropriate Kconfig. In this case, we're doing the video driver, so we open the file: [linux source]/drivers/video/Kconfig:

    430 config FB_SH7727
    431         bool "SH-3 7727 LCD Controller support"
    432         depends on FB && SH_LPD7727
    433         help
    434           Use the on-chip LCD Controller built into the Renesas SH-3 7727 series
    435           chip. For more information see the SH7727 Hardware Manual at 
    436           .

The Kconfig file format is pretty straightforward, so I won't talk about it. Once the config option for your board has been added, you need to make a modification to the Makefile:

    obj-$(CONFIG_FB_SH7727)           += sh7727fb.o

Then, go to the [linux source] directory and run your mmake command. Go to Drivers->Graphics support turn on the framebuffers option, and badda-bing, your board is now part of the build system.

Question. What does the ALIAS_MV macro do? I changed it from ALIAS_MV(hp680) to ALIAS_MV(lpd7727) in my futzing around with mach.c .. I noticed that the machine vector is initialized in mach.c as : "struct sh_machine_vector mv_hp680 __initmv = { ... }" so I also switch this to "struct sh_machine_vector mv_lpd7727 __initmv = { ... } " I'm guessing it calls / registers the function in some way.