Linux kernel programming (08): debugfs file system

Posted by sillyman on Sun, 27 Feb 2022 08:39:42 +0100

1, What is debugfs

Introduction: born for debugging kernel, a memory based file system, developed based on libfs, provides powerful debugging function; Advantages over procfs and sysfs:

procfs: use procfs to debug the kernel and modify registers, and add a lot of debugging code to its underlying read and write interfaces.

sysfs: device model, exported to user space.

debugfs: simplifies the interface at the guide. One line of code can export kernel variables, arrays, linked lists, data in memory, registers and other information. Mount on / sys/kernel/debug.

2, debugfs configuration compilation and registration run

​ linux-5.10: vexpress_defconfig

​ make vexpress_defconfig; NFS v4. x; make uImage; Make all (used to generate the Module.symvers file, which will be used when compiling independent modules. Some files will be generated during normal compilation. If not, we will use make all for compilation);

Many modules in the kernel use the API interface of debugfs, CONFIG_DEBUG_FS, if not configured, the corresponding function is null.

// /fs/debugfs/inode.c
early_param("debugfs", debugfs_kernel);
static int __init debugfs_init(void)
{
    int retval;

    if (!(debugfs_allow & DEBUGFS_ALLOW_MOUNT))
        return -EPERM;

    retval = sysfs_create_mount_point(kernel_kobj, "debug");
    if (retval)
        return retval;

    retval = register_filesystem(&debug_fs_type);
    if (retval)
        sysfs_remove_mount_point(kernel_kobj, "debug");
    else
        debugfs_registered = true;

    return retval;
}

3, The first debugfs programming example

Use the API interface provided by debugfs to generate your own files or directories in the debugfs directory. Related APIs are defined in include / Linux / debugfs H directory.

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

static unsigned int hello_value;
static struct dentry *hello_root;

static int __init hello_debugfs_init(void)
{
    // Create directory hello
    hello_root = debugfs_crate_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_err("%s: create debugfs dir failed.\n", __func__);
        return PTR_ERR(hello_root);
    }
     // Create variable hello_reg
    debugfs_create_u32("hello_reg", 0644, hello_root, &hello_value);
	
    return 0;
}

static void __exit hello_exit(void)
{
    debugfs_remove_recursive(hello_root);
}

