Thursday, 3 August 2017

Memory adressing

Chapter-  Memory addressing

This chapter deals with addressing techniques .An operating system is not forced to keep track of physical memory all by itself.
Memory Addresses : Programmers casually refer to a memory address as the way to access the contents of a memory cell. But when dealing with 80 × 86 microprocessors, we have to distinguish three kinds of addresses:
1-Logical address   2-Linear address ( virtual address)  3- Physical address
1-Logical address :logical address is machine language instruction(operand) by CPU.
 Each logical address consists of a segment and an offset (or displacement) that denotes the distance from the start of the segment to the actual address.
2-Linear address :A single 32-bit unsigned integer that can be used to address up to 4 GB—that is,up to 4,294,967,296 memory cells. Linear addresses are usually represented in hexadecimal notation; their values range from 0x00000000 to 0xffffffff .
3-Physical address:Used to address memory cells in memory chips. They correspond to the electrical signals sent along the address pins of the microprocessor to the memory bus.Physical addresses are represented as 32-bit or 36-bit unsigned integers.

The Memory Management Unit (MMU) transforms a logical address into a linear address by means of a hardware circuit called a segmentation unit; subsequently, a second hardware circuit called a paging unit transforms the linear address into a physical address. 






    

BSP Notes

1.Head.s -> start_kernel -> setup_arch
Setup_arch will get the struct machine_desc defined in the platform file . All the initialization operations on the platform are based on machine_desc . Specific conditions is compiled in accordance with arch / arm / configs in xxx_defconfig

2.According to the above judgment, HAL (BSP) is written entirely with machine_desc as the entrance
The contents of the need to write include plat / xx and mach_xx / under the kernel beat, interrupt management, clock, GPIO , DMA , IO memory mapping, pin multiplexing, external device registration and resource- related code. The external device and device resources are registered in the board initialization, and the device driver is placed in the / drivers directory.Linux BSP (here do not consider the bootloader and file system) development level has three levels: 
1. Architecture level development. 
2.SOC level of transplantation.
3. Device-driven porting. 
The development of the architecture level is generally done by the Linux kernel community or architecture vendor. After the completion of the Linux kernel source tree, this level of development mainly to complete the memory management, process scheduling, anomalies, traps, etc., the development process need to refer to the architecture Of the datasheet , such as arm920TDMI / Cortex A8 even have armV6 / armV7 instruction set architecture .
 In fact, Linux 's arch directory has supported almost all of today's architecture, it is generally not going to be architecture-level BSP development. The most common or SOC level of BSP development and driver development, SOC level of BSP developers only need to refer to SOC 's Datasheet , such as S3C2410 / MX53. Drive the development not only to refer to the datasheet of the SOC but also refer to the PCB design schematic. This article is concerned about the SOC level of development or transplantation. Popular terms, is to let a set of official standard linux source in their own board to run up.

3.Struct machine_desc members and call the time
In Setup_arch() in int_irq(), timer & init_machine are assigned to the following variables:
         init_arch_irq = mdesc-> init_irq;
         system_timer = mdesc-> timer;
         init_machine = mdesc-> init_machine;
