驱动DAY4 字符设备驱动分步注册和ioctl函数点亮LED灯

news2024/12/30 3:15:43

头文件

#ifndef __HEAD_H__
#define __HEAD_H__ 
typedef struct{
    unsigned int MODER;
    unsigned int OTYPER;
    unsigned int OSPEEDR;
    unsigned int PUPDR;
    unsigned int IDR;
    unsigned int ODR;
}gpio_t;
#define PHY_LED1_ADDR 0X50006000
#define PHY_LED2_ADDR    0X50007000
#define PHY_LED3_ADDR 0X50006000
#define PHY_RCC_ADDR    0X50000A28

#define LED_ON _IOW('l',1,int)//开灯
#define LED_OFF _IOW('l',0,int)//关灯

#endif 

驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include "head.h"
#include <linux/slab.h>
#include <linux/io.h>

struct cdev *cdev;
unsigned int major = 0;
unsigned int minor = 0;
dev_t devno;
struct class *cls;
struct device *dev;

char kbuf[128] = {0};
gpio_t *vir_led1;
gpio_t *vir_led2;
gpio_t *vir_led3;
unsigned int *vir_rcc;

// 封装操作方法
int mycdev_open(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    // 根据用户空间功能码的不同实现硬件不同的控制
    switch (cmd)
    {
    case LED_ON: // 开灯
        switch (arg)
        {
        case 1:
            vir_led1->ODR |= (0X1 << 10);
            break;
        case 2:
            vir_led2->ODR |= (0X1 << 10);
            break;
        case 3:
            vir_led3->ODR |= (0X1 << 8);
            break;
        default:
            printk("灯选择输入错误\n");
            break;
        }
        break;
    case LED_OFF: // 关灯
        switch (arg)
        {
        case 1:
            vir_led1->ODR &= (~(0X1 << 10));
            break;
        case 2:
            vir_led2->ODR &= (~(0X1 << 10));
            break;
        case 3:
            vir_led3->ODR &= (~(0X1 << 8));
            break;
        default:
            printk("灯选择输入错误\n");
            break;
        }
        break;
    default:
        printk("开关灯选择输入错误\n");
        break;
    }
    return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
// 定义操作方法结构体遍历并且初始化
struct file_operations fops = {
    .open = mycdev_open,
    .unlocked_ioctl = mycdev_ioctl,
    .release = mycdev_close,
};

int all_led_init(void)
{
    // 寄存器地址的映射
    vir_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
    if (vir_led1 == NULL)
    {
        printk("ioremap filed:%d\n", __LINE__);
        return -ENOMEM;
    }
    vir_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
    if (vir_led2 == NULL)
    {
        printk("ioremap filed:%d\n", __LINE__);
        return -ENOMEM;
    }
    vir_led3 = vir_led1;
    vir_rcc = ioremap(PHY_RCC_ADDR, 4);
    if (vir_rcc == NULL)
    {
        printk("ioremap filed:%d\n", __LINE__);
        return -ENOMEM;
    }
    printk("物理地址映射成功\n");
    // 寄存器的初始化
    // rcc
    (*vir_rcc) |= (3 << 4);
    // led1
    vir_led1->MODER &= (~(3 << 20));
    vir_led1->MODER |= (1 << 20);
    vir_led1->ODR &= (~(1 << 10));
    // led2
    vir_led2->MODER &= (~(3 << 20));
    vir_led2->MODER |= (1 << 20);
    vir_led2->ODR &= (~(1 << 10));
    // led3
    vir_led3->MODER &= (~(3 << 16));
    vir_led1->MODER |= (1 << 16);
    vir_led1->ODR &= (~(1 << 8));
    printk("寄存器初始化成功\n");

    return 0;
}

static int __init mycdev_init(void)
{
    int ret;
    // 1.分配字符设备驱动对象
    cdev = cdev_alloc();
    if (cdev == NULL)
    {
        printk("申请字符设备驱动对象失败\n");
        ret = -EFAULT;
        goto OUT1;
    }
    printk("申请字符设备驱动成功\n");
    // 2.初始化字符设备驱动对象
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) // 静态指定
    {
        ret = register_chrdev_region(MKDEV(major, minor), 3, "mycdev");
        if (ret)
        {
            printk("静态指定设备号失败\n");
            goto OUT2;
        }
    }
    else
    {
        ret = alloc_chrdev_region(&devno, minor, 3, "mycdev");
        if (ret)
        {
            printk("静态指定设备号失败\n");
            goto OUT2;
        }
        minor = MINOR(devno); // 根据设备号获取次设备号
        major = MAJOR(devno); // 根据设备号获取主设备号
    }
    printk("申请设备号成功\n");
    // 4.注册字符设备驱动对象
    ret = cdev_add(cdev, MKDEV(major, minor), 3);
    if (ret)
    {
        printk("注册字符设备驱动对象失败\n");
        goto OUT3;
    }
    printk("注册字符设备驱动对象成功\n");
    // 向上提交目录
    cls = class_create(THIS_MODULE, "mycdev");
    if (IS_ERR(cls))
    {
        printk("向上提交目录失败\n");
        ret = -PTR_ERR(cls);
        goto OUT4;
    }
    printk("向上提交目录成功\n");
    // 向上提交设备节点信息
    int i=0;
    for (i = 0; i < 3; i++)
    {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
        if (IS_ERR(dev))
        {
            printk("向上提交设备节点信息失败\n");
            ret = -PTR_ERR(dev);
            goto OUT5;
        }
    }
    printk("向上提交设备节点成功\n");

    // 寄存器映射以及初始化
    all_led_init();

    return 0;
OUT5:
    for (--i; i >= 0; i--)
    {
        device_destroy(cls, MKDEV(major, i)); // 释放提交成功的设备信息
    }
    class_destroy(cls); // 销毁目录
OUT4:
    cdev_del(cdev);
OUT3:
    unregister_chrdev_region(MKDEV(major, minor), 3);
OUT2:
    kfree(cdev);
OUT1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    // 销毁设备节点信息
    int i;
    for (i = 0; i < 3; i++)
    {
        device_destroy(cls, MKDEV(major, i));
    }
    // 销毁目录
    class_destroy(cls);
    // 注销字符设备驱动对象
    cdev_del(cdev);
    // 释放设备号
    unregister_chrdev_region(MKDEV(major, minor), 3);
    // 释放对象空间
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

测试代码

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

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int a, b;
    int fd = open("/dev/mycdev0", O_RDWR);
    if (fd < 0)
    {
        printf("打开设备文件失败\n");
        exit(-1);
    }
    while (1)
    {
        printf("请输入要实现的功能:1(开灯)0(关灯>)");
        scanf("%d", &a);
        printf("请输入要控制的灯:1(LED1) 2(LED2) 3(LED3)>");
        scanf("%d", &b);
        switch (a)
        {
        case 1:
            ioctl(fd, LED_ON, b);
            break;
        case 0:
            ioctl(fd, LED_OFF, b);
            break;
        default:
            printf("开关灯功能选择输入错误\n");
            break;
        }
    }

    close(fd);

    return 0;
}