module_init(hello_debugfs_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

4, Export shaping data through debugfs

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

static unsigned int hello_value;
static struct dentry *hello_root;

static u8 u8_a;
static u16 u16_a;
static size_t size_t_a;
static unsigned long ulong_a;
static bool bool_a;

static int __init hello_debugfs_init(void)
{
    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_error("%s: create debugfs dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }
    
    debugfs_create_u32("hello_reg", 0644, hello_root, &hello_value);
    debugfs_create_u8("u8_reg", 0644, hello_root, &u8_a);
    debugfs_create_u16("u16_reg", 0644, hello_root, &u16_a);
    debugfs_create_u64("u64_reg", 0644, hello_root, &u64_a);
    debugfs_create_size_t("size_t_reg", 0644, hello_root, &size_t_a);
    debugfs_create_ulong("ulong_reg", 0644, hello_root, &ulong_a);
    debugfs_create_bool("bool_reg", 0644, hello_root, &bool_a);
}

static void __exit hello_debugfs_exit(void)
{
    if (hello_root) {
        debugfs_remove_recursive(hello_root);
    }
}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

5, Exporting hexadecimal data through debugfs

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

static unsigned int hello_value;
static struct dentry *hello_root;

static u8 u8_a;
static u16 u16_a;
static u64 u64_a;


static int __init hello_debugfs_init(void)
{
    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_error("%s: create debugfs dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }
    
    // Functions with x are displayed in hexadecimal
    debugfs_create_x32("hello_reg", 0644, hello_root, &hello_value);
    debugfs_create_x8("u8_reg", 0644, hello_root, &u8_a);
    debugfs_create_x16("u16_reg", 0644, hello_root, &u16_a);
    debugfs_create_x64("u64_reg", 0644, hello_root, &u64_a);
}

static void __exit hello_debugfs_exit(void)
{
    if (hello_root) {
        debugfs_remove_recursive(hello_root);
    }
}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

6, Exporting arrays through debugfs

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

static struct dentry *hello_root;
static u32 hello_array[8] = {1, 2, 3, 4, 5, 6, 7, 8};

static struct debugfs_u32_array array_info = {
    .array = hello_array,
    .n_elements = 8,
};

static int __init hello_debugfs_init(void)
{
    int ret = 0;

    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_error("%s: create debugfs dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }
    
    // Through debugfs_ create_ u32_ The array function exports an array, which is easy to read; I'm not very good at writing
    debugfs_create_u32_array("hello_array", S_IWUGO, hello_root, &array_info);
   
    return ret;
}

static void __exit hello_debugfs_exit(void)
{
    if (hello_root) {
        debugfs_remove_recursive(hello_root);
    }
}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

7, Export memory data through debugfs

Any piece of memory can export data through address

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

static struct dentry *hello_root;
static u32 hello_array[8] = {1, 2, 3, 4, 5, 6, 7, 8};
static char *mem_block_p = NULL;

static struct debugfs_u32_array array_info = {
    .array = hello_array,
    .n_elements = 8,
};

static struct debugfs_blob_wrapper hello_blob = {
    .data = hello_array,
    .size = 8 * sizeof(u32),
};

static struct debugfs_blob_wrapper hello_mem;

static int __init hello_debugfs_init(void)
{
    int ret = 0;
    
    mem_block_p = kmalloc(32, GFP_ATOMIC);
	if (!mem_block_p) {
        pr_err("%s: kmalloc memory failed\n", __func__);
        return -ENOMEM;
    }
    memcpy(mem_block_p, "hello zhaixue.cc\n", 20);
    hello_mem.data = mem_block_p;
    hello_mem.size = 32;
    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_error("%s: create debugfs dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }
    
    // Through debugfs_ create_ u32_ The array function exports an array, which is easy to read; I'm not very good at writing
    debugfs_create_u32_array("hello_array", S_IWUGO, hello_root, &array_info);
   	debugfs_create_blob("hello_blob", S_IWUGO, hello_root, &hello_blob);
    debugfs_create_blob("hello_mem", S_IWUGO, hello_root, &hello_mem);
    return ret;
}

static void __exit hello_debugfs_exit(void)
{
    if (hello_root) {
        debugfs_remove_recursive(hello_root);
    }
}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

8, Export custom format data through debugfs

Build a read-write file suitable for our format_ Operation interface to complete the reading and writing of data.

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

static struct dentry *hello_root;
static unsigned int hello_value;
static char hello_buf[100];

static size_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    if (*ppos >= 100) {
        return 0;
    }
    if (*ppos + count > 100) {
        count = 100 - *ppos;
    }
    
    ret = copy_to_user(buf, hello_buf, count);
    if (ret) {
        return -EFAULT;
    }
    *ppos += count;
    
    return count;
}

static void hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    if (*ppos >= 100) {
        return 0;
    }
    if (*ppos + count > 100) {
        count = 100 - *ppos;
    }
    
    ret = copy_from_user(hello_buf, buf, count);
    if (ret) {
        return -EFAULT;
    }
    *ppos += count;
    
    return count;
}

static struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .read  = hello_read,
    .write = hello_write,
};

static int __init hello_debugfs_init(void)
{
    int ret = 0;
    
    mem_block_p = kmalloc(32, GFP_ATOMIC);
	if (!mem_block_p) {
        pr_err("%s: kmalloc memory failed\n", __func__);
        return -ENOMEM;
    }
	
    debugfs_create_x32("hello_reg", 0644, hello_root, &hello_value);
    debugfs_create_file("hello_buffer", S_IWUGO, hello_root, NULL, &hello_fops);
   
    return ret;
}

static void __exit hello_debugfs_exit(void)
{
    if (hello_root) {
        debugfs_remove_recursive(hello_root);
    }
}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

9, Using SEQ in debugfs_ File interface

​ debugfs + seq_file interface

​ seq_ read, seq_ Options: start / next / show / stop. The show function is implemented by yourself

​ seq_read, seq_opertions: self implement seq_operation interface

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

static struct dentry *hello_root;
static char hello_buf[100];

static int hello_debugfs_show(struct seq_file *s, void *data)
{
    char *p = s->private;
    seq_printf(s, "%s", p);
    
    return 0;
}

static int hello_open(struct inode *inode, struct file *filp)
{
    return single_open(file, hello_debugfs_show, inode->i_private);
}

static size_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
     int ret;
    if (*ppos >= 100) {
        return 0;
    }
    if (*ppos + count > 100) {
        count = 100 - *ppos;
    }
    
    ret = copy_from_user(hello_buf, buf, count);
    if (ret) {
        return -EFAULT;
    }
    *ppos += count;
    
    return count;
}

struct file_operations hello_fops = {
    .owner   = THIS_MODULE,
    .open    = hello_open,
    .read    = seq_read,
    .write   = hello_write,
    .llseek  = seq_lseek,
    .release = single_release,
};

static int __init hello_debugfs_init(void)
{
    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_err("");
        return PTR_ERR(hello_root);
    }

    debugfs_create_file("hello_buffer", S_IWUGO, hello_root, hello_buf, &hello_fops);
   
    return ret;
}

