Documentation:NGW/KernelModule
From AVRFreaks Wiki
Contents |
[edit] Kernel Module
I am writting this as I go as this could take 2 to 3 month. Need to get a GPS track system up and running. 90 percent done, but it always the last 10 percent of debuging with takes most of the time
[edit] Why do I need to write a Kernel Module
If you are coming from an AVR 8-bit world then it will seem very strange that you cannot just directly access internal registers that control peripherals like a UART. When running Linux, a user program CANNOT write to the sections of memory that control such peripherals. The Kernel has exclusive control over such areas of memory.
You may be asking why not. Well the Linux system that is running on the AVR32 is a real linux kernel. Which means it comes from the same code base as that running your full Linux server. As you can imagine with a critical server, you do not what a user's program messing up the Linux kernel controlling the server because it has access to the internal registers which the kernel is trying to control.
I could go on with why but it better if you go to the following web site and read it all. Note that some of the code examples have errors, which I have fixed, I hope and put here. Note that some of the code is out of date. i.e. the /proc code looks like it been updated in the kernel. Which means the example code on the website will not work.
http://www.faqs.org/docs/kernel/index.html
[edit] What you need
- A PC running Linux with buildroot on it and known to be working i.e. you are able to compile your own Kernel (this takes some time).
- A NGW100. (I like to have TFTP and NFS set-up so I can change the code in the server and then reboot the NGW100 and see what happens or just move the file onto the nfs location and run it on the NGW100 with no FTP or messing about with progaming Flash).
- A Makefile and code see below
[edit] Make File
This is bassed of the code I found at the following web site. http://pastebin.ca/804215
Note that buildroot is always change so the paths to the CROSS_COMPLIE and KDIR may have moved. So if the makefile below does a find -name avr32-linux-gcc for the CROSS_COMPLIE and find -name linux-2.6* and look for the kernel you are using. This should be done from the buildroot directory.
You need the full path in the makefile. So you will need to edit it before using it. You do not have to be root to make the modules.
Makefile
# Makefile:
ARCH := avr32
CROSS_COMPILE := /home/user/buildroot/build_avr32/staging_dir/usr/bin/avr32-linux-
KDIR := /home/user/buildroot/project_build_avr32/atngw100/linux-2.6.24
PWD := $(shell pwd)
obj-m := testmodule.o
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
rm -rf testmodule.ko testmodule.o* testmodule.mod* .testmodule* .tmp* Module*
Note that you need to put a tab and NOT 8 space after default: and clean:. The make will tell you this if you just cut and passed as I can not put tab in wiki.
[edit] Hello World
You need to use the make file above and this file to make hello world module.
testmodule.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
int init_module(void)
{
printk("hello\n");
return 0;
}
void cleanup_module(void)
{
printk("world\n");
}
[edit] How to Make
I just use the command
make default
# make default make -C /home/user/buildroot/project_build_avr32/atngw100/linux-2.6.24 SUBDIRS=/home/user/ATNGW100 ARCH=avr32 CROSS_COMPILE=/home/user/buildroot/ build_avr32/staging_dir/usr/bin/avr32-linux- modules make[1]: Entering directory `/home/user/buildroot/project_build_avr32/atngw100/linux- 2.6.24' CC [M] /home/user/ATNGW100/testmodule.o Building modules, stage 2. MODPOST 1 modules CC /home/user/ATNGW100/testmodule.mod.o LD [M] /home/user/ATNGW100/testmodule.ko make[1]: Leaving directory `/home/user/buildroot/project_build_avr32/atngw100/linux-2.6.24'
you can do a make clean to clean up the directory when you are done. This will delete the output file as well.
Note that make will give you the following message and will NOT do any thing. So do a make default
make: Nothing to be done for `Makefile'.
[edit] Run Time
You need to FTP or move the testmodule.ko to the NGW100. It can go anywhere for now. I like putting it in /
Note you should be on the console and be login as root. I have had problem with the insmon command from a ssh console, but I think it fix now with 2.6.24.4.
You need to use the following commands lsmod, insmod and rmmod.
NOTE: The logs may only come out on the concole so it best to run it from there in the NGW100 case it the RS232 port.
Note that the kernel needs to match or you will get the following
~ # insmod ./testmodule.ko testmodule: version magic '2.6.24 mod_unload AVR32v1' should be '2.6.24.4 mod_unload AVR32v1' insmod: cannot insert './testmodule.ko': invalid module format
~ # lsmod Module Size Used by Not tainted xt_state 1856 1 iptable_filter 2112 1 ipt_MASQUERADE 2368 1 iptable_nat 4900 1 nf_nat 12278 2 ipt_MASQUERADE,iptable_nat nf_conntrack_ipv4 11048 3 iptable_nat nf_conntrack 42652 5 xt_state,ipt_MASQUERADE,iptable_nat,nf_nat,nf_conntrack_ipv4 ip_tables 8040 2 iptable_filter,iptable_nat configfs 17552 1
~ # insmod ./testmodule.ko
~ # lsmod Module Size Used by Tainted: P testmodule 928 0 xt_state 1856 1 iptable_filter 2112 1 ipt_MASQUERADE 2368 1 iptable_nat 4900 1 nf_nat 12278 2 ipt_MASQUERADE,iptable_nat nf_conntrack_ipv4 11048 3 iptable_nat nf_conntrack 42652 5 xt_state,ipt_MASQUERADE,iptable_nat,nf_nat,nf_conntrack_ipv4 ip_tables 8040 2 iptable_filter,iptable_nat configfs 17552 1
~ # rmmod testmodule.ko
~ # lsmod Module Size Used by Not tainted xt_state 1856 1 iptable_filter 2112 1 ipt_MASQUERADE 2368 1 iptable_nat 4900 1 nf_nat 12278 2 ipt_MASQUERADE,iptable_nat nf_conntrack_ipv4 11048 3 iptable_nat nf_conntrack 42652 5 xt_state,ipt_MASQUERADE,iptable_nat,nf_nat,nf_conntrack_ipv4 ip_tables 8040 2 iptable_filter,iptable_nat configfs 17552 1
# tail /var/log/messages Jul 10 19:12:24 ngw user.warn kernel: hello Jul 10 19:49:49 ngw user.warn kernel: world
[edit] Making a Device
OK time to make a device. You can use the same makefile just change the the code to testmodule.c file
testmodule.c
/* chardev.c: Creates a read-only char device that says how many times
* you've read from the dev file
*/
#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS)
#include <linux/modversions.h>
#define MODVERSIONS
#endif
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user */
/* Prototypes - this would normally go in a .h file
*/
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */
/* Global variables are declared as static, so are global within the file. */
static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open? Used to prevent multiple */
/* access to the device */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
/* Functions
*/
int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk ("Registering the character device failed with %d\n", Major);
return Major;
}
printk("<1>I was assigned major number %d. To talk to\n", Major);
printk("<1>the driver, create a dev file with\n");
printk("<1>'mknod /dev/hello c %d 0'.\n", Major);
printk("<1>Try various minor numbers. Try to cat and echo to\n");
printk("<1>the device file.\n");
printk("<1>Remove the device file and module when done.\n");
return 0;
}
void cleanup_module(void)
{
printk("Cleanup time\n");
/* Unregister the device */
unregister_chrdev(Major, DEVICE_NAME);
//int ret = unregister_chrdev(Major, DEVICE_NAME);
//if (ret < 0) printk("Error in unregister_chrdev: %d\n", ret);
}
/* Methods
*/
/* Called when a process tries to open the device file, like
* "cat /dev/mycharfile"
*/
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open) return -EBUSY;
Device_Open++;
sprintf(msg,"I already told you %d times Hello world!\n", counter++);
msg_Ptr = msg;
//MOD_INC_USE_COUNT;
return SUCCESS;
}
/* Called when a process closes the device file.
*/
static int device_release(struct inode *inode, struct file *file)
{
Device_Open --; /* We're now ready for our next caller */
/* Decrement the usage count, or else once you opened the file, you'll
never get get rid of the module. */
//MOD_DEC_USE_COUNT;
return 0;
}
/* Called when a process, which already opened the dev file, attempts to
read from it.
*/
static ssize_t device_read(struct file *filp,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
/* Number of bytes actually written to the buffer */
int bytes_read = 0;
/* If we're at the end of the message, return 0 signifying end of file */
if (*msg_Ptr == 0) return 0;
/* Actually put the data into the buffer */
while (length && *msg_Ptr) {
/* The buffer is in the user data segment, not the kernel segment;
* assignment won't work. We have to use put_user which copies data from
* the kernel data segment to the user data segment. */
put_user(*(msg_Ptr++), buffer++);
length--;
bytes_read++;
}
/* Most read functions return the number of bytes put into the buffer */
return bytes_read;
}
/* Called when a process writes to dev file: echo "hi" > /dev/hello */
static ssize_t device_write(struct file *filp,
const char *buff,
size_t len,
loff_t *off)
{
printk ("<1>Sorry, this operation isn't supported.\n");
return -EINVAL;
}
When you do a insmod testmodule.ko and look at the messages you will see it tells you do a mknod. This will make the device in the /dev directory
~ # insmod testmodule.ko I was assigned major number 253. To talk to the driver, create a dev file with 'mknod /dev/hello c 253 0'. Try various minor numbers. Try to cat and echo to the device file. Remove the device file and module when done.
Note that this time it printed out on the console as well as going to the message file. This is triggered by the <1> at the start of the printk.
i.e. printk("<1>I was assigned major number %d. To talk to\n", Major);
from the message logs
~ # tail /var/log/messages Jul 10 19:50:06 ngw user.alert kernel: I was assigned major number 253. To talk to Jul 10 19:50:06 ngw user.alert kernel: the driver, create a dev file with Jul 10 19:50:06 ngw user.warn kernel: 'mknod /dev/hello c 253 0'. Jul 10 19:50:06 ngw user.alert kernel: Try various minor numbers. Try to cat and echo to Jul 10 19:50:06 ngw user.warn kernel: the device file. Jul 10 19:50:06 ngw user.alert kernel: Remove the device file and module when done.
So make the nod
mknod /dev/hello c 253 0
~ # ls /dev/ console log mtd2 mtdblock2 rtc0 ttyS0 core mem mtd2ro mtdblock3 shm ttygserial fd mtd0 mtd3 null stderr urandom full mtd0ro mtd3ro ptmx stdin watchdog kmem mtd1 mtdblock0 pts stdout zero kmsg mtd1ro mtdblock1 random tty ~ # mknod /dev/hello c 253 0 ~ # ls /dev/ console kmsg mtd1ro mtdblock1 random tty core log mtd2 mtdblock2 rtc0 ttyS0 fd mem mtd2ro mtdblock3 shm ttygserial full mtd0 mtd3 null stderr urandom hello mtd0ro mtd3ro ptmx stdin watchdog kmem mtd1 mtdblock0 pts stdout zero
[edit] Time to try the device
Now to time to see if the device works
~ # cd /dev /dev # cat hello I already told you 0 times Hello world! /dev # cat hello I already told you 1 times Hello world! /dev # cat hello I already told you 2 times Hello world! /dev # cat hello I already told you 3 times Hello world!
And it works.
Note That is a read only deivce when you s a echo "hi"> /dev/hello you will get alart in the message file of
~ # echo "hi"> /dev/hello
~ # tail /var/log/messages Jul 10 19:55:57 ngw user.alert kernel: Sorry, this operation isn't supported. Jul 10 19:55:57 ngw user.alert kernel: Sorry, this operation isn't supported. Jul 10 19:55:57 ngw user.alert kernel: Sorry, this operation isn't supported.
Note you will get 3 lines, one for each char you sent. i.e. 'h', 'i' and '\n'
[edit] I will add the code for the write. It is coming. Need to test it.
Misc notes
As usual, /etc/init.d holds scripts which are executed during booting. These scripts have to be invoked explicitly from a file called rcS. Here is my init script:
insmod /media/mmcblk0p1/robodriver.ko
major=`grep freebird /proc/devices | awk '{print $1}'`
echo $major
mknod /tmp/freebird c $major 0
/media/mmcblk0p1/drive &
got this from http://avr32linux.org/twiki/bin/view/Main/PramodeCE
[edit] buildroot Rev
This Document is based on buildroot Rev 22403 (2.6.24.3) svn
$ svn info Path: . URL: svn://uclibc.org/trunk/buildroot Repository Root: svn://uclibc.org Repository UUID: 69ca8d6d-28ef-0310-b511-8ec308f3f277 Revision: 22403 Node Kind: directory Schedule: normal Last Changed Author: jacmet Last Changed Rev: 22402 Last Changed Date: 2008-06-17 09:33:02 -0400 (Tue, 17 Jun 2008)