Makefile

#通用makefile
modname ?= demo
arch ?= arm
ifeq ($(arch),arm)
KERNELDIR:= /home/ubuntu/FSMP1A/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61
#生成ARM架构
else
KERNELDIR:= /lib/modules/$(shell uname -r)/build
#生成x86架构
endif

PWD:= $(shell pwd)
all:
	make -C $(KERNELDIR) M=$(PWD) modules

clean:
	make -C $(KERNELDIR) M=$(PWD) clean

obj-m:= $(modname).o

实验现象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/890436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Next.js - Pages and Layouts

Pages 页面是路由独有的用户界面。你可以通过从 page.js 文件导出组件来定义页面。使用嵌套文件夹定义路由&#xff0c;并使用 page.js 文件公开访问路由。 // app/page.tsx is the UI for the / URL export default function Page() {return <h1>Hello, Home page!<…

python爬虫7:实战1

python爬虫7&#xff1a;实战1 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论&#xff0c;并不会对网站产生不好…

QT学习笔记-Linux ARM环境下实现QT程序通过ODBC驱动访问SQLServer数据库

QT学习笔记-Linux ARM环境下实现QT程序通过ODBC驱动访问SQLServer数据库 0、背景1、基本环境2、搭建交叉编译环境3、在交叉编译服务器上交叉编译安装unixODBC3.1 下载unixODBC3.2 交叉编译unixODBC3.2.1 基本编译说明3.2.2 交叉编译说明3.2.3 ./configure -build,-host,-target…

iTOP-RK3399开发板开发板作为客户端- ssh使用

默认使用本地用户为远程登录时的用户&#xff1b;远程连接虚拟机里面的 Ubuntu&#xff08;IP 地址为 192.168.1.20&#xff09;&#xff0c; 如下图所示&#xff1a; ssh 192.168.1.20 以指定用户身份连接至服务器,输入以下命令&#xff1a; 输入 exit 退出 ssh 登录。 更多…

基础设施SIG月度动态:龙蜥大讲堂 - 基础设施系列专题分享火热进行中(7~8 月上旬持续分享),敬请关注!

基础设施 SIG&#xff08;OpenAnolis Infra SIG&#xff09;目标&#xff1a;负责 OpenAnolis 社区基础设施工程平台的建设&#xff0c;包括官网、Bugzilla、Maillist、ABS、ANAS、CI 门禁以及社区 DevOps 相关的研发工程系统。 01 SIG 整体进展 1、龙蜥大讲堂 - 基础设施系…

Vue3和Vue2对比学习之全局 API 应用实例

文章目录 0.前言1.参考文档2.详细说明2.1 全局 API 应用实例 非兼容2.2 一个新的全局 API&#xff1a;createAppconfig.productionTip 移除config.ignoredElements 替换为 config.isCustomElementVue.prototype 替换为 config.globalPropertiesVue.extend 移除类型推断组件继承…

最新年轻人副业赚钱攻略