static void __exit hello_exit(void)
{
    if (hello_root) {
        debugfs_remove_recursive(hello_root);
    }
    
}

module_init(hello_debugfs_init);
module_exit(hello_exit);

10, Using seq_ The file interface traverses the array

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>

struct hello_struct {
    unsigned int value;
    unsigned int id;
};

static struct dentry *hello_root;
static struct hello_struct hello_array[8];
static char hello_buf[64];
static int index = 0;

static void *hello_seq_start(struct seq_file *s, loff_t *pos)
{
    printk("----------start: *pos = %lld\n", *pos);
    if (*pos == 0){
        return &hello_array[0];
    }
    else {
        *pos = 0;
        return NULL;
    }
}

static void *hello_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
    struct hello_struct  *p_node = NULL;
    (*pos)++;
    printk("---------next: *pos = %lld\n", *pos);
    p_node = &hello_array[*pos];
    
    if (*pos == 8) {
    //    *pos = 0;
        return NULL;
    }
    return p_node;
}

static void hello_seq_stop(struct seq_file *s, void *v)
{
    printk("stop\n");
}

static int hello_seq_show(struct seq_file *s, void *v)
{
    struct hello_struct *p = (struct hello_struct *)v;
    printk("---------show: id = %d\n", p->id);
    if (p->id > 0)
        seq_printf(s, "hello_arr[%d].value = 0x%x\n", p->id-1, \
                      hello_array[p->id-1].value);
    return 0;
}

static struct seq_operations hello_seq_ops = {
    .start = hello_seq_start,
    .next  = hello_seq_next,
    .stop  = hello_seq_stop,
    .show  = hello_seq_show,
};

static int hello_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &hello_seq_ops);
}

static ssize_t hello_write(struct file *filp, const char __user *buf, 
                            size_t len, loff_t *ppos)
{
    int ret;
    if (len == 0 || len > 64) {
        ret = -EFAULT;
        return ret;
    }
    ret = copy_from_user(hello_buf, buf, len);
    if (ret)
        return -EFAULT;
    printk("hello_write: index = %d\n", index);
    hello_array[index].id = index + 1;
    hello_array[index].value = simple_strtoul(hello_buf, NULL, 0);
    index++;
    if (index == 8)
        index = 0;
    return len;
}

static const struct file_operations hello_ops = {
    .owner    = THIS_MODULE,
    .open     = hello_open,
    .read     = seq_read,
    .write    = hello_write,
    .llseek    = seq_lseek,
};



static int __init hello_debugfs_init(void)
{
    int ret = 0;

    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_err("%s: create debugfs dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }

    debugfs_create_file("hello_buffer", S_IWUGO, hello_root,  , 
                        &hello_ops);

    return ret;
}

static void __exit hello_debugfs_exit(void)
{
    if (hello_root)
        debugfs_remove_recursive(hello_root);
}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

11, Using seq_file interface traverses the linked list

Through the implementation of customized seq_operations interface, and seq_file linked list interface

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>

static struct dentry *hello_root;

static char hello_buf[64];
static struct list_head hello_list_head;

struct hello_struct {
    unsigned int value;
    struct list_head node;
};


static void *hello_seq_start(struct seq_file *s, loff_t *pos)
{
   return seq_list_start(&hello_list_head, *pos) ;
}

static void *hello_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
    return seq_list_next(v, &hello_list_head, pos);
}