And the three function pointers are called in the following scenarios:
Start_kernel () -> init_IRQ () [irq.c] -> init_arch_irq ();
Start_kernel () -> time_init () [time.c] -> system_time-> init ();
Customize_machine () -> init_machine ();
Customize_machine is placed in the arch_initcall section, which is called in order. 
The function in the xxx_initcall section is called in the following order: 
start_kernel () -> rest_init () [ start kernel thread ] -> kernel_init () - > do_basic_setup () -> do_initcalls ();
map_io is called in the following order:
Start_kernel () -> setup_arch () -> paging_init () -> devicemaps_init () -> map_io ()
The fixup is called in the following order:
Start_kernel () -> setup_arch () -> setup_machine_tags ()
init_early is called in the following order:
Start_kernel () -> setup_arch () -> init_early
They are called in the start_kernel () order, we can see that they are: 
fixup -> map_io ->  init_early  -> init_irq ->   timer   -> init_machine , 
it should generally follow the order in order to achieve.
4.Fixup in mx53 although set up, but is an empty function
5.Map_io done the job
void __init mx6_map_io (void)
{
        otable_init (mx6_io_desc, ARRAY_SIZE (mx6_io_desc));
        mxc_iomux_v3_init (IO_ADDRESS (MX6Q_IOMUXC_BASE_ADDR));
        mxc_arch_reset_init (IO_ADDRESS (MX6Q_WDOG1_BASE_ADDR));
        mx6_set_cpu_type ();
        mxc_cpu_lp_set (WAIT_CLOCKED);
}
iotable_init is to carry out static memory mapping, some of the physical address permanently mapped to a fixed virtual address up; these virtual addresses must be in the kernel space; why mapping? In the driver can use the ioremap function for dynamic mapping, for the kind of operation in the system need to be frequently accessed during the physical address can use static mapping; may be static mapping of the area should not be too large, mx6q no more than 4M ; Of the physical address, in the future access to the direct use of a fixed virtual address can be accessed; perhaps the use of static mapping can be scattered to the physical address of a centralized virtual address area, so you can reduce the virtual address space debris?
mxc_iomux_v3_init sets the virtual address corresponding to the IOMUXC register block because this physical address area has been statically mapped;
mxc_arch_reset_init sets the virtual address corresponding to the watchdog register block because the physical address area has been statically mapped;
mx6_set_cpu_type read register, get the processor model mx6sl / mx6q / mx6dl and model version 1/2/3
mxc_cpu_lp_set sets the CPU to the specified low power state; this function is used in the system power management code ;
6.init_early is not set in mx6
7.init_irq done the job
void mx6_init_irq (void)
{
       void __iomem * gpc_base = IO_ADDRESS (GPC_BASE_ADDR);
   struct irq_desc * desc;
        unsigned int i;
        gic_init (0, 29, IO_ADDRESS (IC_DISTRIBUTOR_BASE_ADDR), IO_ADDRESS (IC_INTERFACES_BASE_ADDR));
        If (enable_wait_mode) {
                   / * Mask the always pending interrupts - HW bug. * /
                   __raw_writel (0x00400000, gpc_base + 0x0c);
                   __raw_writel (0x20000000, gpc_base + 0x10);
        }
        for (i = MXC_INT_START; i <= MXC_INT_END; i ++) {
                   desc = irq_to_desc (i);
                   desc-> irq_data.chip-> irq_set_wake = mx6_gic_irq_set_wake;
        }
        mx6q_register_gpios ();
#ifdef CONFIG_CPU_FREQ_GOV_INTERACTIVE
                   for (i = 0; i <ARRAY_SIZE (mxc_irq_tuner); i ++)
                             cpufreq_gov_irq_tuner_register (mxc_irq_tuner [i]);
#endif
#ifdef CONFIG_PCI_MSI
                   imx_msi_init ();
#endif
}
gic_init do not know what it is
mx6q_register_gpios () initializes the GPIO , or can be seen here as a GPIO- driven portal, GPIO as a system prerequisite resource, need to be added in the arch when the system is added;
8.Timer to finish the work
static void __init mx6_sabresd_timer_init (void)
{
        struct clk * uart_clk;
#ifdef CONFIG_LOCAL_TIMERS
        twd_base = ioremap (LOCAL_TWD_ADDR, SZ_256);
        BUG_ON (! Twd_base);
#endif
        mx6_clocks_init (32768, 24000000, 0, 0);
        uart_clk = clk_get_sys ("imx-uart.0", NULL);
        early_console_setup (UART1_BASE_ADDR, uart_clk);
}
mx6_clocks_init can be considered a clock driver, where the completion of all peripheral controller clock initialization;
early_console_setup in the system is also required during the serial output, where quickly set the serial port UART clock? Really?
9.Init_machine done the job
In the mx53 or mx6 , or in the s3cc24xx series, init_machine do things very consistent, that is, to the kernel registration controller device ; mainly including uart , i2c , spi , GPU , IPU , fb, etc .; To add some of the equipment on the board, such as i2c equipment, curing the MMC , the network Phy chip and so on; In fact, this function is completely related to the linux driver, and I do not put it into the BSP developed;

Device Driver

Linux kernel module programming


1. Introduction

         This chapter explains the basic elements for writing a kernel module. By end of this chapter readers can

             * Write a kernel module
             * Pass arguments to kernel module
             * Export from kernel modules       

