Difference between revisions of "EBC Exercise 26 Device Drivers"

From eLinux.org
Jump to: navigation, search
m (Inserting your module: Added)
m (Part 2: A Character Device: Updating for 5.10 kernel)
 
(15 intermediate revisions by 2 users not shown)
Line 2: Line 2:
 
{{YoderHead}}
 
{{YoderHead}}
  
Chapter 8 of the text [http://www.amazon.com/Embedded-Linux-Primer-Practical-Real-World/dp/0137017839] gives a nice example of a minimal device driver.  The purpose of this lab is to implement that driver.
+
Derek Molloy's excellent book Exploring BeagleBone [http://exploringbeaglebone.com/] has an Extra Content section [http://exploringbeaglebone.com/kernelprogramming/] on Linux Kernel Programming.  Part 1 [http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/] is a nice example of a writing a minimal kernel module.
  
== Minimal Device Driver Example ==
+
Here are instructions for compiling the example on the Bone.
  
=== Compiling ===
+
== Cloning Source and Compiling ==
Since you are going to make several changes, it's a good idea to use git to help keep track of them. First check the current status
+
You need to load the correct kernel headers on the bone before you can compile the driver.
host$ '''git status'''
 
# On branch v3.8.13-bone28
 
nothing to commit (working directory clean)
 
Good, so far no changes have been made. Now create a new branch.
 
host$ '''git checkout -b hello1'''
 
host$ '''git status'''
 
# On branch hello1
 
nothing to commit (working directory clean)
 
  
This creates a new branch called '''hello1''' and checks it out.
+
bone$ '''time sudo apt install linux-headers-`uname -r`'''
  
Follow the 5 steps given in Section 8.1.4 on page 205. Hint: details are given in the pages that follow 205. '''exercises/modules''' has a copy of hello1.c.  Once finished you will have a file called <code>hello1.c</code> in <code>.../drivers/char/examples</code> and have the kernel configure file and Makefile updated for the new driver.  See Section 4.4 on page 89 for help with modifying the config files.
+
Note: Those are back quotes (top left on the keyboard, above the TAB key) around '''uname -r'''.  
  
Note: There is a typo in Listing 8-2.
+
The '''uname''' command looks up the number of the kernel that's currently running. The back quotes
 +
take that number and past it after '''linux-headers-''' and does an '''apt install''' on it.  A couple minutes later you have all the headers loaded.
  
Assuming you have done the steps from the book, check git's status
+
Now load the examples.
host$ '''cd linux-dev/KERNEL/drivers/char/'''
 
host$ '''git status'''
 
# On branch hello1
 
# Changes not staged for commit:
 
#  (use "git add <file>..." to update what will be committed)
 
#  (use "git checkout -- <file>..." to discard changes in working directory)
 
#
 
# modified:  Kconfig
 
# modified:  Makefile
 
#
 
# Untracked files:
 
#  (use "git add <file>..." to include in what will be committed)
 
#
 
# examples/
 
no changes added to commit (use "git add" and/or "git commit -a")
 
Now add the files you just edited/created.
 
host$ '''git add Kconfig Makefile examples/Makefile examples/hello1.c
 
host$ '''git status'''
 
# On branch hello1
 
# Changes to be committed:
 
#  (use "git reset HEAD <file>..." to unstage)
 
#
 
# modified:  Kconfig
 
# modified:  Makefile
 
# new file:  examples/Makefile
 
# new file:  examples/hello1.c
 
#
 
host$ '''git commit -m "Files for hello1 kernel module"'''
 
[hello1 99346d5] Files for hello1 kernel module
 
  4 files changed, 33 insertions(+)
 
  create mode 100644 drivers/char/examples/Makefile
 
  create mode 100644 drivers/char/examples/hello1.c
 
  
If you have created the '''crossCompileEnv.sh''' file and sourced it, all you have to do to make the modules is cd to the top of the kernel directory and then:
+
bone$ '''git clone https://github.com/derekmolloy/exploringBB.git'''
  
host$ '''cd ''Your path''/linix-dev/KERNEL'''
+
Now you are ready to run the examples.
host$ '''source ~/crossCompileEnv.sh'''  (Only need to run once per terminal session.)
 
host$ '''make modules'''  (or make -j''X'' modules, where ''X'' = number of cores on host.)
 
  
Mine took a while the first time as it compiles all the modules. The second it only took 34 seconds.
+
== Part 1: Introduction - Minimal Device Driver Example ==
 +
Change to the correct directory and make.
  
=== Moving to Beagle ===
+
  bone$ '''cd exploringBB/extras/kernel/hello'''
Here are two choices for moving your module to the Beagle.  The first is the complete way, the second is the fast way.
+
bone$ '''make'''
 
+
make -C /lib/modules/5.10.145-ti-r55/build/ M=/home/debian/exploringBB/extras/kernel/hello modules
==== Option 1, copy everything ====
+
make[1]: Entering directory '/usr/src/linux-headers-5.10.145-ti-r55'
If you want to install all the modules that are created
+
  CC [M]  /home/Debian/exploringBB/extras/kernel/hello/hello.o
 
+
  MODPOST /home/Debian/exploringBB/extras/kernel/hello/Module.symvers
  host$ '''make INSTALL_MOD_PATH=~/BeagleBoard modules_install'''
+
  CC [M]  /home/Debian/exploringBB/extras/kernel/hello/hello.mod.o
 
+
  LD [M]  /home/Debian/exploringBB/extras/kernel/hello/hello.ko
This will create '''lib''' directory in '''~/BeagleBoard''' with everything that goes in '''/lib''' on the Beagle. Then
+
make[1]: Leaving directory '/usr/src/linux-headers-5.10.145-ti-r55'
 
+
  host$ '''rsync --progress -avhe ssh ~/BeagleBoard/lib root@beagle:/'''
+
real 0m11.890s
 
+
user 0m7.320s
Could take a while to transfer.
+
sys 0m3.373s
 +
  bone$ '''ls'''
 +
hello.c  hello.mod    hello.mod.o  Makefile      Module.symvers
 +
hello.ko  hello.mod.c  hello.o      modules.order
  
==== Option 2, copy just what you need ====
+
Your newly compiled kernel module is in '''hello.ko'''.
Here's a faster way, just copy the new file you created
 
host$ '''scp …/drivers/char/examples/hello1.ko root@bone:.'''
 
On the Beagle
 
beagle$ '''cd /lib/modules/3.8.13-bone28/kernel/drivers/char/'''
 
beagle$ '''mkdir examples'''
 
beagle$ '''cd examples'''
 
beagle$ '''mv ~/hello1.ko  .'''
 
Now build a new dependencies file
 
beagle$ '''depmod -a'''
 
  
I suggest putting the <code>scp</code> command in the <code>Makefile</code> since you may use it several times while developing your code.
+
=== Inserting your module ===
  
==== Inserting your module ====
 
 
See if your module is there
 
See if your module is there
  beagle$ '''modinfo hello1'''
+
  bone$ '''modinfo hello.ko'''
  filename:      /lib/modules/3.8.13-bone28/kernel/drivers/char/hello1.ko
+
  filename:      /home/debian/exploringBB/extras/kernel/hello/hello.ko
 +
version:        0.1
 +
description:    A simple Linux driver for the BBB.
 +
author:        Derek Molloy
 
  license:        GPL
 
  license:        GPL
description:    Hello World Example
+
  srcversion:    0DD9FE0DE42157F9221E608
author:        Chris Hallinan
 
  srcversion:    602AF803EAAB89A5CB5DAD7
 
 
  depends:         
 
  depends:         
  intree:         Y
+
  name:           hello
  vermagic:      3.8.13+ SMP mod_unload modversions ARMv7 p2v8  
+
  vermagic:      5.10.145-ti-r55 SMP preempt mod_unload modversions ARMv7 p2v8  
That looks good, now modprobe the module and check the log file.
+
parm:          name: The name to display in /var/log/kern.log (charp)
 +
 
 +
That looks good, now '''insmod''' the module and check the log file.
 +
 
 +
bone$ '''sudo insmod hello.ko'''
 +
bone$ dmesg -H | tail -1
 +
[  +2.857480] EBB: Hello world from the BBB LKM!
  
beagle$ '''modprobe hello1'''
 
beagle$ dmesg | tail -4
 
[    9.106206] snd-usb-audio 1-1:1.0: usb_probe_interface
 
[    9.106244] snd-usb-audio 1-1:1.0: usb_probe_interface - got id
 
[    9.813239] usbcore: registered new interface driver snd-usb-audio
 
[  109.308551] Hello Example Init
 
 
You should see your Init message.  And then...
 
You should see your Init message.  And then...
  beagle$ '''rmmod hello1'''
+
  bone$ '''sudo rmmod hello'''
  beagle$ '''dmesg | tail -4'''
+
  bone$ '''dmesg -H | tail -2'''
  [   9.106244] snd-usb-audio 1-1:1.0: usb_probe_interface - got id
+
  [  +4.182591] EBB: Hello world from the BBB LKM!
  [    9.813239] usbcore: registered new interface driver snd-usb-audio
+
  [  +3.542350] EBB: Goodbye world from the BBB LKM!
[  109.308551] Hello Example Init
 
  [  241.037368] Hello Example Exit
 
  
 
should show your Exit message.
 
should show your Exit message.
  
=== Documenting your work with a patch file ===
+
=== Passing Parameters ===
If you set up a branch on git as shown above, capturing all your changes in a patch file is easyFirst be sure you have checked in everything.
+
You can pass parameters when inserting a module.   
host$ '''cd linux-dev/KERNEL/drivers/char'''
 
host$ '''git status'''
 
# On branch hello1
 
nothing to commit (working directory clean)
 
Good, everything is committed. Now just
 
host$ '''git format-patch v3.8.13-bone28 --stdout > hello1.patch'''
 
This creates a file, '''hello1.patch''', that captures all the changes you made in installing your hello1.c module. [http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git/ how-to-create-and-apply-a-patch-with-git] shows how someone can take your patch file and apply it to their kernel, therefore getting your hello1.c kernel module.
 
  
== Improvements to the Code in the Book==
+
bone$ '''sudo insmod hello.ko name="Prof.Yoder"'''
The code in Listing 8-10 is incomplete. If you compile it as is and load the module, it will work, but if you then remove the module and try to reinsert it it will fail with an error like this:
+
bone$ '''dmesg -H | tail -1'''
<pre>
+
  [Sep24 16:53] EBB: Hello Prof.Yoder from the BBB LKM!
ERROR GOES HERE
+
bone$ '''sudo rmmod hello'''
</pre>
+
  bone$ '''dmesg -H | tail -2'''
The reason for this is that the module is registered with the kernel on load with a command like this:
+
[Sep24 16:53] EBB: Hello Prof.Yoder from the BBB LKM!
<pre>
+
  [Sep24 16:54] EBB: Goodbye Prof.Yoder from the BBB LKM!
register_chrdev(MAJOR_NUMBER, NAME, FILE_OPERATIONS*);
 
</pre>
 
But it is never unregistered when the module is unloaded. To properly unload the module, add this line of code to your module's exit function
 
<pre>
 
unregister_chrdev(MAJOR_NUMBER, NAME);
 
</pre>
 
This will properly unregister the module from the kernel and allow it to be inserted and removed from the kernel at will without restarting your system in between.
 
<br /><br />Listing 8-10 also uses the ioctl field in the file_operations struct. Newer kernels have removed this. If the code from the listing complains about ioctl being an unknown field, use unlocked_ioctl in its place:
 
<pre>
 
struct file_operations hello_fops = {
 
    owner:          THIS_MODULE,
 
    read:            hello_read,
 
    write:          hello_write,
 
    unlocked_ioctl: hello_ioctl,
 
    open:           hello_open,
 
    release:         hello_release,
 
};
 
</pre>
 
  
== Driver Methods ==
+
== Part 2: A Character Device ==
  
Section 8.3 on page 217 gives a longer example of how to use the file interface with modules.  Implement the example. Be sure to fix the ''unsigned int'' format error, and make sure your <code>exit</code> function unregisters the device (unlike the Listing)When compiling the <code>use-hello</code> command be sure you are using the cross compiler for the ARM rather than the x86 compiler.  If you sourced the '''crossCompilerEnv.sh''' file this should work:
+
Part 2 of Molloy's example [http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/] is a character device.  The code needs a slight change before it will run.
  
  host$ '''${CROSS_COMPILE}gcc use-hello.c -o use-hello'''
+
  bone$ '''cd exploringBB/extras/kernel/ebbchar'''
host$ '''file use-hello'''
 
  
The <code>file</code> command will tell you if you got the right compiler. Modify your <code>Makefile</code> to make <code>use-hello</code>.
+
Edit '''ebbchar.c''' and make the following changes.
  
Some questions...
+
bone$ '''git diff ebbchar.c'''
* The major device number 234 is part of a range of unassigned numbers. What is the range?
+
diff --git a/extras/kernel/ebbchar/ebbchar.c b/extras/kernel/ebbchar/ebbchar.c
* What's the new line added to <code>hello_init</code> do?
+
index 771b859..f869859 100644
* What does <code>mknod</code> do?
+
--- a/extras/kernel/ebbchar/ebbchar.c
* Once your device is running try <code>$ cat /proc/devices</code>. Do you see your device?
+
+++ b/extras/kernel/ebbchar/ebbchar.c
 +
@@ -142,7 +142,9 @@ static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *of
 +
  * @param offset The offset if required
 +
  */
 +
  static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
 +
-  sprintf(message, "%s(%zu letters)", buffer, len);  // appending received string with its length
 +
+  unsigned long ret;
 +
+  // sprintf(message, "%s(%zu letters)", buffer, len);  // appending received string with its length
 +
+  ret = copy_from_user(message, buffer, len);
 +
  +  size_of_message = len;                // store the length of the stored message
 +
    printk(KERN_INFO "EBBChar: Received %zu characters from the user\n", len);
 +
    return len;
  
=== Optional Driver Work ===
+
Add the lines starting with '''+''' and comment out the '''sprintf''' starting with '''-'''.
  
Chapter 3 of ''Linux Device Drivers'' by Corbet, Rubini and Kroah-Hartman ([http://www.rose-hulman.edu/~yoder/Beagle/]) gives some more details on device drivers. Our text uses an older, static, method for major device number allocationThe book, referenced above, uses the newer dynamic allocation.   
+
Now make and insert.
* Convert the example in our text to use the newer method. It's only a couple of additional lines, but you will have to read the book to know how to do it.   
+
 
* Modify the ''scull_load'' script (call it ''hello_load'') on page 47, of chapter 3, to load your moduleHintthe back quotes are missing in this line in the text:
+
bone$ '''make'''
 +
make -C /lib/modules/5.10.145-ti-r55/build/ M=/home/debian/exploringBB/extras/kernel/ebbchar modules
 +
make[1]: Entering directory '/usr/src/linux-headers-5.10.145-ti-r55'
 +
  CC [M]  /home/Debian/exploringBB/extras/kernel/ebbchar/ebbchar.o
 +
  MODPOST /home/Debian/exploringBB/extras/kernel/ebbchar/Module.symvers
 +
  CC [M] /home/Debian/exploringBB/extras/kernel/ebbchar/ebbchar.mod.o
 +
  LD [M] /home/Debian/exploringBB/extras/kernel/ebbchar/ebbchar.ko
 +
  make[1]: Leaving directory '/usr/src/linux-headers-5.10.145-ti-r55'
 +
  cc testebbchar.c -o test
 +
   
 +
bone$ '''sudo insmod ebbchar.ko'''
 +
bone$ '''dmesg -H | tail -4
 +
[Sep24 17:08] EBBChar: Initializing the EBBChar LKM
 +
[  +0.011910] EBBChar: registered correctly with major number 241
 +
  [  +0.016113] EBBChar: device class registered correctly
 +
[ +0.010024] EBBChar: device class created correctly
  
  major=`awk "\\$2==\"$module\" {print \\$1}" /proc/devices)`
+
Now test it.
 +
  bone$ '''sudo ./test'''
 +
Starting device test code example...
 +
Type in a short string to send to the kernel module:
 +
'''This is a test!'''
 +
Writing message to the device [This is a test!].
 +
Press ENTER to read back from the device...
 
   
 
   
* Test it with <code>use-hello.c</code> from page 222 of ''Embedded Linux Primer''.
+
Reading from the device...
* Write a ''hello_unload'' script that will rmmod the driver and remove the nodes in /dev
+
The received message is: [This is a test!]
* Modify ''hello.c'' to pass the major device number in as a parameter during <code>insmod</code>.
+
End of the program
 +
bone$ '''dmesg -H | tail -8'''
 +
[Sep24 17:08] EBBChar: Initializing the EBBChar LKM
 +
[  +0.011910] EBBChar: registered correctly with major number 241
 +
[  +0.016113] EBBChar: device class registered correctly
 +
[  +0.010024] EBBChar: device class created correctly
 +
[Sep24 17:09] EBBChar: Device has been opened 1 time(s)
 +
[  +9.333771] EBBChar: Received 15 characters from the user
 +
[  +1.185798] EBBChar: Sent 15 characters to the user
 +
[  +0.009778] EBBChar: Device successfully closed
  
Optional: Stretch time, I though these would be easy, but after reading up on them, they look rather involved.
+
Look over '''ebbchar.c''' and '''testebbchar.c''' to see how the user space and the kernel interact.
* How can your driver find what the minor device number is?
 
* Modify the driver to return some characters when <code>/dev/hello1</code> is read.
 
  
 
== Reference ==
 
== Reference ==

Latest revision as of 09:48, 26 December 2022

thumb‎ Embedded Linux Class by Mark A. Yoder


Derek Molloy's excellent book Exploring BeagleBone [1] has an Extra Content section [2] on Linux Kernel Programming. Part 1 [3] is a nice example of a writing a minimal kernel module.

Here are instructions for compiling the example on the Bone.

Cloning Source and Compiling

You need to load the correct kernel headers on the bone before you can compile the driver.

bone$ time sudo apt install linux-headers-`uname -r`

Note: Those are back quotes (top left on the keyboard, above the TAB key) around uname -r.

The uname command looks up the number of the kernel that's currently running. The back quotes take that number and past it after linux-headers- and does an apt install on it. A couple minutes later you have all the headers loaded.

Now load the examples.

bone$ git clone https://github.com/derekmolloy/exploringBB.git

Now you are ready to run the examples.

Part 1: Introduction - Minimal Device Driver Example

Change to the correct directory and make.

bone$ cd exploringBB/extras/kernel/hello
bone$ make
make -C /lib/modules/5.10.145-ti-r55/build/ M=/home/debian/exploringBB/extras/kernel/hello modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.145-ti-r55'
  CC [M]  /home/Debian/exploringBB/extras/kernel/hello/hello.o
  MODPOST /home/Debian/exploringBB/extras/kernel/hello/Module.symvers
  CC [M]  /home/Debian/exploringBB/extras/kernel/hello/hello.mod.o
  LD [M]  /home/Debian/exploringBB/extras/kernel/hello/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.145-ti-r55'

real	0m11.890s
user	0m7.320s
sys	0m3.373s
bone$ ls
hello.c   hello.mod    hello.mod.o  Makefile       Module.symvers
hello.ko  hello.mod.c  hello.o      modules.order

Your newly compiled kernel module is in hello.ko.

Inserting your module

See if your module is there

bone$ modinfo hello.ko
filename:       /home/debian/exploringBB/extras/kernel/hello/hello.ko
version:        0.1
description:    A simple Linux driver for the BBB.
author:         Derek Molloy
license:        GPL
srcversion:     0DD9FE0DE42157F9221E608
depends:        
name:           hello
vermagic:       5.10.145-ti-r55 SMP preempt mod_unload modversions ARMv7 p2v8 
parm:           name: The name to display in /var/log/kern.log (charp)

That looks good, now insmod the module and check the log file.

bone$ sudo insmod hello.ko
bone$ dmesg -H | tail -1

[ +2.857480] EBB: Hello world from the BBB LKM!

You should see your Init message. And then...

bone$ sudo rmmod hello
bone$ dmesg -H | tail -2
[  +4.182591] EBB: Hello world from the BBB LKM!
[  +3.542350] EBB: Goodbye world from the BBB LKM!

should show your Exit message.

Passing Parameters

You can pass parameters when inserting a module.

bone$ sudo insmod hello.ko name="Prof.Yoder"
bone$ dmesg -H | tail -1
[Sep24 16:53] EBB: Hello Prof.Yoder from the BBB LKM!
bone$ sudo rmmod hello
bone$ dmesg -H | tail -2
[Sep24 16:53] EBB: Hello Prof.Yoder from the BBB LKM!
[Sep24 16:54] EBB: Goodbye Prof.Yoder from the BBB LKM!

Part 2: A Character Device

Part 2 of Molloy's example [4] is a character device. The code needs a slight change before it will run.

bone$ cd exploringBB/extras/kernel/ebbchar

Edit ebbchar.c and make the following changes.

bone$ git diff ebbchar.c
diff --git a/extras/kernel/ebbchar/ebbchar.c b/extras/kernel/ebbchar/ebbchar.c
index 771b859..f869859 100644
--- a/extras/kernel/ebbchar/ebbchar.c
+++ b/extras/kernel/ebbchar/ebbchar.c
@@ -142,7 +142,9 @@ static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *of
  *  @param offset The offset if required
  */
 static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
-   sprintf(message, "%s(%zu letters)", buffer, len);   // appending received string with its length
+   unsigned long ret;
+   // sprintf(message, "%s(%zu letters)", buffer, len);   // appending received string with its length
+   ret = copy_from_user(message, buffer, len);
+   size_of_message = len;                 // store the length of the stored message
    printk(KERN_INFO "EBBChar: Received %zu characters from the user\n", len);
    return len;

Add the lines starting with + and comment out the sprintf starting with -.

Now make and insert.

bone$ make
make -C /lib/modules/5.10.145-ti-r55/build/ M=/home/debian/exploringBB/extras/kernel/ebbchar modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.145-ti-r55'
  CC [M]  /home/Debian/exploringBB/extras/kernel/ebbchar/ebbchar.o
  MODPOST /home/Debian/exploringBB/extras/kernel/ebbchar/Module.symvers
  CC [M]  /home/Debian/exploringBB/extras/kernel/ebbchar/ebbchar.mod.o
  LD [M]  /home/Debian/exploringBB/extras/kernel/ebbchar/ebbchar.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.145-ti-r55'
cc testebbchar.c -o test

bone$ sudo insmod ebbchar.ko
bone$ dmesg -H | tail -4
[Sep24 17:08] EBBChar: Initializing the EBBChar LKM
[  +0.011910] EBBChar: registered correctly with major number 241
[  +0.016113] EBBChar: device class registered correctly
[  +0.010024] EBBChar: device class created correctly

Now test it.

bone$ sudo ./test
Starting device test code example...
Type in a short string to send to the kernel module:
This is a test!
Writing message to the device [This is a test!].
Press ENTER to read back from the device...

Reading from the device...
The received message is: [This is a test!]
End of the program
bone$ dmesg -H | tail -8
[Sep24 17:08] EBBChar: Initializing the EBBChar LKM
[  +0.011910] EBBChar: registered correctly with major number 241
[  +0.016113] EBBChar: device class registered correctly
[  +0.010024] EBBChar: device class created correctly
[Sep24 17:09] EBBChar: Device has been opened 1 time(s)
[  +9.333771] EBBChar: Received 15 characters from the user
[  +1.185798] EBBChar: Sent 15 characters to the user
[  +0.009778] EBBChar: Device successfully closed

Look over ebbchar.c and testebbchar.c to see how the user space and the kernel interact.

Reference

How to Write and Submit a Linux Kernel Patch




thumb‎ Embedded Linux Class by Mark A. Yoder