static void hello_seq_stop(struct seq_file *s, void *v)
{
    //printk("stop\n");
}

static int hello_seq_show(struct seq_file *s, void *v)
{
    struct hello_struct *p_node = list_entry(v, struct hello_struct, node);
    seq_printf(s, "node = 0x%x\n",  p_node->value);
    return 0;
}

static struct seq_operations hello_seq_ops = {
    .start = hello_seq_start,
    .next  = hello_seq_next,
    .stop  = hello_seq_stop,
    .show  = hello_seq_show,
};

static int hello_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &hello_seq_ops);
}

static ssize_t hello_write(struct file *filp, const char __user *buf, 
                            size_t len, loff_t *ppos)
{
    int ret;
    struct hello_struct *data;
    if (len == 0 || len > 64) {
        ret = -EFAULT;
        return ret;
    }
    ret = copy_from_user(hello_buf, buf, len);
    if (ret)
        return -EFAULT;

    data = kmalloc(sizeof(struct hello_struct), GFP_KERNEL);
    if (data != NULL) {
        data->value = simple_strtoul(hello_buf, NULL, 0);
        list_add(&data->node, &hello_list_head);
    }

    return len;
}

static const struct file_operations hello_ops = {
    .open   = hello_open,
    .read   = seq_read,
    .write  = hello_write,
    .llseek = seq_lseek,
};