For executing the sample codes, I suggest to use ubuntu machine with Linux kernel version > 2.6      

Note ::

All examples in this post are executed and tested with     

1.1 Simplest kernel module

#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */

MODULE_LICENSE("GPL");    /* Setting Licence to GPL */

int __init ourinitmodule(void) /* Will be called during insmod <module>.ko */
{

    printk(KERN_ALERT "\n Welcome to sample kernel module.... \n");

    return 0;
}

void __exit ourcleanupmodule(void) /* Will be called during rmmod <module> */{
    printk(KERN_ALERT "\n Thanks....Exiting. \n");
    return;
}
/* Macros for init and cleanup module */
module_init(ourinitmodule);
module_exit(ourcleanupmodule);

Refer the following github example for ready-to-run code.
https://github.com/jeyaramvrp/kernel-module-programming/tree/master/helloworld

1.2  Passing command line arguments to a kernel module

#include <linux/module.h>
#include <linux/kernel.h>
/* Added for module_param */
#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");

/* Module param : Integer data */
static int data;
module_param(data, int, S_IRUSR|S_IWUSR);

/* Module param : String data */
static char *mystr = "Default";
module_param(mystr, charp, 0);

/* Module param : Integer Array data */
static int myarray[10] = {-1};
static int count = 0;
module_param_array(myarray, int,&count, 0);

int ourinitmodule(void)
{
    int tmp = 0;

    /* Print data - Default values will be printed if No argument is passed. */
    printk(KERN_ALERT "\n Demo for Passing arguments to kernel module \n");
    printk(KERN_ALERT "\n data:%d\n", data);
    printk(KERN_ALERT "\n mystr:%s\n", mystr);

    for(tmp = 0; tmp < count ; tmp++)   
    printk(KERN_ALERT "\n myarray[%d]:%d", tmp, myarray[tmp]);

    return 0;
}

void ourcleanupmodule(void)
{
    printk(KERN_ALERT "\n Thanks....Exiting Passing args sample.. \n");
}

module_init(ourinitmodule);
module_exit(ourcleanupmodule);
http://stackoverflow.com/questions/10994576/passing-an-array-as-command-line-argument-for-linux-kernel-module

https://github.com/jeyaramvrp/kernel-module-programming/tree/master/passingargs

http://lxr.free-electrons.com/source/include/linux/moduleparam.h?a=arm#L112

1.3  Exporting a function to another kernel module(Module dependency) 


#include <linux/module.h>
#include <linux/kernel.h>

#include "CommonHeader.h"

MODULE_LICENSE("GPL");

int __init ourinitmodule(void)
{
    printk(KERN_ALERT "\n sample -1 init.... \n");
    return 0;
}

void __exit ourcleanupmodule(void)
{
    printk(KERN_ALERT "\n sample -1 Exit.... \n");
}

int sample1func()
{
    printk(KERN_ALERT "\n sample -1 Exported Function .... \n");
    return 0;
}
EXPORT_SYMBOL(sample1func);

module_init(ourinitmodule);
module_exit(ourcleanupmodule);
In the above kernel module(module - 1), samplefunc() is exported by this module. i.e It can be used by other kernel modules. For exporting a function/global variable
        
            1. It should not be "static"
            2. Explicitly exported using EXPORT_SYMBOL() macro

There are other variants of EXPORT_SYMBOL(). It is up to reader's interest.
http://lxr.free-electrons.com/source/include/linux/export.h#L68

Lets see how other kernel module(module -2) uses the exported samplefunc()

#include <linux/module.h>
#include <linux/kernel.h>

#include "CommonHeader.h"

MODULE_LICENSE("GPL");

int __init ourinitmodule(void)
{
    printk(KERN_ALERT "\n sample -2 init.... \n");
    /* Making two.c to depend on one.c */
    sample1func();
    return 0;
}

void __exit ourcleanupmodule(void)
{
    printk(KERN_ALERT "\n sample -2 Exit.... \n");
}

module_init(ourinitmodule);
module_exit(ourcleanupmodule);
Refer here for full code & Make files.
https://github.com/jeyaramvrp/kernel-module-programming/tree/master/depmod-export-sym-demo

module-2 which uses samplefunc() requires module-1 loaded already. Refer the following screenshot which explains the sequence of operation.


