驱动开发_驱动开发入门 - CSDN
精华内容
参与话题
  • 从零开始之驱动开发、linux驱动(一、驱动基础)

    万次阅读 多人点赞 2018-10-08 20:28:15
    准备19年在深圳这边找驱动相关的工作了,所以从头开始再学一遍韦东山老师的驱动课程,并做好记录,希望能找到满意的工作。 同时该系列的驱动都是在前面新移植的linux3.16.57的基础上工作的。   之前的驱动模型...

     

    准备19年在深圳这边找驱动相关的工作了,所以从头开始再学一遍韦东山老师的驱动课程,并做好记录,希望能找到满意的工作。

    同时该系列的驱动都是在前面新移植的linux3.16.57的基础上工作的。

     

    之前的驱动模型学习和分析,从框架上了解的驱动的组织形式(在4.9的内核的基础上分析的)。

    https://blog.csdn.net/qq_16777851/article/category/7901554

     

     

    在ARM工作模式中,处理器模式切换可以通过软件控制进行切换,即修改CPSR模式位,但这是在特权模式下,当我们处于用户模式下,是没有权限实现模式转换的。若想实现模式切换,只能由另一种方法来实现,即通过中断或是异常处理过程进行切换。于是ARM指令集中提供了两条产生异常的指令,通过这两条指令可以用软件的方法实现异常,其中一个就是中断指令swi(另一个是断点中断BKPT ).

     

     软件中断指令(Software Interrupt, swi)用于产生软中断,实现从用户模式变换到管理模式,CPSR保存到管理模式的SPSR中,执行转移到swi向量。在其他模式下也可以使用SWI指令,处理器同样切换到管理模式。

    1.SWI指令格式如下:

    cond  是执行指令的条件

    immed_24  24位立即数,值为从0――16777215之间的整数

    SWI指令后面的24立即数是干什么用的呢?用户程序通过SWI指令切换到特权模式,进入软中断处理程序,但是软中断处理程序不知道用户程序到底想要做什么?SWI指令后面的24位用来做用户程序和软中断处理程序之间的接头暗号。通过该软中断立即数来区分用户不同操作,执行不同内核函数。如果用户程序调用系统调用时传递参数,根据ATPCSC语言与汇编混合编程规则将参数放入R0~R4即可。

     

    使用SWI指令时,通常使用以下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。SWI异常中断处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。

    1)、指令中24位的立即数指定了用户请求的服务类型,中断服务的参数通过通用寄存器传递。

    如下面这个程序产生一个中断号位12 的软中断:

    MOV R0,#34                    ;设置功能号为34
    
    SWI 12                              ;产生软中断,中断号为12

    2)、指令中的24位立即数被忽略,用户请求的服务类型有寄存器R0的值决定,参数通过其他的通用寄存器传递。

    如下面的例子通过R0传递中断号,R1传递中断的子功能号:

    MOV R0, #12                  ;设置12号软中断
    
    MOV R1, #34                  ;设置功能号为34
    
    SWI  0

     

     操作系统的主要功能是为应用程序的运行创建良好的环境,保障每个程序都可以最大化利用硬件资源,防止非法程序破坏其它应用程序执行环境,为了达到这个目的,操作系统会将硬件的操作权限交给内核程序来管理,用户程序不能随意使用硬件,使用硬件(对硬件寄存器进行读写)时要先向操作系统发出请求,操作系统内核帮助用户程序实现其操作,也就是说用户程序不会直接操作硬件,而是提供给用户程序一些具备预定功能的内核函数,通过一组称为系统调用的(system call)的接口呈现给用户,系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序。

     

    操作系统里将用户程序运行在用户模式下,并且为其分配可以使用内存空间,其它内存空间不能访问,内核态运行在特权模式下,对系统所有硬件进行统一管理和控制。从前面所学知识可以了解到,用户模式下没有权限进行模式切换,这也就意味着用户程序不可能直接通过切换模式去访问硬件寄存器,如果用户程序试图访问没有权限的硬件,会产生异常。这样用户程序被限制起来,如果用户程序想要使用硬件时怎么办呢?用户程序使用硬件时,必须调用操作系统提供的API接口才可以,而操作系统API接口通过软件中断方式切换到管理模式下,实现从用户模式下进入特权模式。

    在3.16.57的内核中总共有382个系统调用

    arch/arm/kernel/colls.S
    /* 0 */		CALL(sys_restart_syscall)
    		CALL(sys_exit)
    		CALL(sys_fork)
    		CALL(sys_read)
    		CALL(sys_write)
    /* 5 */		CALL(sys_open)
    		CALL(sys_close)
    		CALL(sys_ni_syscall)		/* was sys_waitpid */
    		CALL(sys_creat)
    		CALL(sys_link)
    
            ......
    
    /* 375 */	CALL(sys_setns)
    		CALL(sys_process_vm_readv)
    		CALL(sys_process_vm_writev)
    		CALL(sys_kcmp)
    		CALL(sys_finit_module)
    /* 380 */	CALL(sys_sched_setattr)
    		CALL(sys_sched_getattr)
    		CALL(sys_renameat2)

    其中CALL的定义如下,可以看出是直接定义为代码段的某个地址了,方便数组下标索引,可以看到sys_open的偏移是5

    #define CALL(x) .long x

     

    EABI (Extended ABI)

    CONFIG_OABI_COMPAT    //表示老的系统调用接口
    CONFIG_AEABI          //新的系统调用接口

     

    EABI ,说的是这样的一种新的系统调用方式

    mov r7, #num

    swi 0x0

    原来的系统调用方式是这样,

    swi (#num | 0x900000) (0x900000是个magic值)

    也就是说原来的调用方式(Old ABI)是通过跟随在swi指令中的调用号来进行的,现在的是根据r7中的值。

    现在看两个宏,一个是

    CONFIG_OABI_COMPAT 意思是说和old ABI兼容

    另一个是

    CONFIG_AEABI 意思是说指定现在的方式为EABI

    这两个宏可以同时配置,也可以都不配,也可以配置任何一种。

    我说一下内核是怎么处理这一问题的。

    我们知道,sys_call_table 在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针就是系统调用函数的指针,如(sys_open).系统调用是根据一个调用号(通常就是表的索引)找到实际该调用内核哪个函数,然后运行该函数完成的。

    首先,对于old ABI,内核给出的处理是给它建立一个单独的system calltable,叫sys_oabi_call_table,这样,兼容方式下就会有两个system call table, 以oldABI方式的系统调用会执行old_syscall_table表中的系统调用函数,EABI方式的系统调用会用sys_call_table中的函数指针

    配置无外乎以下4中

    第一 两个宏都配置 行为就是上面说的那样

    第二 只配置CONFIG_OABI_COMPAT , 那么以old ABI方式调用的会用sys_oabi_call_table,以EABI方式调用的 用sys_call_table,和1实质相同,只是情况1更加明确。

    第三 只配置CONFIG_AEABI 系统中不存在 sys_oabi_call_table, 对old ABI方式调用不兼容。只能 以EABI方式调用,用sys_call_table

    第四 两个都没有配置 系统默认会只允许old ABI方式,但是不存在old_syscall_table,最终会通过sys_call_table 完成函数调用

     

     

    用户空间如何让产生系统调用,即触发SWI异常

    就看定义编译内核是默认是OABI还是EABI,可以看到新的EABI是r7传参方式

    #ifndef CONFIG_CPU_THUMBONLY
    #define ARM_OK(code...)	code        //正常都是这种
    #else
    #define ARM_OK(code...)
    #endif
    
    
    	.align
    
    sigreturn_codes:
    
    	/* ARM sigreturn syscall code snippet */
    	arm_slot 0
    ARM_OK(	mov	r7, #(__NR_sigreturn - __NR_SYSCALL_BASE)	)
    ARM_OK(	swi	#(__NR_sigreturn)|(__NR_OABI_SYSCALL_BASE)	)
    
    	/* Thumb sigreturn syscall code snippet */
    	thumb_slot 0
    	movs	r7, #(__NR_sigreturn - __NR_SYSCALL_BASE)
    	swi	#0
    
    	/* ARM sigreturn_rt syscall code snippet */
    	arm_slot 1
    ARM_OK(	mov	r7, #(__NR_rt_sigreturn - __NR_SYSCALL_BASE)	)
    ARM_OK(	swi	#(__NR_rt_sigreturn)|(__NR_OABI_SYSCALL_BASE)	)
    
    	/* Thumb sigreturn_rt syscall code snippet */
    	thumb_slot 1
    	movs	r7, #(__NR_rt_sigreturn - __NR_SYSCALL_BASE)
    	swi	#0

     

     

     

    通过上面的calls.S和下面两句可以知道,sys_call_table就是系统调用表的首地址。而#include "call.S"里面的内容在前面已经说明了,

    就是以 .long sys_xxx   的函数的地址

    	.type	sys_call_table, #object
    ENTRY(sys_call_table)
    #include "calls.S"

    方然也有老的调用接口,但对里面的系统调用函数,明显都是一样的

    	.type	sys_oabi_call_table, #object
    ENTRY(sys_oabi_call_table)
    #include "calls.S"

     

     

    下面就是SWI异常处理函数的实现

    
    /*=============================================================================
     * SWI handler
     *-----------------------------------------------------------------------------
     */
    
    	.align	5
    ENTRY(vector_swi)
    #ifdef CONFIG_CPU_V7M
    	v7m_exception_entry
    #else
    	sub	sp, sp, #S_FRAME_SIZE
    	stmia	sp, {r0 - r12}			@ Calling r0 - r12
     ARM(	add	r8, sp, #S_PC		)
     ARM(	stmdb	r8, {sp, lr}^		)	@ Calling sp, lr
     THUMB(	mov	r8, sp			)
     THUMB(	store_user_sp_lr r8, r10, S_SP	)	@ calling sp, lr
    	mrs	r8, spsr			@ called from non-FIQ mode, so ok.
    	str	lr, [sp, #S_PC]			@ Save calling PC
    	str	r8, [sp, #S_PSR]		@ Save CPSR
    	str	r0, [sp, #S_OLD_R0]		@ Save OLD_R0
    #endif
    	zero_fp
    	alignment_trap ip, __cr_alignment
    	enable_irq
    	ct_user_exit
    	get_thread_info tsk
    
    	/*
    	 * Get the system call number.
    	 */
    
    #if defined(CONFIG_OABI_COMPAT)
    
    	/*
    	 * If we have CONFIG_OABI_COMPAT then we need to look at the swi
    	 * value to determine if it is an EABI or an old ABI call.
    	 */
    #ifdef CONFIG_ARM_THUMB
    	tst	r8, #PSR_T_BIT
    	movne	r10, #0				@ no thumb OABI emulation
     USER(	ldreq	r10, [lr, #-4]		)	@ get SWI instruction
    #else
     USER(	ldr	r10, [lr, #-4]		)	@ get SWI instruction
    #endif
     ARM_BE8(rev	r10, r10)			@ little endian instruction
    
    #elif defined(CONFIG_AEABI)
    
    	/*
    	 * Pure EABI user space always put syscall number into scno (r7).
    	 */
    #elif defined(CONFIG_ARM_THUMB)
    	/* Legacy ABI only, possibly thumb mode. */
    	tst	r8, #PSR_T_BIT			@ this is SPSR from save_user_regs
    	addne	scno, r7, #__NR_SYSCALL_BASE	@ put OS number in
     USER(	ldreq	scno, [lr, #-4]		)
    
    #else
    	/* Legacy ABI only. */
     USER(	ldr	scno, [lr, #-4]		)	@ get SWI instruction
    #endif
    
    	adr	tbl, sys_call_table		@ load syscall table pointer
    
    #if defined(CONFIG_OABI_COMPAT)
    	/*
    	 * If the swi argument is zero, this is an EABI call and we do nothing.
    	 *
    	 * If this is an old ABI call, get the syscall number into scno and
    	 * get the old ABI syscall table address.
    	 */
    	bics	r10, r10, #0xff000000
    	eorne	scno, r10, #__NR_OABI_SYSCALL_BASE
    	ldrne	tbl, =sys_oabi_call_table
    #elif !defined(CONFIG_AEABI)
    	bic	scno, scno, #0xff000000		@ mask off SWI op-code
    	eor	scno, scno, #__NR_SYSCALL_BASE	@ check OS number
    #endif
    
    local_restart:
    	ldr	r10, [tsk, #TI_FLAGS]		@ check for syscall tracing
    	stmdb	sp!, {r4, r5}			@ push fifth and sixth args
    
    	tst	r10, #_TIF_SYSCALL_WORK		@ are we tracing syscalls?
    	bne	__sys_trace
    
    	cmp	scno, #NR_syscalls		@ check upper syscall limit
    	adr	lr, BSYM(ret_fast_syscall)	@ return address
    	ldrcc	pc, [tbl, scno, lsl #2]		@ call sys_* routine
    
    	add	r1, sp, #S_OFF
    2:	cmp	scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
    	eor	r0, scno, #__NR_SYSCALL_BASE	@ put OS number back
    	bcs	arm_syscall
    	mov	why, #0				@ no longer a real syscall
    	b	sys_ni_syscall			@ not private func
    
    #if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
    	/*
    	 * We failed to handle a fault trying to access the page
    	 * containing the swi instruction, but we're not really in a
    	 * position to return -EFAULT. Instead, return back to the
    	 * instruction and re-enter the user fault handling path trying
    	 * to page it in. This will likely result in sending SEGV to the
    	 * current task.
    	 */
    9001:
    	sub	lr, lr, #4
    	str	lr, [sp, #S_PC]
    	b	ret_fast_syscall
    #endif
    ENDPROC(vector_swi)
    

     

    真正的系统调用则是用过下面这个函数实现的。

    asmlinkage long sys_open(const char __user *filename,
    				int flags, umode_t mode);
    
    
    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
    {
    	if (force_o_largefile())
    		flags |= O_LARGEFILE;
    
    	return do_sys_open(AT_FDCWD, filename, flags, mode);
    }
    
    
    
    SYSCALL_DEFINE3是一个宏,可以自己解析一下,实际下面的两个是一样的
    asmlinkage long sys_open(const char __user *filename,int flags, umode_t mode);
    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode);

     

     

     

    对照上图,可以看到应用程序使用系统调用接口,需要从用户级切换到内核级。

    方法是:通过SWI指令和寄存器传入参数,通过输入的swi中断号,直接以查表方式找到对应的系统调用函数。

    以open一个led灯为例:

    可以看到调用顺序依次是应用程序 open ->  swi软中断 -> 系统调用接口  -> sys_open  ->

    文件子系统VFS中的open -> 驱动程序open  -> 硬件操作

     

    其中应用程序空间的open需要通过swi指令来实现swi中断。

    通过文件的属性(普通文件,设备文件),来不同的处理。

    如果是设备文件,则继续通过属性查看是字符还是块设备文件,找到对应的驱动程序,最终操纵硬件。

     

    展开全文
  • 驱动开发入门总结

    千次阅读 2020-03-11 04:15:58
    初学Windows驱动开发的一些总结

    1.简单的驱动框架

    开发环境为VS2019,新建项目选择Kernel Model Driver,Empty(KMDF).
    为项目添加一个DriverEntry.c文件.注意!是.c文件
    项目属性:
    1.C\C++:警告等级设置为3级,警告视为错误设置为否,SDL检查设置为否;
    2.Driver Setting:Target OS Version设置为要开发的Windows系统平台的版本
    (这里Windows 7),Target Platform设置为Desktop;
    3.Stamplnf:Enable Architectrue设置为否;
    4.Inf2Cat:Run Inf2Cat暂时不需要,设置为否;
    5.删除Driver Files下面的inf文件

    定义驱动的入口函数和卸载函数:

    #include<ntifs.h>
    
    //声明卸载函数(卸载前要做什么)
    void DriverUnload(PDRIVER_OBJECT driver);
    
    //入口函数
    NTSTATUS DriverEntry(PDRIVER_OBJECT driver, UNICODE_STRING path)
    {
    	driver->DriverUnload = DriverUnload;
    
    	return STATUS_SUCCESS;
    }
    
    void DriverUnload(PDRIVER_OBJECT driver)
    {
    	KdPrint(("驱动已卸载"));
    }
    

    2.创建驱动设备与绑定符号链接

    1.调用IoCreateDevice()创建IO设备
    2.通过IoCreateSymbolicLink创建符号链接并绑定IO设备
    注意:UNICODE_STRING类型的变量声明后需要用RtlInitUnicodeString()初始化

    NTSTATUS CreateDevice(PDRIVER_OBJECT driver)
    {
    	NTSTATUS status = STATUS_SUCCESS;
    	UNICODE_STRING DeviceName;
    	PDEVICE_OBJECT MyDevice=NULL;
    
    	//初始化DeviceName
    	RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDevice");
    
    	status = IoCreateDevice(
    		driver,
    		sizeof(driver->DriverExtension),
    		&DeviceName,
    		FILE_DEVICE_UNKNOWN,//设备类型
    		FILE_DEVICE_SECURE_OPEN,//设备特性
    		FALSE,//这个设备是否是独占的
    		&MyDevice
    	);
    
    	if (status == STATUS_SUCCESS)
    	{
    		KdPrint(("设备已创建:%xZ",DeviceName));
    
    		UNICODE_STRING SymbolName;
    		RtlInitUnicodeString(&SymbolName, L"\\??\\MySymbol");
    
    		//创建符号链接
    		status = IoCreateSymbolicLink(&SymbolName, &DeviceName);
    
    		if (status == STATUS_SUCCESS)
    		{
    			KdPrint(("创建符号链接失败!"));
    		}
    		else
    		{
    			KdPrint(("符号链接已创建: %xZ",SymbolName));
    		}
    	}
    	else
    	{
    		KdPrint(("设备创建失败!"));
    		IoDeleteDevice(MyDevice);
    	}
    }
    

    3.IRP派遣函数

    关于派遣函数的解释
    1.用IoGetCurrentIrpStackLocation来获取应用层传来的消息
    2.使用switch语句调用对应的消息处理函数
    3.用IoCompleteRequest返回数据给应用层
    4.绑定消息处理函数

    NTSTATUS DriverIrpCtrl(PDEVICE_OBJECT devoce, PIRP pirp)
    {
    	PIO_STACK_LOCATION IrpStack=NULL;
    	ULONG CtrlCode = 0;//控制码
    	ULONG InputBuffSize=0;//接收输入的缓冲区长度
    
    	//获取应用层传来的消息
    	IrpStack = IoGetCurrentIrpStackLocation(pirp);
    
    	switch (IrpStack->MajorFunction)
    	{
    
    	//设备读写消息
    	case IRP_MJ_DEVICE_CONTROL:
    	{
    		KdPrint(("设备读写消息"));
    		//处理设备读写的函数
    		DoMessage_RW(pirp);
    		break;
    	}
    
    	//设备打开消息
    	case IRP_MJ_CREATE:
    	{
    		KdPrint(("设备打开消息"));
    		break;
    	}
    
    	//设备关闭消息
    	case IRP_MJ_CLOSE:
    	{
    		KdPrint(("设备关闭消息"));
    		break;
    	}
    
    	}
    
    	pirp->IoStatus.Status = STATUS_SUCCESS;
    	//返回数据给应用层
    	IoCompleteRequest(pirp, IO_NO_INCREMENT);
    
    	return STATUS_SUCCESS;
    }
    

    绑定消息处理函数,需要在DriverEntry中调用

    void BindingDriverIrpCtrl(PDRIVER_OBJECT driver)
    {
    	driver->MajorFunction[IRP_MJ_CREATE] = DriverIrpCtrl;
    	driver->MajorFunction[IRP_MJ_CLOSE] = DriverIrpCtrl;
    	driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIrpCtrl;
    }
    

    4.应用层与驱动层通信

    1.应用层:
    a.新建一个MFC工程(其他的也可以)
    b.用CreateFileW获取驱动IO设备的句柄
    c.用DeviceIoControl向驱动发送数据
    d.不再需要读写操作时,用CloseHandle关闭设备句柄
    2.驱动层:

    	//获取传入的数据
    	int *pInBuff = pirp->AssociatedIrp.SystemBuffer;
    	KdPrint(("传入参数: %d", pInBuff));
    
    	//处理传入的数据
    	*pInBuff = *pInBuff + 1234567;
    	
    	//返回数据(pInBuff也可以是其他)
    	pirp->IoStatus.Information = sizeof(pInBuff);//返回数据给DeviceIoControl倒数第二个参数(返回字节数)
    

    打开设备:

    bool OpenDriverDevice(LPCWSTR SymbolName)
    {
    	DeviceHandle = CreateFileW(SymbolName,
    		GENERIC_READ | GENERIC_WRITE,
    		FILE_SHARE_READ | FILE_SHARE_WRITE,
    		NULL,
    		OPEN_EXISTING,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL
    	);
    
    	if (DeviceHandle == NULL)
    	{
    		return false;
    	}
    	else
    	{
    		return true;
    	}
    }
    

    输入数据:

    int InputDevice(void* InBuff, DWORD InBuffSize,void* OutBuff, DWORD OutBuffSize,HANDLE DeviceHandle, DWORD CtrlCode)
    {
    	DWORD dwRetSize;
    
    	bool bRet = DeviceIoControl(DeviceHandle,
    		CtrlCode,//控制码
    		InBuff,//输入参数指针
    		InBuffSize,//输入参数大小
    		OutBuff,//返回缓冲器指针
    		OutBuffSize,//返回缓冲区大小
    		&dwRetSize,//返回缓冲区字节数
    		NULL
    	);
    
    	if (bRet)
    		return dwRetSize;
    	else
    		return -1;
    }
    

    关闭设备:

    void CloseDriverDevice(HANDLE DeviceHandle)
    {
    	CloseHandle(DeviceHandle);
    }
    

    5.驱动的加载与卸载

    1.加载驱动:
    a.用GetFullPathNameA获取驱动的完整路径
    b.用OpenSCManager打开服务控制管理器
    c.用CreateServiceA创建服务
    d.如果服务创建已存在,直接用OpenServiceA打开服务,否则用StartServiceA开启服务
    2.卸载驱动:
    a.用OpenSCManager打开服务控制管理器
    b.用OpenServiceA打开服务
    c.用ControlService停止驱动服务

    加载驱动:

    bool LoadDriver(const char* DriverName, const char* DriverFileName)
    {
    	char DriverImagePath[256] = { 0 };//用于保存驱动文件的全路径名
    
    	//获取驱动文件全路径名
    	GetFullPathNameA(DriverFileName, 256, DriverImagePath, NULL);
    
    	bool bRet = false;
    
    	SC_HANDLE hServiceMgr = NULL;//SCM管理器的句柄
    	SC_HANDLE hServiceDDX = NULL;//NT驱动服务的服务句柄
    
    	//打开SCM管理器
    	hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    
    	if (hServiceMgr != NULL)
    	{
    		//创建驱动服务
    		hServiceDDX = CreateServiceA(hServiceMgr,
    			DriverName,//驱动程序注册表中的名字
    			DriverName,//注册表中驱动程序的 DisPlayName 的值
    			SERVICE_START,//加载驱动的访问权限 SERVICE_START 或 SERVICE_ALL_ACCESS
    			SERVICE_KERNEL_DRIVER,//表示加载服务是驱动程序
    			SERVICE_DEMAND_START,//注册表驱动程序的 Start 值
    			SERVICE_ERROR_NORMAL,//注册表程序的 ErrorControl 值
    			DriverImagePath,
    			NULL, NULL, NULL, NULL, NULL
    		);
    	}
    	else
    	{
    		return false;
    	}
    
    	if (hServiceDDX==NULL)
    	{
    		DWORD errorCode = GetLastError();
    		if (errorCode == ERROR_SERVICE_EXISTS)
    		{
    			hServiceDDX = OpenServiceA(hServiceMgr, DriverName, SERVICE_ALL_ACCESS);
    			if (!hServiceDDX)
    			{
    				if (hServiceMgr)CloseServiceHandle(hServiceMgr);
    				return false;
    			}
    		}
    		else
    		{
    			CString str;
    			str.Format(L"错误代码:%d",errorCode);
    			MessageBox(NULL,str,L"",MB_OK);
    			return false;
    		}
    	}
    
    	bRet = StartService(hServiceDDX, NULL, NULL);
    
    	if (hServiceDDX)
    	{
    		CloseServiceHandle(hServiceDDX);
    	}
    
    	if (hServiceMgr)
    	{
    		CloseServiceHandle(hServiceMgr);
    	}
    
    	return bRet;
    }
    

    卸载驱动:

    bool UnLoadDriver(const char* DriverName)
    {
    	bool bRet = false;
    	SC_HANDLE hServiceMgr = NULL;//SCM管理器的句柄
    	SC_HANDLE hServiceDDX = NULL;//NT驱动程序的服务句柄
    
    	SERVICE_STATUS Svrsta;
    	//先关闭设备
    	if (DeviceHandle) CloseHandle(DeviceHandle);
    	//打开SCM管理器
    	hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    	if (hServiceMgr == NULL)
    	{
    		bRet = FALSE;
    		goto BeforeLeave;
    	}
    
    	//打开驱动所有对应服务
    	hServiceDDX = OpenServiceA(hServiceMgr, DriverName, SERVICE_ALL_ACCESS);
    
    	if (hServiceDDX == NULL)
    	{
    		bRet = false;
    		goto BeforeLeave;
    	}
    
    	//停止驱动程序
    	ControlService(hServiceDDX, SERVICE_CONTROL_STOP, &Svrsta);
    
    	//动态卸载驱动程序
    	bRet = DeleteService(hServiceDDX);
    
    BeforeLeave:
    	if (hServiceDDX)
    	{
    		CloseServiceHandle(hServiceDDX);
    	}
    	if (hServiceMgr)
    	{
    		CloseServiceHandle(hServiceMgr);
    	}
    	return bRet;
    }
    

    驱动开发方面我是个刚刚入门的萌新,如果我写的有问题望多多指教,谢谢!

    展开全文
  • 因此,今天用了一些时间,去简单的了解了一下驱动及驱动开发。如果有错误的理解,请予以指正,不胜感激! 什么是驱动 在计算中,设备驱动程序是一种计算机程序,用于操作或控制连接到计算机的特定类型的设备。驱动...

    一直对驱动有着强烈的好奇心,怎奈工作始终与其无缘,且未来也不大可能接触驱动。因此,今天用了一些时间,去简单的了解了一下驱动及驱动开发。如果有错误的理解,请予以指正,不胜感激!

    什么是驱动

    在计算中,设备驱动程序是一种计算机程序,用于操作或控制连接到计算机的特定类型的设备。驱动程序提供了与硬件设备的软件接口,使操作系统和其他计算机程序可以访问硬件功能,而无需了解有关所使用硬件的精确细节。

    驱动程序通过硬件连接到的计算机总线或通信子系统与设备进行通信。当调用程序调用驱动程序中的例程时,驱动程序向设备发出命令。设备将数据发送回驱动程序后,驱动程序可以调用原始调用程序中的例程。驱动程序依赖于硬件且特定于操作系统。它们通常为那些有必要的时间异步的硬件接口提供终端处理。

    驱动开发是什么样子的

    有两篇博客,很好的讲述了驱动开发大概什么样子的,感谢作者:

    需要关注的一些点

    • ioctl
    • writel and readl
    • ioremap, 通过数据手册可以找到每个端口的物理地址. #define FS4412_GPF3CON 0x114001E0

    (摘)系统驱动和裸机平台设备驱动的不同

    第一次接触带有操作系统的驱动编程,之前感觉系统驱动比较神秘的面纱也被揭开了,跟裸机平台的设备驱动相比,区别就是

    1)系统给驱动的编程增加了一个框架,需要依照系统对于驱动的统一管理来实现框架的内容,比如增加moudle_init,在init中注册设备号 添加设备等, 这都是在告诉系统,我们写的驱动具体实现的东西在哪里。
    2)具体实际的动作,跟裸机驱动是一致的,因为驱动的本质就是直接操作硬件,而对于硬件的操作方法是硬件的数据手册里边统一定义的,手册没有改,对硬件的操作同样不会变化,
    3)系统实现控制驱动的通信方法,裸机驱动调用控制驱动的方法直接调用就可以,而系统中需要通过ioctl来实现应用驱动的程序跟驱动程序的通信控制。

    展开全文
  • linux驱动开发架构

    千次阅读 2019-07-30 16:57:29
    最近开始开发驱动,现总结通用驱动开发模型如下 驱动整体模型: 添加一个设备,多数需要用户空间下发指令等操作。那么有两个问题: kernel如何控制设备 用户空间如何和kernel中的驱动交互 问题1: kernel中有各种...

    驱动模型

    最近开始开发驱动,现总结通用驱动开发模型如下
    驱动整体模型:
    在这里插入图片描述
    添加一个设备,多数需要用户空间下发指令等操作。那么有两个问题:

    1. kernel如何控制设备
    2. 用户空间如何和kernel中的驱动交互

    问题1:
    kernel中有各种总线,设备挂载在总线上,驱动通过kernel总线提供的接口初始化控制设备。
    问题2:
    kernel中提供文件设备驱动,在驱动中增加一个文件设备,如字符设备、proc、sys等文件设备。

    基于以上两个问题,驱动包含两部分
    在这里插入图片描述

    开发设备驱动

    系统端驱动开发步骤

    1、阅读设备相关的规格书、demo
    2、确定设备挂载总线、文件交互设备
    3、参照demo,编写驱动代码,代码包含两部分:设备树添加结点、逻辑代码

    注意: 设备树结点中的字段可以是标准的(内核已有代码解析),也可以包含自定义的(设备驱动逻辑代码解析)。

    设备端基于单片机驱动开发

    设备端硬件形态有两种:
    1、设备端有一个独立的小系统,通过一个单片机运行
    2、设备端仅由电子原件和电路图实现

    形态1:
    一般情况下,该部分代码,设备厂商已提供,无需系统端负责开发。系统端通过总线和设备进行交互。

    形态2:
    设备端上电后,驱动实现初始化,控制设备端寄存器,配置设备以满足对设备功能、数据的需求

    应用场景

    以上描述的驱动开发架构模式,常用于应用级驱动开发。一些控制器类型,如i2c控制器、spi总线控制器等kernel中框架层的,一般不需要文件系统设备的存在,只需要开发设备驱动即可;为debug方便,一般也会搭配文件系统设备,方便命令行查看设备状态。

    在开发中,可以根据实际需求决定。

    DEMO

    需求描述

    给一个i2c编写驱动,i2c提供一些接口给userspace使用

    添加设备树结点
    // SoC上的i2c控制器的地址
    i2c@138B0000 {
    	#address-cells = <1>;
        #size-cells = <0>;
        samsung,i2c-sda-delay = <100>;
        samsung,i2c-max-bus-freq = <20000>;
        pinctrl-0 =<&i2c5_bus>;
        pinctrl-names="default";
        // 这个一定要okay,其实是对"./arch/arm/boot/dts/exynos4.dtsi +387"处的status = "disabled"的重写,
        // 相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写
        status="okay";
        // 设备子节点,/表示板子,它的子节点node1表示SoC上的某个控制器,
        // 控制器中的子节点node2表示挂接在这个控制器上的设备(们)。68即是设备地址。
        // 父结点是一个i2c总线,在此处定义i2c设备,设备i2c客户端自动和总线关联;
        // 否则,多个总线,设备i2c客户端驱动如何和总线关联?(待学习了解)
        mpu6050@68{
        	// 这个属性就是我们和驱动匹配的钥匙,一个字符都不能错
            compatible="invensense,mpu6050";
            // 这个属性是从设备的地址,我们可以通过查阅手册"MPU-6050_DataSheet_V3_4"得到
            reg=<0x68>;
        };
    };
    
    驱动实现
    //mpu6050_common.h
    #define MPU6050_MAGIC 'K'
    
    union mpu6050_data
    {
        struct {
            short x;
            short y;
            short z;
        }accel;
        struct {
            short x;
            short y;
            short z;
        }gyro;
        unsigned short temp;
    };
    
    #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)
    #define GET_GYRO  _IOR(MPU6050_MAGIC, 1, union mpu6050_data) 
    #define GET_TEMP  _IOR(MPU6050_MAGIC, 2, union mpu6050_data)
    
    //mpu6050_drv.h
    
    #define SMPLRT_DIV      0x19    //陀螺仪采样率,典型值:0x07(125Hz)
    #define CONFIG          0x1A    //低通滤波频率,典型值:0x06(5Hz)
    #define GYRO_CONFIG     0x1B    //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
    #define ACCEL_CONFIG        0x1C    //加速计自检、测量范围及高通滤波,典型值:0x18(不自检,2G,5Hz)
    #define ACCEL_XOUT_H        0x3B
    #define ACCEL_XOUT_L        0x3C
    #define ACCEL_YOUT_H        0x3D
    #define ACCEL_YOUT_L        0x3E
    #define ACCEL_ZOUT_H        0x3F
    #define ACCEL_ZOUT_L        0x40
    #define TEMP_OUT_H      0x41
    #define TEMP_OUT_L      0x42
    #define GYRO_XOUT_H     0x43
    #define GYRO_XOUT_L     0x44
    #define GYRO_YOUT_H     0x45
    #define GYRO_YOUT_L     0x46
    #define GYRO_ZOUT_H     0x47    //陀螺仪z轴角速度数据寄存器(高位)
    #define GYRO_ZOUT_L     0x48    //陀螺仪z轴角速度数据寄存器(低位)
    #define PWR_MGMT_1      0x6B    //电源管理,典型值:0x00(正常启用)
    #define WHO_AM_I        0x75    //IIC地址寄存器(默认数值0x68,只读)
    #define SlaveAddress        0x68    //MPU6050-I2C地址寄存器
    #define W_FLG           0
    #define R_FLG           1
    
    //mpu6050.c
    struct mpu6050_pri {
        struct cdev dev;
        struct i2c_client *client;
    };
    struct mpu6050_pri dev;
    static void mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val)
    { 
        char txbuf[2] = {reg,val};
        struct i2c_msg msg[2] = {
            [0] = {
                .addr = client->addr,
                .flags= W_FLG,
                .len = sizeof(txbuf),
                .buf = txbuf,
            },
        };
        i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
    }
    static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg)
    {
        char txbuf[1] = {reg};
        char rxbuf[1] = {0};
        struct i2c_msg msg[2] = {
            [0] = {
                .addr = client->addr,
                .flags = W_FLG,
                .len = sizeof(txbuf),
                .buf = txbuf,
            },
            [1] = {
                .addr = client->addr,
                .flags = I2C_M_RD,
                .len = sizeof(rxbuf),
                .buf = rxbuf,
            },
        };
    
        i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
        return rxbuf[0];
    }
    static int dev_open(struct inode *ip, struct file *fp)
    {
        return 0;
    }
    static int dev_release(struct inode *ip, struct file *fp)
    {
        return 0;
    }
    static long dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
    {
        int res = 0;
        union mpu6050_data data = {{0}};
        switch(cmd){
        case GET_ACCEL:
            data.accel.x = mpu6050_read_byte(dev.client,ACCEL_XOUT_L);
            data.accel.x|= mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8;
            data.accel.y = mpu6050_read_byte(dev.client,ACCEL_YOUT_L);
            data.accel.y|= mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8;
            data.accel.z = mpu6050_read_byte(dev.client,ACCEL_ZOUT_L);
            data.accel.z|= mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8;
            break;
        case GET_GYRO:
            data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L);
            data.gyro.x|= mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8;
            data.gyro.y = mpu6050_read_byte(dev.client,GYRO_YOUT_L);
            data.gyro.y|= mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8;
            data.gyro.z = mpu6050_read_byte(dev.client,GYRO_ZOUT_L);
            data.gyro.z|= mpu6050_read_byte(dev.client,GYRO_ZOUT_H)<<8;
            printk("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
            break;
        case GET_TEMP:
            data.temp = mpu6050_read_byte(dev.client,TEMP_OUT_L);
            data.temp|= mpu6050_read_byte(dev.client,TEMP_OUT_H)<<8;
            printk("temp: %d\n",data.temp);
            break;
        default:
            printk(KERN_INFO "invalid cmd");
            break;
        }
        printk("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
        res = copy_to_user((void *)arg,&data,sizeof(data));
        return sizeof(data);
    }
    
    // 初始化文件系统设备操作接口
    struct file_operations fops = {
        .open = dev_open,
        .release = dev_release,
        .unlocked_ioctl = dev_ioctl, 
    };
    
    #define DEV_CNT 1
    #define DEV_MI 0
    #define DEV_MAME "mpu6050"
    
    struct class *cls;
    dev_t dev_no ;
    
    static void mpu6050_init(struct i2c_client *client)
    {
        mpu6050_write_byte(client, PWR_MGMT_1, 0x00);
        mpu6050_write_byte(client, SMPLRT_DIV, 0x07);
        mpu6050_write_byte(client, CONFIG, 0x06);
        mpu6050_write_byte(client, GYRO_CONFIG, 0x18);
        mpu6050_write_byte(client, ACCEL_CONFIG, 0x0);
    }
    static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id)
    {
        dev.client = client;
        printk(KERN_INFO "xj_match ok\n");
        // 初始化设备文件系统
        cdev_init(&dev.dev,&fops);    
        alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME);    
        cdev_add(&dev.dev,dev_no,DEV_CNT);
        
        // 设备初始化
        mpu6050_init(client);
    
        /*自动创建设备文件*/
        cls = class_create(THIS_MODULE,DEV_MAME);
        device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI);
        
        printk(KERN_INFO "probe\n");
        
        return 0;
    }
    
    static int mpu6050_remove(struct i2c_client * client)
    {
        device_destroy(cls,dev_no);
        class_destroy(cls);
        unregister_chrdev_region(dev_no,DEV_CNT);
        return 0;
    }
    
    struct of_device_id mpu6050_dt_match[] = {
        {.compatible = "invensense,mpu6050"},
        {},
    };
    
    // 设备驱动注册到总线
    struct i2c_device_id mpu6050_dev_match[] = {};
    struct i2c_driver mpu6050_driver = {
        .probe = mpu6050_probe,
        .remove = mpu6050_remove,
        .driver = {
            .owner = THIS_MODULE,
            .name = "mpu6050drv",
            .of_match_table = of_match_ptr(mpu6050_dt_match), 
        },
        .id_table = mpu6050_dev_match,
    };
    module_i2c_driver(mpu6050_driver);
    MODULE_LICENSE("GPL");
    

    在代码实现中把文件系统设备实现和设备驱动实现混合在一起,个人加以分开实现,利于代码重用和设备驱动替换和多方案切换。

    验证

    通过上面的驱动, 我们可以在应用层操作设备文件从mpu6050寄存器中读取原始数据, 应用层如下

    int main(int argc, char * const argv[])
    {
        int fd = open(argv[1],O_RDWR);
        if(-1== fd){
            perror("open");
            return -1;
        }
        union mpu6050_data data = {{0}};
        while(1){
            ioctl(fd,GET_ACCEL,&data);
            printf("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
            ioctl(fd,GET_GYRO,&data);
            printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
            ioctl(fd,GET_TEMP,&data);
            printf("temp: %d\n",data.temp);
            sleep(1);
        }
        return 0;
    }
    

    最终可以获取传感器的原始数据如下
    在这里插入图片描述
    说明: 以上demo是借鉴 https://www.cnblogs.com/xiaojiang1025/p/6500540.html 的,在实际开发中个人也有实现,代码不在写该篇博客的电脑中,就借用了大神代码。

    展开全文
  • Linux设备驱动开发入门

    万人学习 2018-10-22 21:38:04
    本课程讲解Linux驱动程序开发基本知识,程序架构,字符设备编程,杂项设备编程,具体硬件模块驱动开发
  • 驱动开发教程

    2020-07-30 23:32:00
    |-学习WIN64驱动开发的硬件准备 |-配置驱动开发环境 ------------------------------ 1.驱动级HelloWorld |-配置驱动测试环境 |-编译和加载内核HelloWorld ------------------------------ 2.内核编程基础 |-WIN64...
  • Windows驱动开发之入门篇(一)

    千次阅读 2017-11-24 11:05:46
    本文主要介绍“如何入门Windows驱动开发”和“新手需要掌握哪些知识和动手技能”,大部分是本人探索Windows驱动开发近一月时间的经验之谈。大致包括如下几个方面: 1,开发工具、调试工具和调试手段; 2,Windows...
  • Windows驱动开发之第一个驱动程序

    千次阅读 2016-07-05 10:18:09
    一个简单的驱动程序
  • Windows驱动开发如何入门

    千次阅读 2018-11-07 09:53:24
    搞Windows驱动开发是一件痛苦的事情,特别是初学Windows驱动开发。有的人觉得Windows驱动开发就是把开发包WDK下载下来,然后只要掌握了C/C++语言,接下来无非就是类库调来调去,像调用MFC、QT之类的库那样,看着书和...
  • linux2.6驱动开发系列教程

    万次阅读 热门讨论 2011-11-01 21:44:33
    这段时间一直在做android下的驱动,android驱动底层跟linux如出一辙,所以这里准备做一个专题,把linux驱动做一个总结,为android接下来的驱动开发打好基础,大致的思想如下: 一、linux驱动基础开发 0、linux驱动...
  • Android驱动深度开发视频教程

    万人学习 2018-11-30 10:35:12
    也许是中国第一个讲解android驱动的课程,涵盖: bootloader,内核移植,INIT进程,框架(BINDER IPC,SERVICE FRAMEWORK Activity Manager Serive,JNI,HAL等),binder驱动,logger,Ashmen,电源管理,常用驱动(如灯光...
  • 于此,将框架(Framework)和设计模式(Design Pattern)应用于Linux驱动开发,说明了如何以面向对象、设计模式和框架概念来看待Linux驱动程序的架构。其直接的益处就是:让我们能基于一致的设计理念来结合Android HAL与...
  • Android底层技术:HAL驱动开发

    万人学习 2019-06-26 11:58:07
    本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL ...
  • 如何学习Android驱动开发

    千次阅读 2017-04-05 14:53:20
    如何成为一名优秀的Android驱动程序员?参数如下要求: 一、Android驱动的基础知识 1.Android驱动是基于Linux驱动,强烈推荐阅读Linux Device Driver 3rd版,这本书讲了Linux下设备驱动的基础知识,要求反复细读。 2...
  • 嵌入式Linux驱动教程(韦东山2期)

    万人学习 2018-11-30 11:01:24
    1.没有废话,句句都是干货!学习后保证可以跟着视频完成相应的实验。 2.现场从0编写/调试工作中的绝大部分驱动,内容--- 理论 +
  • 如何正确入门Windows系统下驱动开发领域?

    万次阅读 热门讨论 2013-06-10 16:27:59
    很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都以英文为主,这样让很多驱动初学者很头疼.本人从事驱动开发时间不长也不短,大概也就3~4年时间....
  • TDD、BDD、ATDD、DDD 软件开发模式

    万次阅读 2017-04-17 15:50:37
    TDD:测试驱动开发(Test-Driven Development) BDD:行为驱动开发(Behavior Driven Development) ATDD:验收测试驱动开发(Acceptance Test Driven Development) DDD:领域驱动开发(Domain Drive Design) ...
  • VS2019 windows驱动开发环境配置

    千次阅读 2019-08-13 13:43:42
    配置Windows驱动开发环境 VS2019 下载VS2019 下载链接 https://visualstudio.microsoft.com/zh-hans/downloads/ 选择你所需要的开发环境和配置 确认下载完后, 在你安装的WDK 的文件夹下选择 双击WDK....
  • Windows驱动开发入门系列教程

    万次阅读 2010-02-02 11:47:00
    从事驱动开发也有一段时间了,从最初的无头苍蝇到懵懵懂懂,到入门,直至今天,感觉一路走来,走了不少的弯路,只因为没有人引导。前几天,一个朋友问到我怎么学习Windows驱动开发,我就想到把我学习Windows驱动开发...
  • Windows下USB驱动开发入门小结

    万次阅读 多人点赞 2018-08-20 11:02:14
    今年刚从学校毕业,来到公司后学了学画板子然后经理便给了我了一块板子,需要用到USB传输数据,让我学习一下Windows驱动程序开发并将这个USB驱动解决。当时一脸懵逼,便将开发板通过USB接口插上电脑后发现显示的未...
1 2 3 4 5 ... 20
收藏数 638,801
精华内容 255,520
关键字:

驱动开发