9_linux key experiment

Posted by roach on Fri, 14 Jan 2022 14:46:32 +0100

1, Create project

1. Add device tree node

1) Add device node

Create a device node under the root node:

gpioled {
		compatible = "gpioled_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl-0 = <&gpioled>;
		gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

key_input {
		compatible = "key_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl = <&keyinput>;
		gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
	};

2) Add pinctrl node

Add the pinctrl node imx6ul-evk to the child node under the iomuxc node:

gpioled: ledgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03   0x10b0	
			>;
		};

keyinput: keygrp {
			fsl,pins = <
				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18   0xf080
			>;
		};

2. Add device structure

/* Equipment structure */
struct key_dev {
	dev_t devid;			//Equipment number
	int major;				//Main equipment No
	int minor;				//Secondary equipment No
	struct class *class;	//class
	struct device *device;	//equipment
	struct cdev cdev;		//Character device	
	struct device_node *led_nd;	//led device tree node
	struct device_node *key_nd; //key device tree node
	int led_gpio;	//led gpio number
	int key_gpio;	//key gpio No
	int led_state;
	atomic_t key_val;	//Key value
};

struct key_dev key;

3. Write load and unload registration functions

The loading and unloading registration functions are as follows:

module_init(key_init);
module_exit(key_exit);

Entry function:

/* Entry function */
static int __init key_init(void)
{
	int ret = 0;

	/* Equipment number processing */
	key.major = 0;
	if(key.major){	//The master device number is set
		key.devid = MKDEV(key.major, key.minor);
		ret = register_chrdev_region(key.devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){	//Failed to register device number
			printk("fail to register devid\r\n");
			return -EINVAL;
		}
	}else{	//The master device number is not set
		ret = alloc_chrdev_region(&key.devid, 0, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){	//Failed to apply for equipment number
			printk("fail to alloc devid\r\n");
			return -EINVAL;
		}
		key.major = MAJOR(key.devid);
		key.minor = MINOR(key.devid);
		printk("major is %d\r\nminor is %d\r\n",key.major, key.minor);
	}

	/* Register character device */	
	key.cdev.owner = THIS_MODULE;
	cdev_init(&key.cdev, &key_fops);
	ret = cdev_add(&key.cdev, key.devid, DEVICE_CNT);
	if(ret < 0){	//Failed to add character device
		ret = -EINVAL;
		printk("fail to add cdev\r\n");
		goto fail_add_cdev;
	}

	/* Automatically create device nodes */
	key.class = NULL;
	key.device = NULL;
	key.class = class_create(THIS_MODULE, DEVICE_NAME);
	if(key.class == NULL){	//Failed to create class
		ret = -EINVAL;
		printk("fail to create class\r\n");
		goto fail_cclass;
	}
	key.device = device_create(key.class, NULL, key.devid, NULL, DEVICE_NAME);
	if(key.device == NULL){		//Failed to create device
		ret = -EINVAL;
		printk("fail to create device\r\n");
		goto fail_cdevice;
	}

	/* Get led device tree node */
	key.led_nd = of_find_node_by_name(NULL, "gpioled");
	if(key.led_nd == NULL){
		printk("fail to find led_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_nd;
	}

	/* Get key device tree node */
	key.key_nd = of_find_node_by_name(NULL, "key_input");
	if(key.key_nd == NULL){
		printk("fail to find led_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_nd;
	}

	/* Get led_gpio number */
	key.led_gpio = of_get_named_gpio(key.led_nd, "gpios", 0);
	if(key.led_gpio < 0){	//Get led_gpio number failed
		printk("fail to find led_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_num;
	}
	printk("led_gpio num: %d\r\n", key.led_gpio);

	/* Get key_gpio number */
	key.key_gpio = of_get_named_gpio(key.key_nd, "gpios", 0);
	if(key.key_gpio < 0){	//Get led_gpio number failed
		printk("fail to find key_nd\r\n");
		ret = -EINVAL;
		goto fail_find_gpio_num;
	}
	printk("key_gpio num: %d\r\n", key.key_gpio);

	/* Register gpio */
	gpio_request(key.led_gpio, "gpioled");
	gpio_request(key.key_gpio, "gpiokey");

	/* Set gpio I / O */
	gpio_direction_output(key.led_gpio, 1);
	gpio_direction_input(key.key_gpio);

	printk("key init\r\n");

	return 0;

fail_add_cdev:
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_cclass:
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_cdevice:
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_find_gpio_nd:
	device_destroy(key.class, key.devid);
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
fail_find_gpio_num:
	device_destroy(key.class, key.devid);
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);

	return ret;
}

Exit function:

/* Exit function */
static void __exit key_exit(void)
{
	led_switch(LED_OFF);
	gpio_free(key.led_gpio);
	gpio_free(key.key_gpio);
	device_destroy(key.class, key.devid);
	class_destroy(key.class);
	cdev_del(&key.cdev);
	unregister_chrdev_region(key.devid, DEVICE_CNT);
}

3. Write the specific operation function of the device

/* open function */
static int key_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &key;
	key.led_state = 1;
	return 0;
}

/* read function */
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int value;
	int ret = 0;
	struct key_dev *dev = filp->private_data;

	if(gpio_get_value(dev->key_gpio) == 0){	//key press
		while(!gpio_get_value(dev->key_gpio))	//Wait for the key to release
		atomic_set(&dev->key_val, KEY_VAL);
		led_switch(dev->led_state = !(dev->led_state));
	}else{
		atomic_set(&dev->key_val, INVALKEY);
	}
	value = atomic_read(&dev->key_val);
	ret = copy_to_user(buf, &value, sizeof(value));

	return 0;
}

