侧边栏壁纸
  • 累计撰写 185 篇文章
  • 累计创建 77 个标签
  • 累计收到 18 条评论

目 录CONTENT

文章目录

通过ioctl操作硬件端口的Linux内核模块代码

码峰
2023-02-20 / 0 评论 / 0 点赞 / 776 阅读 / 1,150 字 / 正在检测是否收录...
广告 广告

前言

在Linux中,如果要对特定的硬件端口进行操作,用户空间是没有足够的权限的,可以在内核模块中实现端口的读写操作,然后用户空间中的程序通过内核模块的ioctl进行操作,相关的代码实现和操作记录备忘。

内核模块代码

以下是一个简单的Linux内核驱动,可以实现对特定IO端口进行读写操作。这个驱动可以将一个整数写入端口,并从端口读取一个整数,并将结果返回给用户空间。具体实现如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <asm/io.h>

#define DRIVER_NAME "my_port_driver"
#define IOCTL_WRITE_PORT _IOW('k', 1, int)
#define IOCTL_READ_PORT _IOR('k', 2, int)

MODULE_LICENSE("GPL");

static int my_port_driver_open(struct inode *inode, struct file *file);
static int my_port_driver_release(struct inode *inode, struct file *file);
static long my_port_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg);

static int port = 0x378;

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_port_driver_open,
    .release = my_port_driver_release,
    .unlocked_ioctl = my_port_driver_ioctl,
};

static dev_t dev;
static struct cdev my_cdev;

static int my_port_driver_init(void) {
    int ret = alloc_chrdev_region(&dev, 0, 1, DRIVER_NAME);
    if (ret) {
        printk(KERN_ERR "Failed to allocate char device region\n");
        return ret;
    }

    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;

    ret = cdev_add(&my_cdev, dev, 1);
    if (ret) {
        printk(KERN_ERR "Failed to add char device\n");
        unregister_chrdev_region(dev, 1);
        return ret;
    }

    printk(KERN_INFO "my_port_driver loaded\n");

    return 0;
}

static void my_port_driver_exit(void) {
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "my_port_driver unloaded\n");
}

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

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

static long my_port_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    int tmp;

    switch (cmd) {
        case IOCTL_WRITE_PORT:
            if (copy_from_user(&tmp, (int *)arg, sizeof(int))) {
                return -EFAULT;
            }

            outb(tmp, port);
            break;
        case IOCTL_READ_PORT:
            tmp = inb(port);

            if (copy_to_user((int *)arg, &tmp, sizeof(int))) {
                return -EFAULT;
            }

            break;
        default:
            return -ENOTTY;
    }

    return 0;
}

module_init(my_port_driver_init);
module_exit(my_port_driver_exit);

这个驱动会注册一个字符设备,并且实现了open、release和ioctl三个函数。在用户空间,可以使用ioctl系统调用来调用驱动中的ioctl函数。这个驱动实现了两个命令:IOCTL_WRITE_PORT和IOCTL_READ_PORT。前者会将一个整数写入端口,后者会从端口读取一个整数。这个驱动代码中的inb和outb函数是内联汇编实现的,用于向端口发送和接收数据。inb函数会从指定端口读取一个字节,而outb函数会将一个字节写入指定端口。port变量表示要读写的IO端口的地址。需要注意的是,这个驱动没有进行足够的错误处理,例如没有检查用户空间指针是否合法。在实际的驱动开发中,需要进行更严格的错误处理。

如何使用

驱动编译时需要使用Linux内核源代码中提供的Makefile文件进行编译。具体编译步骤如下:

  1. 编写驱动代码并保存到文件中(例如my_port_driver.c)。
  2. 在Linux内核源代码目录中创建一个Kbuild文件,内容如下:
obj-m := my_port_driver.o
  1. 在Linux内核源代码目录中打开终端,执行以下命令进行编译:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

其中(unamer)表示当前内核版本,(uname -r)表示当前内核版本,(pwd)表示当前目录。
4. 如果编译成功,会生成一个my_port_driver.ko文件。
加载驱动模块:

sudo insmod my_port_driver.ko

查看内核日志,确认驱动已经加载成功:

dmesg

可以使用ioctl系统调用进行读写操作。例如,向端口0x378写入数值100:

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define DEVICE_NAME "/dev/my_port_driver"
#define IOCTL_WRITE_PORT _IOW('k', 1, int)

int main() {
    int fd = open(DEVICE_NAME, O_RDWR);

    if (fd < 0) {
        perror("Failed to open device file");
        return -1;
    }

    int value = 100;
    if (ioctl(fd, IOCTL_WRITE_PORT, &value) < 0) {
        perror("Failed to write to port");
        return -1;
    }

    close(fd);

    return 0;
}

这个程序会向端口0x378写入数值100。

0
广告 广告

评论区