Skip to main content

Linux Kernel Porting to New Architecture. includes Processor Specific & Board Specific

1.Processor-specific or Architecture changes to the kernel code


1.  The number of TLB3 (translation look aside buffers) entries may be different for different implementations.

2. Add a config option (CONFIG_MYCPU) to include code that is specific to the processor. 

3.  Directories $(TOPDIR)/arch/MY_ARCH/kernel and
 $(TOPDIR)/arch/MY_ARCH/mm 
contain the processor-specific code that require modifications if you are dealing with a new implementation.

4. Assembly file $(TOPDIR)/arch/MY_ARCH/kernel/head.S contains kernel_entry, the entry point for the kernel. 
This file also contains the exception handling code. Listing 1 shows the implementation of the kernel_entry routine in pseudo code.

5. Kernel entry pseudo code: 
  1. Set desired endian mode
  1. Clear the BEV bit
  1. Set the TLB bit
  1. GOTO cpu_probe and return
  1. Set up stack for kernel
  1. Clear bss
  1. GOTO prom_init and return
  1. GOTO loadmmu and return
  1. Disable coprocessors
  1. GOTO start_kernel
        la       t3, mips_cputype
       b       probe_done
       sw       t2, (t3)
END(cpu_probe)
2. Platform/Board specific changes to the kernel code
{
       unsigned int mem_limit;// set upper limit to maximum physical RAM (32MB)
       mem_limit = 32 * 1024 * 1024;
       //In the present case, these arguments are not
       //passed from the bootloader. The kernel wants
       // one big string. put it in arcs_cmdline, which
        ater gets copied to command_line
       //(see arch/mips/kernel/setup.c)
       mips_memory_upper = KSEG0 + mem_limit;
}
6. Starting the kernel
{
       char * command_line;
         * Interrupts are still disabled. Do necessary setups, then
         * enable them
         */
        lock_kernel();
       printk(linux_banner);
       setup_arch(&command_line, &memory_start, &memory_end);
       memory_start = paging_init(memory_start, memory_end);
       trap_init();
       init_IRQ();
       sched_init();
       time_init();
       parse_options(command_line);
              unsigned long * memory_start_p, unsigned long * memory_end_p))
{ #ifdef CONFIG_BLK_DEV_INITRD
#if CONFIG_BLK_DEV_INITRD_OFILE
       extern void *__rd_start, *__rd_end;
#endif
#endif
       *cmdline_p = command_line;
       *memory_end_p = mips_memory_upper;
#if CONFIG_BLK_DEV_INITRD_OFILE
       // Use the linked-in ramdisk
       // image located at __rd_start.
       initrd_start = (unsigned long)&__rd_start;
       initrd_end = (unsigned long)&__rd_end;
       initrd_below_start_ok = 1;
       if (initrd_end > memory_end)
       {
          printk(*initrd extends beyond end of memory *
          *(0x%08lx > 0x%08lx)\ndisabling initrd\n*,
          initrd_end, memory_end);
      initrd_start = 0;
    }
#endif
#endif
}
     * mips_io_port_base is the beginning
     *of the address space to which x86
     * style I/O ports are mapped.
     */
     mips_io_port_base = 0xa0000000;
     * platform_io_mem_base is the beginning of I/O bus memory space as
     * seen from the physical address bus. This may or may not be ident-
     * ical to mips_io_port_base, e.g. the former could point to the beginning of PCI
     *memory space while the latter might indicate PCI I/O
     * space. The two values are used in different sets of macros. This
     * must be set to a correct value by the platform setup code.
     */
     platform_io_mem_base=0x10000000;
     * platform_mem_iobus_base is the beginning of main memory as seen
     * from the I/O bus, and must be set by the platform setup code.
     */
platform_mem_iobus_base=0x0; #ifdef CONFIG_REMOTE_DEBUG
    /*
     * Do the minimum necessary to set up debugging
     */
    myplatform_kgdb_hook(0);
    remote_debug = 1;