2. Device Driver Programming

        This chapter guides a kernel programmer to understand the device driver especially char device driver. By end of this chapter readers will have understanding of 

             * Writing a char device driver
             * Using char driver at user space

2.1  What is a device driver ?


       Device drivers are kernel modules specifically written to handle particular hardware. Since the kernel is responsible managing the hardware, piece of code is written to handle it. 

The Linux kernel provides APIs for writing the device driver so that the device seamlessly integrated in to our system. If the particular device is vendor specific(i.e Not available in market), mostly it needs driver to be written. Otherwise Linux kernel itself has lot of drivers for most of the devices available in market today.  

To make the kernel generic as much as possible, built-in drivers which is already written by kernel community is controlled by configurations. 

2.2  What are all the major types of device driver?

       The Linux kernel drivers can be categorized in to two major types
                       
                     * Character device driver
                     * Block device driver
    
Char device drivers usually deals with read/write byte on device. For example keyboard or mouse drivers. There is no much involvement of large data to handle unlike hard disk.

Block devices usually involves large data to handle such as hard disk, SD card.

The sources for character devices are kept in drivers/char/, and the sources for block devices are kept in drivers/block/. They have similar interfaces, and are very much alike, except for reading and writing. Block devices will be covered later.
   
2.3  Character device driver

       This section will explain Char device drivers with a sample.  

2.3.1 Major and Minor Number 
       
       The Linux kernel refers each driver with a number called Major number. This major number is either allocated by kernel or specified by driver developer. While the developer specifies the major number, he should be careful enough to use unused number.


Refer http://lxr.free-electrons.com/source/include/uapi/linux/major.h for already allocated major numbers.

Minor numbers represents the devices. For example, if two devices of same type is connected to system(say two MS USB mouse), then each device will be assigned a number. But these two devices will be handled by single driver.

Lets see the following screen for clearing understanding of Minor and Major number.


The Linux kernel assigns 4 as the Major number for TTY driver(Refer Here). 
So each tty device is assigned with unique minor number.

Did you notice each row starts with "c" ??. This is meant for char driver. For block driver it will be "b".

2.3.2 file_operations structure

The file_operations structure is define in <linux/fs.h>. This structure is a collection of function pointers which is implemented in our drivers.

In the following driver example, only minimal operations are implemented. Just have a look at how the file operation is filled. At the end of example, you will get idea of file_operations struct.

static const struct file_operations sample_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = sample_ioctl,
        .open           = sample_open,
        .release        = sample_close 
};  
2.3.3 Register and un-register device driver
The Linux kernel provides APIs for register and un register of your driver. In this registration, driver obtains major number and the list of operations supported by driver will be intimated to kernel.


major = register_chrdev(0, "samplechar", &sample_fops);
The first argument as 0 allows the kernel to allocate a major number which is available. If you want specific number, pass here instead of 0. If that number is not allocated already then it will be assigned to this driver. Best practice is to pass 0.

Second argument represents the driver name which is displayed in /proc/devices

Third argument is the pointer to file_operations structure which we filled earlier. 

Usually this registration is done at the init function of kernel module(i.e Driver's init module)

The un register operation is done at clean up function.
unregister_chrdev(major, "samplechar");
The major number and driver name is need for un register.
2.3.4 ioctl()
ioctl() is very important function in device driver.  The commands specific to devices are issued to driver through ioctls. From user space, the command is received at ioctl() and the corresponding action will be taken by driver.

Lets write a calculator with device driver APIs to demonstrate. The ioctl() function will be similar to 


static long sample_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    switch(cmd) 
        {
        case SAMPLE_IOCTL_ADD:
        {
            // Addition related operation   
            return 0;
        }

        case SAMPLE_IOCTL_SUB:
        {
            // Subtraction related operation   
            return 0;
        }

        case SAMPLE_IOCTL_MUL:
        {
           //Multiplication related operation
            return 0;
        }

        case SAMPLE_IOCTL_DIV:
        {
           //Division related operation
            return 0;
        }
        default:
            return -EINVAL;
    }
}
Based on the ioctl command from user space, driver performs operations and returns results to user space if necessary. The functions copy_to_user() and copy_from_user() is responsible for copying the information to/from user space.
2.3.5 Sample driver with user space application 
Refer here for the sample char driver which implements basic calculator functionality.