static int __init hello_debugfs_init(void)
{
    int ret = 0;
    
    INIT_LIST_HEAD(&hello_list_head);

    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_err("%s: create debugfs dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }

    debugfs_create_file("hello_list", S_IWUGO, hello_root, NULL, &hello_ops);

    return ret;
}

static void __exit hello_debugfs_exit(void)
{
    struct hello_struct *data;
    
    if (hello_root)
        debugfs_remove_recursive(hello_root);

    while (!list_empty(&hello_list_head)) {
        data = list_entry(hello_list_head.next, struct hello_struct, node);
        list_del(&data->node);
        kfree(data);
    }

}

module_init(hello_debugfs_init);
module_exit(hello_debugfs_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");

12, Use debugfs to export the list of registers

In this case, the register is read-only.

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>

typedef volatile struct {
        unsigned long  RTCDR;    /* +0x00: data register */
        unsigned long  RTCMR;    /* +0x04: match register */
        unsigned long  RTCLR;    /* +0x08: load register */
        unsigned long  RTCCR;    /* +0x0C: control register */
        unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
        unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
        unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
        unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time {
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;

static dev_t devno;
static struct cdev *rtc_cdev;

static void rtc_time_translate(void)
{
    tm.hour = (cur_time  % 86400) / 3600;
    tm.min  = (cur_time  % 3600) / 60;
    tm.sec  = cur_time  % 60;
}

static void set_rtc_alarm(void)
{
    unsigned long tmp = 0;
    
    tmp = regs->RTCCR;
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;


    cur_time = regs->RTCDR;
    regs->RTCMR = cur_time + 15;

    regs->RTCICR = 0x1;
    regs->RTCIMSC = 0x1;

    tmp = regs->RTCCR;
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{

    cur_time = regs->RTCDR;
    rtc_time_translate();
    printk("\nalarm: beep~ beep~ \n");
    printk("\n    %d:%d:%d\n", tm.hour, tm.min, tm.sec);
    regs->RTCICR = 1;
    set_rtc_alarm();
    return IRQ_HANDLED;
}

static struct file_operations rtc_fops;

static const struct debugfs_reg32 rtc_reg_array[] = {
    {
        .name   = "RTCDR",
        .offset = 0x0
    },
    {
        .name   = "RTCMR",
        .offset = 0x4
    },
    {
        .name   = "RTCLR",
        .offset = 0x8
    },
    {
        .name   = "RTCCR",
        .offset = 0xC
    },
    {
        .name   = "RTCIMSC",
        .offset = 0x10
    },
    {
        .name   = "RTCRIS",
        .offset = 0x14
    },
    {
        .name   = "RTCMIS",
        .offset = 0x18
    },
    {
        .name   = "RTCICR",
        .offset = 0x1C
    }
};

static struct debugfs_regset32 rtc_regset;
static struct dentry *hello_root;

static int __init rtc_init(void)
{
    int ret = 0;
    
    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));

    ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo");
    if (ret) {
        printk("alloc char device number failed!\n");
        return ret;
    }
    printk("RTC devnum:%d minornum:%d\n", MAJOR(devno), MINOR(devno));

    rtc_cdev = cdev_alloc();
    cdev_init(rtc_cdev, &rtc_fops);

    ret = cdev_add(rtc_cdev, devno, 1);
    if (ret < 0) {
        printk("cdev_add failed..\n");
        return -1;
    }
    else {
        printk("Register char module: rtc success!\n");
    }

    ret = request_irq(39, rtc_alarm_handler, 0, "rtc-test", NULL);
    if (ret == -1) {
        printk("request_irq failed!\n");
        return -1;
    }

    set_rtc_alarm();

    hello_root = debugfs_create_dir("hello", NULL);
    if (IS_ERR(hello_root)) {
        pr_err("%s: create rtc dir failed\n", __func__);
        return PTR_ERR(hello_root);
    }

    rtc_regset.regs  = rtc_reg_array;
    rtc_regset.nregs = 8;
    rtc_regset.base  = (void *)regs;
    // Pay attention to debugfs_ create_ The last parameter of the regset32 function is the register related structure
    debugfs_create_regset32("reglist", 0644, hello_root, &rtc_regset);

    return 0;
}

static void __exit rtc_exit(void)
{
    if (hello_root)
        debugfs_remove_recursive(hello_root);

    free_irq(39, NULL);
    cdev_del(rtc_cdev);
    unregister_chrdev_region(devno, 1);
}

module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");

13, Using debugfs to modify registers

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>

typedef volatile struct {
        unsigned int  RTCDR;    /* +0x00: data register */
        unsigned int  RTCMR;    /* +0x04: match register */
        unsigned int  RTCLR;    /* +0x08: load register */
        unsigned int  RTCCR;    /* +0x0C: control register */
        unsigned int  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
        unsigned int  RTCRIS;   /* +0x14: raw interrupt status register*/
        unsigned int  RTCMIS;   /* +0x18: masked interrupt status register */
        unsigned int  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time {
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;
static struct rtc_time tm;

static void rtc_time_translate(void)
{
    tm.hour = (cur_time % 86400) / 3600;
    tm.min  = (cur_time % 3600) / 60;
    tm.sec = cur_time % 60;
}

static void rtc_tm_to_time(void)
{
    cur_time = tm.hour * 3600 + tm.min * 60 + tm.sec;
}


static dev_t devno;
static struct cdev *rtc_cdev;

static int rtc_open(struct inode *inode, struct file *fp)
{
    return 0;
}

static int rtc_release(struct inode *inode, struct file *fp)
{
    return 0;
}

static ssize_t rtc_read(struct file *fp, char __user *buf, 
                           size_t size, loff_t *pos)
{

    cur_time = regs->RTCDR;
    rtc_time_translate();
    if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){
        printk("rtc_read error!\n");
        return -1;
    }

    return sizeof(struct rtc_time);
}

static ssize_t rtc_write(struct file *fp, const char __user *buf, 
                            size_t size, loff_t *pos)
{
    int len = 0;
    
    len = sizeof(struct rtc_time);
    if (copy_from_user(&tm, buf, len) != 0) {
        printk("rtc_write error!\n");
        return -1;
    }
    rtc_tm_to_time();
    regs->RTCLR = cur_time;

    return len;
}

/* standard file I/O system call interface */
static const struct file_operations rtc_fops = {
    .owner   = THIS_MODULE,
    .read    = rtc_read,
    .write   = rtc_write,
    .open    = rtc_open,
    .release = rtc_release,
};

static void set_rtc_alarm(void)
{
    unsigned int tmp = 0;
    
    tmp = regs->RTCCR;
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;


    cur_time = regs->RTCDR;
    regs->RTCMR = cur_time + 25;

    regs->RTCICR = 1;
    regs->RTCIMSC = 1;

    tmp = regs->RTCCR;
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{

    cur_time = regs->RTCDR;
    rtc_time_translate();
    printk("\nalarm: beep~ beep~ \n");
    printk("\n    %d:%d:%d\n", tm.hour, tm.min, tm.sec);
    regs->RTCICR = 1;
    set_rtc_alarm();
    return IRQ_HANDLED;
}


static struct dentry *rtc_root;
static struct dentry *reg_dir;
static u32 *reg_p = NULL;

static int __init rtc_init(void)
{
    int ret = 0;
    
    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
    printk("rtc_init\n");

    ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo");
    if (ret) {
        printk("alloc char device number failed!\n");
        return ret;
    }
    printk("RTC devnum:%d minornum:%d\n", MAJOR(devno), MINOR(devno));

    rtc_cdev = cdev_alloc();
    cdev_init(rtc_cdev, &rtc_fops);

    ret = cdev_add(rtc_cdev, devno, 1);
    if (ret < 0) {
        printk("cdev_add failed..\n");
        return -ret;
    }
    else {
        printk("Register char module: rtc success!\n");
    }
    
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc-test", NULL);
    if (ret == -1) {
        printk("request_irq failed!\n");
        return -ret;
    }

    rtc_root = debugfs_create_dir("rtc", NULL);
    if (IS_ERR(rtc_root)) {
        pr_err("%s: create rtc dir failed\n", __func__);
        return PTR_ERR(rtc_root);
    }
    reg_dir = debugfs_create_dir("reg_list", rtc_root);
    if (IS_ERR(reg_dir)) {
        pr_err("%s: create reg dir failed\n", __func__);
        return PTR_ERR(reg_dir);
    }

    reg_p =(u32 *) regs;
    // The interface function used is debugfs_create_x32, that is, export an address, readable and writable
    debugfs_create_x32("RTCDR", 0644, reg_dir, reg_p);
    debugfs_create_x32("RTCMR", 0644, reg_dir, reg_p + 0x01);
    debugfs_create_x32("RTCLR", 0644, reg_dir, reg_p + 0x02);
    debugfs_create_x32("RTCCR", 0644, reg_dir, reg_p + 0x03);
    debugfs_create_x32("RTCIMSC", 0644, reg_dir, reg_p + 0x04);
    debugfs_create_x32("RTCRIS", 0644, reg_dir, reg_p + 0x05);
    debugfs_create_x32("RTCMIS", 0644, reg_dir, reg_p + 0x06);
    debugfs_create_x32("RTCICR", 0644, reg_dir, reg_p + 0x07);


    set_rtc_alarm();

    return 0;
}

static void __exit rtc_exit(void)
{
    if (rtc_root)
        debugfs_remove_recursive(rtc_root);

    free_irq(39, NULL);
    cdev_del(rtc_cdev);
    unregister_chrdev_region(devno, 1);
}

module_init(rtc_init);
module_exit(rtc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");

14, Export debugging interface through debugfs

After the debugging interface is exported through debugfs, the user's debugging program can test the function of the interface through the file under / sys/kernel/debugfs.

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>

typedef volatile struct {
        unsigned int  RTCDR;    /* +0x00: data register */
        unsigned int  RTCMR;    /* +0x04: match register */
        unsigned int  RTCLR;    /* +0x08: load register */
        unsigned int  RTCCR;    /* +0x0C: control register */
        unsigned int  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
        unsigned int  RTCRIS;   /* +0x14: raw interrupt status register*/
        unsigned int  RTCMIS;   /* +0x18: masked interrupt status register */
        unsigned int  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time {
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

#define CUR_TIME_CMD   1      /* command for set/get current time */
#define ALARM_TIME_CMD 2      /* command for set/get alarm time */

static volatile rtc_reg_t *regs = NULL;
static unsigned long cur_time = 0;   /* current time */
static unsigned long alarm_time = 0; /* alarm time  */
static struct rtc_time tm; /* global time */
static char rtc_buf[64]; /* buffer for time string input from user space */

static void rtc_time_to_tm(unsigned long time)
{
    tm.hour = (time % 86400) / 3600;
    tm.min  = (time % 3600) / 60;
    tm.sec = time % 60;
}

static void rtc_tm_to_time(unsigned long *time)
{
    *time = tm.hour * 3600 + tm.min * 60 + tm.sec;
}

static void rtc_string_to_tm(void)
{
    tm.hour = (rtc_buf[0] - '0') * 10 + (rtc_buf[1] - '0');
    tm.min  = (rtc_buf[2] - '0') * 10 + (rtc_buf[3] - '0');
    tm.sec  = (rtc_buf[4] - '0') * 10 + (rtc_buf[5] - '0');
}

static void rtc_tm_to_string(void)
{
    rtc_buf[0] = tm.hour / 10 + '0';
    rtc_buf[1] = tm.hour % 10 + '0';
    rtc_buf[2] = tm.min / 10 + '0';
    rtc_buf[3] = tm.min % 10 + '0';
    rtc_buf[4] = tm.sec / 10 + '0';
    rtc_buf[5] = tm.sec % 10 + '0';
    rtc_buf[6] = '\n';
    rtc_buf[7] = '\0';
}


static dev_t devno;
static struct cdev *rtc_cdev;

static int rtc_open(struct inode *inode, struct file *fp)
{
    return 0;
}

static int rtc_release(struct inode *inode, struct file *fp)
{
    return 0;
}

static ssize_t rtc_read(struct file *fp, char __user *buf, 
                           size_t size, loff_t *pos)
{

    cur_time = regs->RTCDR;
    rtc_time_to_tm(cur_time);
    if (copy_to_user(buf, &tm, sizeof(struct rtc_time)) != 0){
        printk("rtc_read error!\n");
        return -1;
    }

    return sizeof(struct rtc_time);
}

static ssize_t rtc_write(struct file *fp, const char __user *buf, 
                            size_t size, loff_t *pos)
{
    int len = 0;
    
    len = sizeof(struct rtc_time);
    if (copy_from_user(&tm, buf, len) != 0) {
        printk("rtc_write error!\n");
        return -1;
    }
    rtc_tm_to_time(&cur_time);
    regs->RTCLR = cur_time;

    return len;
}

/* standard file I/O system call */
static const struct file_operations rtc_fops = {
    .owner   = THIS_MODULE,
    .read    = rtc_read,
    .write   = rtc_write,
    .open    = rtc_open,
    .release = rtc_release,
};


static void set_rtc_alarm(void)
{
    unsigned int tmp = 0;
    
    tmp = regs->RTCCR;
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;


    cur_time = regs->RTCDR;
    regs->RTCMR = alarm_time;

    regs->RTCICR = 1;
    regs->RTCIMSC = 1;

    tmp = regs->RTCCR;
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{

    cur_time = regs->RTCDR;
    rtc_time_to_tm(cur_time);
    printk("\nalarm: beep~ beep~ \n");
    printk("\n    %d:%d:%d\n", tm.hour, tm.min, tm.sec);
    regs->RTCICR = 1;

    return IRQ_HANDLED;
}

static ssize_t rtc_debugfs_read(struct file *filp, char __user *buf,
                                size_t count, loff_t *ppos, int cmd)
{
    int ret;

    if (*ppos >= 64)
        return 0;
    if (*ppos + count > 64)
        count = 64 - *ppos;

    if (cmd == CUR_TIME_CMD) {
        cur_time = regs->RTCDR;
        rtc_time_to_tm(cur_time);
    }

    if (cmd == ALARM_TIME_CMD) {
        alarm_time = regs->RTCMR;
        rtc_time_to_tm(alarm_time);
    }

    rtc_tm_to_string();

    ret = copy_to_user(buf, rtc_buf, count);
    if (ret)
        return -EFAULT;
   *ppos += count;

    return count;
}

static ssize_t rtc_debugfs_write(struct file *filp, const char __user *buf, 
                                     size_t count, loff_t *ppos, int cmd)
{
    int ret;
    
    if (*ppos > 64)
        return 0;
    if (*ppos + count > 64)
        count = 64 - *ppos;
    
    ret = copy_from_user(rtc_buf, buf, count);
    if (ret)
        return -EFAULT;
    *ppos += count;

    rtc_string_to_tm();

    if (cmd == CUR_TIME_CMD) {
        rtc_tm_to_time(&cur_time);
        regs->RTCLR = cur_time;
    }

    if (cmd == ALARM_TIME_CMD) {
        rtc_tm_to_time(&alarm_time);
        set_rtc_alarm();
    }

    return count;
}


/* set current time interface */
static ssize_t rtc_cur_time_read(struct file *filp, char __user *buf, 
                                     size_t count, loff_t *ppos)
{
    return rtc_debugfs_read(filp, buf, count, ppos, CUR_TIME_CMD);
}

static ssize_t rtc_cur_time_write(struct file *filp, const char __user *buf, 
                                     size_t count, loff_t *ppos)
{
    return rtc_debugfs_write(filp, buf, count, ppos, CUR_TIME_CMD);
}

static const struct file_operations rtc_cur_time_fops = {
    .read    = rtc_cur_time_read,
    .write   = rtc_cur_time_write,
};


/* set alarm time interface */
static ssize_t rtc_alarm_time_read(struct file *filp, char __user *buf, 
                                     size_t count, loff_t *ppos)
{
    return rtc_debugfs_read(filp, buf, count, ppos, ALARM_TIME_CMD);
}

static ssize_t rtc_alarm_time_write(struct file *filp, const char __user *buf, 
                                     size_t count, loff_t *ppos)
{
    return rtc_debugfs_write(filp, buf, count, ppos, ALARM_TIME_CMD);
}

static const struct file_operations rtc_alarm_time_fops = {
    .read    = rtc_alarm_time_read,
    .write   = rtc_alarm_time_write,
};



static struct dentry *rtc_root;
static struct dentry *reg_dir;
static u32 *reg_p = NULL;

static int __init rtc_init(void)
{
    int ret = 0;
    
    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
    printk("rtc_init\n");

    ret = alloc_chrdev_region(&devno, 0, 1, "rtc-demo");
    if (ret) {
        printk("alloc char device number failed!\n");
        return ret;
    }
    printk("RTC devnum:%d minornum:%d\n", MAJOR(devno), MINOR(devno));

    rtc_cdev = cdev_alloc();
    cdev_init(rtc_cdev, &rtc_fops);

    ret = cdev_add(rtc_cdev, devno, 1);
    if (ret < 0) {
        printk("cdev_add failed..\n");
        return -ret;
    }
    else {
        printk("Register char module: rtc success!\n");
    }
    
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc-test", NULL);
    if (ret == -1) {
        printk("request_irq failed!\n");
        return -ret;
    }

    rtc_root = debugfs_create_dir("rtc", NULL);
    if (IS_ERR(rtc_root)) {
        pr_err("%s: create rtc dir failed\n", __func__);
        return PTR_ERR(rtc_root);
    }
    reg_dir = debugfs_create_dir("reg_list", rtc_root);
    if (IS_ERR(reg_dir)) {
        pr_err("%s: create reg dir failed\n", __func__);
        return PTR_ERR(reg_dir);
    }

    reg_p =(u32 *) regs;
    debugfs_create_x32("RTCDR", 0644, reg_dir, reg_p);
    debugfs_create_x32("RTCMR", 0644, reg_dir, reg_p + 0x01);
    debugfs_create_x32("RTCLR", 0644, reg_dir, reg_p + 0x02);
    debugfs_create_x32("RTCCR", 0644, reg_dir, reg_p + 0x03);
    debugfs_create_x32("RTCIMSC", 0644, reg_dir, reg_p + 0x04);
    debugfs_create_x32("RTCRIS", 0644, reg_dir, reg_p + 0x05);
    debugfs_create_x32("RTCMIS", 0644, reg_dir, reg_p + 0x06);
    debugfs_create_x32("RTCICR", 0644, reg_dir, reg_p + 0x07);

    debugfs_create_file("cur_time", 0644, rtc_root, NULL, &rtc_cur_time_fops);
    debugfs_create_file("alarm_time", 0644, rtc_root, 
                          NULL, &rtc_alarm_time_fops);
    
    return 0;
}

static void __exit rtc_exit(void)
{
    if (rtc_root)
        debugfs_remove_recursive(rtc_root);

    free_irq(39, NULL);
    cdev_del(rtc_cdev);
    unregister_chrdev_region(devno, 1);
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");

Topics: Linux Operation & Maintenance server