#endif
    ide_ops = &std_ide_ops;
#endif
#if defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
#endif
#endif
 * just set rtc_ops && pci_ops; forget the rest
 */
rtc_ops = &myplatform_rtc_ops;
pci_ops = &myplatform_pci_ops;
}

  • myplatform_pcibios_fixup()
  • myplatform_pcibios_read_config_byte()
  • myplatform_pcibios_read_config_word()
  • myplatform_pcibios_read_config_dword()
  • myplatform_pcibios_write_config_byte()
  • myplatform_pcibios_write_config_word()
  • myplatform_pcibios_write_config_dword()
10 .Interrupt Handling :
    printk (*Setting debug traps - please connect the remote debugger.\n*);
    set_debug_traps ();
    breakpoint ();
#endif
}
    .set    .noat
    SAVE_ALL
    CLI
    .set    .at
    andi    .a0, s0, CAUSEF_IP5
    beq    .a0, zero, 1f
    andi    .a0, s0, CAUSEF_IP2    # delay slot, check hw0 interrupt
    move    .a0, sp
    jal    .timer_interrupt
    nop    .    .# delay slot
    nop    .    .# delay slot
    beq    .a0, zero, 1f
    nop
    level zero interrupt. */
    jal    .InterruptController_InterruptHandler
    move    .a0, sp    .# delay slot
    nop    .    .# delay slot
    /* Here by mistake? This is possible,
    *what can happen is that by the time we
    *take the exception the IRQ pin goes low, so
     *just leave if this is the case.
     */
    j    .ret_from_irq
    nop
    END(myplatform_handle_int)
InterruptController_InterruptHandler (
    struct pt_regs *regs
    )
    IntVector intvector;
    struct irqaction *action;
    int irq, cpu = smp_processor_id();
    InterruptControllerGetPendingIntSrc((&irq);
      printk(*No handler for hw0 irq: %i\n*, irq);
        return;
      }
      kstat.irqs[0]irq++;
      hardirq_exit(cpu);
Timer Interrupt:

  • serial_console_init() - for registering the console printing procedure for kernel printk() functionality, before the console driver is properly initialised
  • serial_console_setup() -for initialising the serial port
  • serial_console_write(struct console *console, const char *string, int count) - for writing "count" characters

tty driver


The configuration register has to be set up correctly. 
1. The first thing to be done is to make sure that we are running in the desired endian mode.

2. The bootstrap exception vector bit needs to be cleared to make sure that the normal exception vectors are used.

3.  The TLB bit is set to make sure that TLB-based translation is used.
   $(TOPDIR)/arch/MY_ARCH/mm contains the TLB routines and cache handling routines.

4. The next step is to probe for the cputype. 
    Table 2: is simple implementation of this function. 
$(TOPDIR)/include/asm/bootinfo.h contains entries for the cpu type (MYCPU) and machine group (MY_MACH_GROUP). 

This value is used later to determine the exception handling and MMU routines that need to be loaded for the given CPU, as well as to get the CPU information in the /proc file system.

Code to probe cpu type
LEAF(cpu_probe)
        li       t2, MYCPU       /* include/asm-mips/bootinfo.h */
5. The initial stack for the kernel is set up next. 
6. The bss section of the kernel image is cleared. 
7. Control the transfers to the prom_init() function. 
8. The TLB and caches are flushed and the cache manipulation functions are set up inside loadmmu(). 
9. Disabling of the coprocessors other than coprocessor 0 is done next, 
10. Jump to start_kernel().
1. $(TOPDIR)/arch/MY_ARCH has a sub-directory for each target development platform that is supported. 
2. Create a MY_PLATFORM directory by copying a platform closest to your configuration. This directory should contain the interrupt handling, timer, initialisation, and setup routines for your specific platform. 
3. Create a MY_PLATFORM directory under $(TOPDIR)/include/asm. This directory is used to hold include files specific to your platform.
4. The prom_init() function, which is part of $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/prom.c (Listing 3), modifies the command line string to add parameters that need to be passed to the kernel from the boot loader. The machine group and upper bound of usable memory are set up here.
5. PROM initialisation:
int __init prom_init (int argc, char **argv, char **envp)
       // the bootloader usually passes us argc/argv[] . 
       strcpy (arcs_cmdline, *root=/dev/ram*);
       mips_machgroup = MY_MACH_GROUP;
       // set the upper bound of usable memory
       printk(*Detected %dMB of memory\n*, mem_limit >> 20);
       return 0;

