Deploying Your First Driver
This chapter describes how to write a "hello world" driver on the Pico-G1 board and expose it as a user-space interface that an application can call, completing the full flow from driver writing to building to invocation. This chapter is based on Deploying Your First Application. Please read Deploying Your First Application first.
Driver execution flow:
- When the module is loaded, hellodrv_init is executed.
- misc_register() is called to register the device.
- The kernel automatically creates /dev/hellodrv.
- The user-space program opens /dev/hellodrv.
- The user space sends commands to the driver via ioctl.
- The driver returns data to user space via copy_to_user().
- When the module is unloaded, hellodrv_exit is executed.
- misc_deregister() is called to deregister the device.
Modify the Application
Refer to Deploying Your First Application and change helloworld.c to the following form, then build and flash as described.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define HELLODRV_MAGIC 'H'
#define HELLODRV_GET_MSG _IOR(HELLODRV_MAGIC, 0x01, char *)
int main(void)
{
int fd;
char buf[64] = {0};
printf("hello world from firmware!\n");
fd = open("/dev/hellodrv", O_RDWR);
if (fd < 0) {
perror("open /dev/hellodrv failed");
return -1;
}
if (ioctl(fd, HELLODRV_GET_MSG, buf) < 0) {
perror("ioctl HELLODRV_GET_MSG failed");
close(fd);
return -1;
}
printf("message from driver: %s\n", buf);
close(fd);
return 0;
}Create a New Driver
# Run from the SDK root directory:
cd source/kernel/linux-5.10.y/drivers/misc
mkdir hellodrv
cd hellodrv
touch hellodrv.c
touch Makefile
touch KconfigNote
linux-5.10.yis determined by Linux System --> Kernel --> Kernel Version in the menuconfig. If you configured the 4.9 version, replace it withlinux-4.9.y.- The misc driver type is the "miscellaneous device driver" in Linux, suitable for small drivers that do not belong to a specific subsystem but still need to provide a /dev interface. It is usually simpler than a full character device driver and is well suited for getting started and for verification.
Write the Driver
Write hellodrv.c
- Register a device in the kernel.
- Generate a device node on the board, for example /dev/hellodrv.
- Let the user-space program helloworld communicate with the driver through this device node.
- First implement a minimal feature: read a fixed string from the driver via ioctl.
ioctl
ioctl is short for I/O control — an input/output control interface. It is one of the ways user-space programs and drivers communicate in Linux.
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/device.h>
/*
* Driver name.
* The device node created under /dev is /dev/hellodrv.
*/
#define HELLODRV_NAME "hellodrv"
/*
* ioctl magic number.
* Used to distinguish the ioctl commands of different drivers and avoid conflicts.
*/
#define HELLODRV_MAGIC 'H'
/*
* ioctl command: get a string from the driver.
* _IOR means: the user space reads data from the kernel.
* The third parameter is the data type, here char *.
*/
#define HELLODRV_GET_MSG _IOR(HELLODRV_MAGIC, 0x01, char *)
/*
* String the driver returns to user space.
* Hard-code a fixed message here for easy verification of user/kernel communication.
*/
static const char *g_msg = "hello from driver";
/*
* Callback when the device is opened.
* Entered when user space calls open("/dev/hellodrv", ...).
*/
static int hellodrv_open(struct inode *inode, struct file *filp)
{
pr_info("hellodrv: device opened\n");
return 0;
}
/*
* Callback when the device is closed.
* Entered when user space calls close(fd).
*/
static int hellodrv_release(struct inode *inode, struct file *filp)
{
pr_info("hellodrv: device closed\n");
return 0;
}
/*
* ioctl callback.
* User-space programs can interact with the driver through ioctl(fd, cmd, arg).
*
* cmd: command passed in from user space.
* arg: argument passed in from user space, usually a user-space address.
*/
static long hellodrv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
char msg[64];
switch (cmd) {
case HELLODRV_GET_MSG:
/*
* Clear the temporary buffer first.
*/
memset(msg, 0, sizeof(msg));
/*
* Copy the fixed string to the local buffer.
*/
snprintf(msg, sizeof(msg), "%s", g_msg);
/*
* Copy data from the kernel to user space.
* You must not pass a kernel address directly to user space; use copy_to_user.
*/
if (copy_to_user((void __user *)arg, msg, strlen(msg) + 1))
return -EFAULT;
pr_info("hellodrv: ioctl GET_MSG\n");
return 0;
default:
/*
* Unknown command; return an invalid-argument error.
*/
return -EINVAL;
}
}
/*
* File operations table.
* The kernel uses it to know which operations the device supports.
*/
static const struct file_operations hellodrv_fops = {
.owner = THIS_MODULE,
.open = hellodrv_open,
.release = hellodrv_release,
.unlocked_ioctl = hellodrv_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = hellodrv_ioctl,
#endif
};
/*
* misc device structure.
* After registration via misc_register, /dev/hellodrv is created automatically.
*/
static struct miscdevice hellodrv_miscdev = {
.minor = MISC_DYNAMIC_MINOR, /* Dynamically allocate the minor number */
.name = HELLODRV_NAME, /* Device node name */
.fops = &hellodrv_fops, /* Bind the file operations set */
.mode = 0666, /* Device node permissions: readable and writable */
};
/*
* Module initialization function.
* Executed when the driver is loaded.
*/
static int __init hellodrv_init(void)
{
int ret;
ret = misc_register(&hellodrv_miscdev);
if (ret) {
pr_err("hellodrv: misc_register failed, ret=%d\n", ret);
return ret;
}
pr_info("hellodrv: module loaded, /dev/%s created\n", HELLODRV_NAME);
return 0;
}
/*
* Module exit function.
* Executed when the driver is unloaded.
*/
static void __exit hellodrv_exit(void)
{
misc_deregister(&hellodrv_miscdev);
pr_info("hellodrv: module unloaded\n");
}
/*
* Specify the module entry and exit.
*/
module_init(hellodrv_init);
module_exit(hellodrv_exit);
/*
* Module information.
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ljh");
MODULE_DESCRIPTION("Hello driver for helloworld app");Write the Makefile
obj-$(CONFIG_HELLODRV) += hellodrv.oWrite Kconfig
config HELLODRV
tristate "Hello driver"
help
A simple misc driver for user-space test.Modify the Parent Makefile and Kconfig
- In source/kernel/linux-5.10.y/drivers/misc/Makefile, add this line at a suitable location. It means: if CONFIG_HELLODRV is enabled, descend into the hellodrv/ directory to continue the build.
obj-$(CONFIG_HELLODRV) += hellodrv/ - In source/kernel/linux-5.10.y/drivers/misc/Kconfig, add this line at a suitable location. It means: there is a new configuration item in drivers/misc/hellodrv/Kconfig that should be sourced in.
source "drivers/misc/hellodrv/Kconfig"Build the Driver as a Kernel Module
- Configure the Hello Drv optionAdd
vim source/kernel/linux-5.10.y/arch/arm/configs/xmorca_defconfigCONFIG_HELLODRV=mat the end. Verify the configuration:grep CONFIG_HELLODRV source/kernel/linux-5.10.y/arch/arm/configs/xmorca_defconfig - Build the kernelConfirm the configuration took effect:
make linuxLocate the driver module:grep CONFIG_HELLODRV out/xm7206v12a/linux-5.10.y/.configfind out/xm7206v12a/linux-5.10.y -name "hellodrv.ko" - Install the driver module into rootfs
cd out/xm7206v12a/rootfs/lib/ mkdir -p modules/5.10.0/extr # Back at the SDK root directory, run: cp -f out/xm7206v12a/linux-5.10.y/drivers/misc/hellodrv/hellodrv.ko out/xm7206v12a/rootfs/lib/modules/5.10.0/extr/ # Verify the copy succeeded ls -l out/xm7206v12a/rootfs/lib/modules/5.10.0/extr/hellodrv.ko # Rebuild the image make fs_image - Flash the image. See Flashing chapter: Image Flashing to flash the SPI image.
Load the Driver Module
insmod lib/modules/5.10.0/extr/hellodrv.koRun the Application
/usr/bin/helloworldExpected Output
/ # insmod lib/modules/5.10.0/extr/hellodrv.ko
hellodrv: module loaded, /dev/hellodrv created
/ # /usr/bin/helloworld
hello world from firmware!
hellodrv: device opened
hellodrv: ioctl GET_MSG
message from driver: hello from driver
hellodrv: device closed
/ #