最近几年&#xff0c;随着互联网的快速发展和社会变革的加速&#xff0c;年轻人副业赚钱的需求也越来越大。无论是为了增加收入、提升个人能力还是实现自我价值&#xff0c;年轻人们都纷纷开始寻找副业赚钱的机会。 那么&#xff0c;如何才能在副业赚钱中脱颖而出呢&#xff1…

软考笔记——10.项目管理

进度管理 进度管理就是采用科学的方法&#xff0c;确定进度目标&#xff0c;编制进度计划和资源供应计划&#xff0c;进行进度控制&#xff0c;在与质量、成本目标协调的基础上&#xff0c;实现工期目标。 具体来说&#xff0c;包括以下过程&#xff1a; (1) 活动定义&#…

Appium-移动端自动测试框架,如何入门?

Appium是一个开源跨平台移动应用自动化测试框架。 既然只是想学习下Appium如何入门&#xff0c;那么我们就直奔主题。文章结构如下&#xff1a; 1、为什么要使用Appium&#xff1f; 2、如何搭建Appium工具环境?(超详细&#xff09; 3、通过demo演示Appium的使用 4、Appium如何…

PD协议受电端芯片是什么?——浅谈快充协议在Hi003/XSP16应用

PD协议的全称是USB Power Delivery协议&#xff0c;该协议是基于USB 3.1中的Type-C接口&#xff0c;所以一般使用Type-C接口的充电器都有PD协议。 目前充电器常见的USB-PD功率为&#xff1a;5V3A&#xff08;15W&#xff09;、9V2A&#xff08;18W&#xff09;、12V3A&#xf…

扩散模型实战(四):从零构建扩散模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 本文以MNIST数据集为例&#xff0c;从…

【Linux】POSIX信号量和基于环形队列的生产消费者模型

目录 写在前面的话 什么是POSIX信号量 POSIX信号量的使用 基于环形队列的生产消费者模型 写在前面的话 本文章主要先介绍POSIX信号量&#xff0c;以及一些接口的使用&#xff0c;然后再编码设计一个基于环形队列的生产消费者模型来使用这些接口。 讲解POSIX信号量时&#x…

阿里云服务器镜像大全_Linux和Windows操作系统清单

阿里云服务器操作系统大全&#xff0c;阿里云提供的镜像均为正版授权&#xff0c;正版镜像可以在云服务器ECS上运行的应用程序提供安全、稳定的运行环境系统&#xff0c;阿里云服务器以公共镜像为例分享阿里云服务器操作系统大全&#xff0c;包括Alibaba Cloud Linux镜像、Linu…

洁净室气流流型分类、检测及相关法规要求概览

洁净室气流形式可以分为单向流或非单向流两种。如果综合利用两种气流&#xff0c;通常叫做混合气流。单向流可以是垂直的或水平的。两种单向流都以最终过滤的送风和回风入口&#xff0c;它们几乎是相对设置&#xff0c;这样才能使气流的流动形式保持尽可能的呈直线状。确认气流…

java全局捕捉异常并返回统一返回值

1. 首先定义全局异常处理类 import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.…

vcsa6.7 vsan超融合,虚拟机突然识别不到加密狗

问题现象&#xff1a; vcsa6.7 vsan超融合&#xff0c;虚拟机突然识别不到加密狗&#xff0c;加密狗直通给虚拟机&#xff0c;打开设备管理器&#xff0c;有未知的usb设备&#xff0c;重启虚拟机&#xff0c;卸载tools&#xff0c;重新安装tools&#xff0c;都不行 问题解决…

面向对象编程(OOP):Python中的抽象与封装

文章目录 &#x1f340;引言&#x1f340; 类与对象&#x1f340;封装&#x1f340;继承&#x1f340;多态&#x1f340;面向对象编程的优势&#x1f340;使用面向对象编程的场景&#x1f340;实例化与构造函数&#x1f340; 成员属性和类属性&#x1f340;魔术方法&#x1f34…

opencv基础:几个常用窗口方法

开始说了一些opencv中的一些常用方法。 namedWindow方法 在OpenCV中&#xff0c;namedWindow函数用于创建一个窗口&#xff0c;并给它指定一个名字。这个函数的基本语法如下&#xff1a; import cv2cv2.namedWindow(窗口名称, 标识 )窗口名称&#xff1a;其实窗口名称&…

SqlServer Management工具格式化代码、美化SQL

1、下载simple but powerful SQL formatter 插件下载地址&#xff1a;地址 2、安装 点击next—> finish 3、重新打开SqlServer Management即可看到 SQL Beautifier 点击Format ALL SQL 或者选中sql点击Format Selected SQL即可

Linux的基本权限(文件,目录)

文章目录 前言一、Linux权限的概念二、Linux权限管理 1.文件访问者分类2.文件类型和访问类型3.文件访问权限的相关设置方法三、目录的权限四、权限的总结 前言 Linux下一切皆文件&#xff0c;指令的本质就是可执行文件&#xff0c;直接安装到了系统的某种路径下 一、Linux权限的…