Listing 4 contains the first few interesting lines of the start_kernel() function, located in $(TOPDIR)/init/main.c.
Listing 4: The beginning of the start_kernel function
asm linkage void __init start_kernel(void)
        /*

7. Listing 5 shows the setup_arch() function in $(TOPDIR)/arch/MY_ARCH/kernel/setup.c.
 The board-specific setup function is called from here. 

8.The command line string and memory start and memory end are passed over to the caller of this function. The start and end addresses for the linked-in ram disk image are also updated here.

Listing 5: Architecture setup function
__initfunc(void setup_arch(char **cmdline_p,
       myplatform_setup();        strncpy(command_line, arcs_cmdline, CL_SIZE);
       *memory_start_p = (unsigned_long) &_end;
#ifdef CONFIG_BLK_DEV_INITRD
Listing 6: Platform-specific initialization code
__initfunc(void myplatform_setup(void)) {
     irq_setup = myplatform_irq_setup;     /*
    /*
    /*
#ifdef CONFIG_BLK_DEV_IDE
#ifdef CONFIG_VT
/*
9. $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/setup.c contains the platform-specific initialisation code (Listing 6). Here, the various base addresses and the platform-specific RTC and PCI operations are set up.
 For PCI, the following seven functions need to be implemented for the given platform:

  The trap_init() function copies the top-level exception handlers to the KSEG0 vector location based on       the CPU type. 
The interrupt handling code is contained in $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/irq.c int-handler.S. 
Most systems use a dedicated interrupt controller to handle the interrupts in the system. The interrupt controller is hooked to one of the external interrupt lines in the processor.
The architecture-dependent code has to be modified to fit the interrupt controller into the kernel interrupt handling.
Listing 7 shows the platform-specific interrupt initialisation code. 
The topmost interrupt handler has to be installed using set_except_vector(). The interrupt controller that is used in the platform has to be initialised next. 
If remote debugging is enabled, a call to set_debug_traps() has to be made to allow any breakpoints or error conditions to be properly intercepted and reported to the debugger. In addition, a break point needs to be generated to begin communication with the debugger running on the host.
Listing 7: Platform-specific interrupt initialization
static void __init myplatform_irq_setup (void) {     set_except_vector (0, myplatform_handle_int);
    // Initialize InterruptController
    InterruptController_Init(IsrTable);
#ifdef CONFIG_REMOTE_DEBUG
The top-level interrupt handler (Listing 8) first saves all the registers and then disables further interrupts. The CAUSE register is examined to find the source of the interrupt. If it is a timer interrupt, the corresponding ISR is called. In case it is not a timer interrupt, it checks whether an interrupt has occurred on the line connected to the interrupt controller.
Listing 8: Top-level interrupt handler
NESTED(myplatform_handle_int, PT_SIZE, ra)
    mfc0    .s0, CP0_CAUSE    .# get irq mask
    /* First, we check for counter/timer IRQ. */
    /* Wheee, a timer interrupt. */
    j ret_from_irq