After compiling the driver, insert in to kernel by using insmod.

# insmod sampledrv.ko

Create the device node of type "c" with major number(say 58) in /dev 

#mknod /dev/samplechar c 58 1

Now driver is ready to be used by applications. Lets write application which uses this calc driver.



#include <stdio.h> #include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "../sampleioctl.h"
int main()
{
struct calc dat;
int opt;
int fd, err=0;
fd = open("/dev/samplechar", O_RDWR);
if(fd < 0)
{
perror("open:");
return -1;
}
while(1)
{
printf("\n 1.Add\n2.Sub\n3.Mul\n4.Div\n \
5.ExitApp\nChoose Operation:" );
scanf("%d", &opt);
if(opt == 5)
break;
if(opt < 1 || opt > 4)
{
printf("\n Invalid Option... ");
continue;
}
printf("\n Enter Data1:");
scanf("%d", &dat.data1);
printf("\n Enter Data2:");
scanf("%d", &dat.data2);
switch(opt)
{
case 1:
err=ioctl(fd, SAMPLE_IOCTL_ADD, &dat);
break;
case 2:
err=ioctl(fd, SAMPLE_IOCTL_SUB, &dat);
break;
case 3:
err=ioctl(fd, SAMPLE_IOCTL_MUL, &dat);
break;
case 4:
err= ioctl(fd, SAMPLE_IOCTL_DIV, &dat);
break;
}
printf("\n Result is :%d", dat.result);
}
close(fd);
return 0;
}
   

Based on user's selection, the application fills the operands in structure and send to kernel for processing through ioctl(). Since the sample driver is registered for that device node, the ioctl() in driver will be invoked.

The ioctl() in kernel performs the corresponding operation and returns the result to the user space application. 

2.3.6 Wrappers for drivers in user space  
Usually driver developers will hide the complexity of opening device file, remembering the ioctl commands. If the application is developed by third party, it is better to write a wrapper which exposes only functionality.

The wrapper is written as shared library(.so) which takes care of handling driver related stuff and exposes only functionality.

The following is the source code for shard library.



#include <stdio.h> #include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include "../sampleioctl.h"
#include "mylib.h"
int fd;
struct calc dat;
int OpenDev()
{
fd = open("/dev/samplechar", O_RDWR);
if(fd < 0)
{
perror("open:");
return -1;
}
return 0;
}
int Add(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err=ioctl(fd, SAMPLE_IOCTL_ADD, &dat);
if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int Sub(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err=ioctl(fd, SAMPLE_IOCTL_SUB, &dat);
if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int Mul(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err=ioctl(fd, SAMPLE_IOCTL_MUL, &dat);if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int Div(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err= ioctl(fd, SAMPLE_IOCTL_DIV, &dat);
if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int CloseDev()
{
close(fd);
return 0;
}
        
Use the following commands to generate the shared library.
gcc -fPIC -g -c -Wall mylib.c
gcc -shared -W1,-soname, -o libmylib.so mylib.o -lc
Then link this library with the application.
Here is the sample application which uses this library.



#include <stdio.h> #include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "../mylib.h"
int main()
{
int data1, data2;
int opt;
int err=0;
err = OpenDev();
if(err < 0)
{
printf("\n open: Error in file open... ");
return -1;
}
while(1)
{
printf("\n 1.Add\n2.Sub\n3.Mul\n4.Div\n \
5.ExitApp\nChoose Operation:" );
scanf("%d", &opt);
if(opt == 5)
break;
if(opt < 1 || opt > 4)
{
printf("\n Invalid Option... ");
continue;
}
printf("\n Enter Data1:");
scanf("%d", &data1);
printf("\n Enter Data2:");
scanf("%d", &data2);
switch(opt)
{
case 1:
err = Add(data1, data2);
break;
case 2:
err=Sub(data1, data2);
break;
case 3:
err= Mul(data1, data2);
break;
case 4:
err= Div(data1, data2);
break;
}
printf("\n Result is :%d", err);
}
CloseDev();
return 0;
}

Just compare the app with wrapper and with out wrapper. This gives you some clear understanding of creating wrapper for drivers.

Here is the git hub which provides all necessary make files and source/header for execution & testing.