/* Set of operation functions */
static const struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_open,
	.read = key_read,
};

4. Add header file and create virtual address pointer

When referring to the driver code of linux kernel, find the header file that may be used and add it to the project. When calling a system call function or library function, use the man command on the terminal to view which header files need to be included in the called function.
man command number meaning: 1: standard command 2: system call 3: library function
Add the following header files:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define DEVICE_NAME "key_input"
#define DEVICE_CNT 1
#define LED_ON  1
#define LED_OFF 0
#define KEY_VAL		1
#define INVALKEY    0

5. Add License and author information

The driver License is required. If it is missing, an error will be reported. Add the following code at the end of the file:

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");

6. Write led state switching function

/* LED State switching function */
void led_switch(int led_state)
{
	if(led_state == LED_ON){
		gpio_set_value(gpioled.led_gpio, 0);	//Using API functions of gpio subsystem
	}else{
		gpio_set_value(gpioled.led_gpio, 1);
	}
}

2, Writing test applications

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define KEY_VAL		1
#define INVALKEY    0

int main(int argc, char *argv[])
{
    int fd = 0;
    int ret = 0;
    char *filename;
    int keyvalue;
    
    if(argc != 3){
        printf("missing parameter!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("open file %s failed\r\n", filename);
        return -1;
    }

    while(1){
        read(fd, &keyvalue, sizeof(keyvalue));
        if(keyvalue == KEY_VAL){
            printf("KEY0 Press\r\n");/* Press */
        }
    }


    ret = close(fd);
    if(ret < 0) //The return value is less than 0. Closing the file failed
    {
        printf("close file %s failed\r\n", filename);
        return -1;
    }

    return 0;
}

3, Compilation and testing

1. Write Makefile and compile the driver

The driver source code needs to be compiled into ko module, create Makefile:

KERNELDIR := /home/liuzhikai/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := key.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
  • In line 1, KERNELDIR indicates the Linux kernel source code directory used by the development board, using the absolute path.

  • Line 2, CURRENT_PATH indicates the current path.

  • In line 3, obj-m indicates that the key C this file is compiled into modules.

  • Line 5, the default target is kernel_modules.

  • Line 8, the specific compilation command. The following modules represent the compilation module, and - C represents switching the current working directory to the specified directory. M represents the module source code directory. After adding M=dir to the "make modules" command, the program will automatically read the module source code in the specified dir directory and compile it ko file.

    After the compilation is successful, a key will be generated Ko = = file, which is the driver module.

2. Compile driver

The test APP needs to run on the ARM development board, so it is compiled using the cross compiler:

arm-linux-gnueabihf-gcc keyAPP.c -o ledAPP

Compile the executable program that generates the keyAPP.

3. Run test

Select network startup for linux system, and mount the root file system with nfs. The U-Boot settings are as follows:
Value of bootcmd:

tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000

Value of bootargs:

console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.138:/home/liuzhikai/linux/nfs/rootfs ip=192.168.1.123:192.168.1.138:192.168.1.2:255.255.255.0::eth0:off

Set dtsled KO and ledAPP are copied to the following directory (non-existent directory creation):

sudo cp dtsled.ko keyApp /home/liuzhikai/linux/nfs/rootfs/lib/modules/4.1.15/ -f

Load the driver module with the following command:

depmod
modprobe dtsled.ko  //Load driver module
./keyAPP /dev/dtsled 1  //Execute the application and turn on the led
./keyAPP /dev/dtsled 0  //Execute the application and turn off the led
rmmod dtsled.ko  //Unloading the driver module