1:
    /* Wheee, combined hardware 
    j    .ret_from_irq
1:
The interrupt handler for the interrupt controller (Listing 9) has to get the pending interrupt vector that caused the interrupt and then execute the handler for the particular interrupt source.
Listing 9: Interrupt handler for the interrupt controller
void
{
    InterruptControllerGetPendingIntVector(&intvector);
  action = (struct irqaction *)intvector;
      if ( action == NULL ) {
    hardirq_enter(cpu);
      action->handler(irq, action->dev_id, regs);
} // Interrupt Controller_Interrupt Handler ()
The functions request_irq(), free_irq(), enable_irq() and disable_irq() have to be implemented for your target platform.
request_irq() is used to install an interrupt handler for a given interrupt source. 
free_irq() needs to free the memory allocated for the given interrupt. 
enable_irq() needs to make a call to the interrupt controller function that enables the given interrupt line disable_irq() needs to disable the given interrupt line.
Timer interrupt File $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/time.c contains the platform-dependent timer code. The Linux kernel on MIPS requires a 100Hz timer interrupt. In the MIPS, one of the timers in coprocessor 0 is programmed to generate 100Hz interrupts. 
The count register and the compare register together make up the timer. 
When active, the count register contains a free running counter. On each processor clock-tick, the value in the register increments by one. The register is reset and an external hardware interrupt is generated when the values in the count register and compare register match. After the count register is reset, it restarts to count on the next processor clock-tick. The timer interrupt service routine (ISR) needs to call the do_timer() routine. Performing a write to the compare register clears the timer interrupt.
Serial console driver:
The console runs on top of a serial driver. A polled serial driver can be used for printk() (kernel debug message) functionality. The minimum functions that this driver needs to provide are the following:
An interrupt driven serial driver can be used to create a terminal device. A terminal device can be created by registering the serial driver with tty. A variety of serial drivers are available in the $(TOPDIR)/drivers/char directory. 
The driver that matches closest to the serial port hardware being used should be picked up and modified. The interfaces to an interrupt-driven character driver under Linux have been explained in Linux Device Drivers by Rubini.
CONFIG_SERIAL (serial support) has to be defined as Y in "make menu config."  To test, hook up the interrupt-driven serial port to the host development platform and run a serial communication program to communicate with your target (terminal device).

Comments

Popular posts from this blog

DMA Debug - Zynq ZC702 - XMD Debugger

DMA Debug in Baremetal using XMD


















Writing Startup Code for ARM Cortex M4 Controllers

STM32F4 Board bringup using OpenOCD

1.STM32F4 uses ARM Cortex M4 Controller
2.Internal Jtag pin present on the STM32F4 board supports for Debugging kernel which is already flashed on the Board.
3.STM32F4 using RIOT (Revoultionay IOT ) Micro Kernel.

Adding screenshots in sequence from Reset vector table.

1.













Steps followed for writing startup code:
1.Initialize vector table
          2. Enter  to reset_vector()
         3. Copy .data section to RAM
         4. Initialize BSS section to Zeros
         5. Call board_init () 
               (call peripherals _init & cpu_init)
         6. Call libc_init_array()
         7. Call kernel_init()

Writing Startup Code for ARM Cortex A9 - Board bringup series - ZED Board

ZED Board uses Dual ARM Cortex A9
ZED board internally presents Debugger called XMD (Xilinx Microprocessor Debugger)
Xilinx provides Xilinx - SDK which supports Bare metal driver debug using XMD (XMD is having Node locked License)

This boot process is equal to debug using Hardware debugger like Lauterbach /ARM DS-5 /Segger Debugger & the Same is not possible with KGDB.
KGDB is supported grom GNU tools which can enter the Code in RAM (Maxium 4 Stack frames), Debugging code present outside the RAM is possible through Physical debuggers only.

Cortex A9 Processor boot method in Zynq SoC:

   1. The boot.S file contains a minimal set of code for transferring control from the processor's reset      2. Location to the start of the application. It performs the following tasks.
       Invalidate L1 caches, TLBs, Branch Predictor Array, etc.
       Invalidate L2 caches and initialize L2 Cache Controller.
      Enable caches and MMU
     Load MMU translation table base address into TLB  …