openwrt入门经典案例

一:下载编译

编译出来的镜像位于 bin 目录下面

其中 openwrt-ramips-rt305x-mpr-a2-squashfs-sysupgrade.bin 这个就是我们要的镜像。烧写到板子上面即可启动。这样最简单的 OpenWrt 就可以启动了。

二:设置开发板的IP地址

开发板中设置IP地址

在开发板上面,通过修改/etc/config 目录下的 network 配置文件,可以达到目的,首先输入一些命令

# cd /

# vi etc/config/network

在源码中设置IP地址

        如果大家对OpenWrt系统的启动流程有一定的了解的话,我们就知道,系统在启动的时候,会通过运行uci-defaults.sh这个脚本程序来设置IP等基本参数。该脚本文件位于系统源码的openwrt/trunk/,package/base-files,/files/lib./unctions目录下,那么接下来我们就来修改该脚本文件,从而修改IP地址。
(注意:新版本的openwrt修改IP的地方在:openwrt/trunk/package/base-file/files/bin/目录下的config._generate文件) 

首先打开配置文件

# cd openwrt/trunk/package/base-files/files/lib/functions

# vim uci-defaults.sh

新版本命令如下

# cd openwrt/trunk/package/base-file/files/bin/

# vim config_generate

 三 WAN 与 LAN 的切换

        我们就来通过修改开发板上面的配置文件的 方式来将网口灵活的配置成 WAN 口或者 LAN 口。同样是通过修改/etc/config 目录下 的 network 配置文件,可以达到目的。首先输入一些命令。
# cd /
# vi etc/config/network
此时,我们可以看到 network 配置文件关于 VLAN 的内容如下。
1 config switch
2 option name 'rt305x'
3 option reset '1'
4 option enable_vlan '1'
5 config switch_vlan
6 option device 'rt305x'
7 option vlan '1'
8 option ports '0 1 2 3 6t'
9 config switch_vlan
10 option device 'rt305x'
11 option vlan '2'
12 option ports '4 6t'
        其中第 4 行代表开启 vlan,6,7,8 行为 vlan0 的设置,10,11,12 行为 vlan1 的设置。这样配置的话重启网络会自动生成 2 个设备接口,eth0.0 (vlan0), eth0.1 (vlan1),这样你可以配置这两个 valn 做为 lan 口或者 wan 口。其中端口 0,端口 1,端口 2,端口 3 都属于 vlan0,端口 4 属于 vlan1。
因为开发板只用 vlan 2 个端口,可以把 option ports '0 1 2 3 6t'这一行的 0,1,2 去掉,这样就配置个两个 vlan,端口 3 作为第一个 vlan,端口 4 作为第 2 个 vlan,这样网络环境就配置好了。端口 3 为 LAN,端口 4 为 WAN。如果想将开发板端口 3、 端口 4 全部设置成 LAN 口, 则对/etc/config/network 做如下修改。
(1)将
20 option ports '0 1 2 3 6t'
改为
20 option ports '0 1 2 3 4 6t'
(2)将
21 config switch_vlan
22 option device 'rt305x'
23 option vlan '2'
24 option ports '4 6t'
给注释掉。
(3)将
9 config interface 'wan'
10 option ifname 'eth0.2'
11 option proto 'dhcp'
12 option macaddr ':
给注释掉。
OK!
重启开发板就可以了:
reboot

四 为 OpenWrt 配置支持 Web 界面

 

五 开发板做无线接入点、做站点、做中继器

无线接入点:(做设备,如手机等)
站点:(做路由器)
中继器:(路由转发器、做B,使A、C远距离连接)

六 开发板访问虚拟机Ubuntu文件

1、Ubuntu中安装SSH网络服务
2、开发板登录到Ubuntu的SSH服务器中
登录的命令格式为:
ssh servername@serverip
其中servername是Ubuntu的用户名,serverip是Ubuntu的ip地址
比如我们登录到一个用户名为if的Ubuntu的ssh服务器中
# ssh if@192.168.10.233
接下来我们要从Ubuntu上面下载文件到开发板中,下载方式很简单
scp 源(服务器目标文件路径) 目标(用户名@用户ip:文件路径)
scp /home/if/test/a.c root@192.168.10.1:/

七 添加 OpenWrt 软件包概述

        OpenWrt 是一个比较完善的嵌入式 Linux 开发平台, 在无线路由器应用上已有 4000 多个软件包。我们可以在其基础上增加软件包,以扩大其应用范围。在 OpenWrt 中增加软件包极其方便,按照 OpenWrt 的约定就可以很简单的完成。加入的软件包可以是网上可下载的开源软件或自行开发的软件。为加入软件包需要在 package 目录下创建一个目录,以包含该软件包的各种信息和与 OpenWrt 建立联系的文件。然后创建一个Makefile 与 OpenWrt 建立联系,Makefile 需要遵循 OpenWrt 的约定。另外可以创建一個 patchs 目录保存 patch 文件,对下载的源代码进行适量修改。
Makefile 语法
引入文件
OpenWrt 使用三个 makefile 的子文件,分别为:
        include $(TOPDIR)/rules.mk
        include $(INCLUDE_DIR)/kernel.mk
        include $(INCLUDE_DIR)/package.mk
        由这些 makefile 子文件确立软件包加入 OpenWrt 的方式和方法。
$(TOPDIR)/rules.mk 一般在 Makefile 的开头,
$(INCLUDE_DIR)/kernel.mk 文件对于软件包为内核时是不可缺少,$(INCLUDE_DIR)/package.mk 一般在软件包的基本信息完成后再引入。
编写软件包的基本信息
软件包的信息均以 PKG_开头,其意思和作用如下:
PKG_NAME 表示软件包名称,将在 menuconfig 和 ipkg 可以看到。
PKG_VERSION 表示软件包版本号。
PKG_RELEASE 表示 Makefile 的版本号。
PKG_SOURCE 表示源代码的文件名。
PKG_SOURCE_URL 表示源代码的下载网站位置。@SF 表示在 sourceforge 网站,
@GNU 表示在 GNU 网站,還有@GNOME、@KERNEL。
PKG_MD5SUM 表示源代码文件的效验码。用于核对软件包是否正确下载。
PKG_CAT 表示源代码文件的解压方法。包括 zcat, bzcat, unzip 等。
PKG_BUILD_DIR 表示软件包编译目录。它的父目录为$(BUILD_DIR)。如果不指定
默认为$(BUILD_DIR)/$( PKG_NAME)/$( PKG_VERSION)。
#
# Copyright (C) 2014-2016 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=acl
PKG_VERSION:=2.3.1
PKG_RELEASE:=$(AUTORELEASE)

PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://git.savannah.nongnu.org/cgit/acl.git/snapshot
PKG_HASH:=8cad1182cc5703c3e8bf7a220fc267f146246f088d1ba5dd72d8b02736deedcc
PKG_MAINTAINER:=Maxim Storchak <m.storchak@gmail.com>
PKG_LICENSE:=LGPL-2.1 GPL-2.0
PKG_LICENSE_FILES:=doc/COPYING doc/COPYING.LGPL

PKG_INSTALL:=1
PKG_FIXUP:=autoreconf

include $(INCLUDE_DIR)/package.mk

define Package/acl/Default
  TITLE:=Access control list (ACL) manipulation
  URL:=https://savannah.nongnu.org/projects/acl
  SUBMENU:=Filesystem
endef

define Package/acl/Default/description
  Access control list support
endef

define Package/acl
$(call Package/acl/Default)
  SECTION:=utils
  CATEGORY:=Utilities
  TITLE+=utils
  DEPENDS:=+libacl
endef

define Package/libacl
$(call Package/acl/Default)
  SECTION:=libs
  CATEGORY:=Libraries
  TITLE+=library
  DEPENDS:=+libattr
endef

define Package/libacl/description
$(call Package/acl/Default/description)
 This package provides libacl
endef

define Package/acl/description
$(call Package/acl/Default/description)
 This package provides ACL manipulation utilities
 - chacl
 - getfacl
 - setfacl
endef

CONFIGURE_ARGS += --enable-static --enable-shared

define Package/acl/install
	$(INSTALL_DIR) $(1)/usr/bin
	$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/bin/
endef

define Package/libacl/install
	$(INSTALL_DIR) $(1)/usr/lib
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/*.so* $(1)/usr/lib/
endef

define Build/InstallDev
	mkdir -p $(1)/usr/include
	mkdir -p $(1)/usr/lib/pkgconfig
	$(CP) $(PKG_INSTALL_DIR)/usr/{include,lib} $(1)/usr/
endef

$(eval $(call BuildPackage,acl))
$(eval $(call BuildPackage,libacl))
编译包定义
        应用程序和内核驱动模块的定义不一样。应用程序软件包使用 Package,內核驱动模块使用 KernelPackage。

1). 应用程序编译包定义

        应用程序的编译包以 Package/开头,然后接着软件名,在 Package 定义中的软件名可以与软件包名不一样,而且可以多个定义。下面使用$(PKG_NAME)只是做一个标示,并非真正使用$(PKG_NAME),如 Package/$(PKG_NAME)。
        SECTION 表示包的类型,预留。
        CATEGORY 表示分类,在 make menuconfig 的菜单下将可以找到。
        TITLE 用于软件包的简短描述。
        DESCRIPTION 用于软件包的详细描述,已放弃使用。如果使用 DESCRIPTION 將會
提示“error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description”。
        URL 表示软件包的下载位置。
        MAINTAINER 表示维护者,选项。
        DEPENDS 表示与其他软件的依赖。即如编译或安装需要其他软件时需要说明。如果
存在多个依赖,则每个依赖需要用空格分开。依赖前使用+号表示默认为显示,即对象沒有
选中时也会显示,使用@则默认为不显示,即当依赖对象选中后才显示。
        在用户空间的应用程序软件包中沒有內核驱动模块的 AUTOLOAD 参数。如果应用软
件需要在 boot 时自动运行,则需要在/etc/init.d 中增加相应的脚本文件。脚本文件需要
START 参数, 说明在 boot 时的优先级, 如果在 boot 过程启动后再关闭,
则需要进 一步设置 STOP 参数。
        如果 STOP 参数存在, 其值必须大于 START。 脚本文件需要 start()和 stop()两个函
数, start()是执行程序, stop()是关闭程序。 关闭程序一般需要執行 killall 命令。 由
/etc/rc.d/S10boot 知道,装载內核驱动模块的优先级为 10,需要使用自己设计的內核驱
动模块的程序其 START 的值必须大于 10。 同样由/etc/rc.d/S40network 知道, 使用网络通信的程序其 START 的值必须大于 40。
        Package/$(PKG_NAME)/conffiles
        本包安裝的配置文件,一行一个。如果文件结尾使用/,则表示为目录。用于备份配置文件说明,在 sysupgrade 命令执行时将会用到。
        Package/$(PKG_NAME)/description
软件包的详细描述,取代前面提到的 DESCRIPTION 详细描述。
        Build/Prepare
        编译准备方法,对于网上下载的软件包不需要再描述。对于非网上下载或自行开发的软
件包必须说明编译准备方法。一般的准备方法为:
        define Build/Prepare     
                mkdir -p $(PKG_BUILD_DIR)
                $(CP) ./src/* $(PKG_BUILD_DIR)/
        endef
按 OpenWrt 的习惯,一般把自己设计的程序全部放在 src 目录下。
        Build/Configure 
        在 Automake 中需要进行./configure,所以本配置方法主要针对需要配置的软件包而设计,一般自行开发的软件包可以不在这里说明。需要使用本定义的情况,可参考dropbear。Build/Compile 编译方法,没有特别说明的可以不予以定义。如果不定义将使用默认的编译方法 Build/Compile/Default。
        自行开发的软件包可以考虑使用下面的定义。
        define Build/Compile
                $(MAKE) -C $(PKG_BUILD_DIR) \
                $(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS)
                -I$(LINUX_DIR)/include"
        Endef
        Package/$(PKG_NAME)/install
        软件包的安装方法,包括一系列拷贝编译好的文件到指定位置。调用时会带一个参数,
就是嵌入系統的镜像文件系统目录,因此$(1)表示嵌入系统的镜像目录。一般可以采用下面
的方法:
        define Package/$(PKG_NAME)/install
                $(INSTALL_DIR) $(1)/usr/bin
                $(INSTALL_BIN) $(PKG_BUILD_DIR)/ $(PKG_NAME) $(1)/usr/bin/
        endef
        INSTALL_DIR、INSTALL_BIN 在$(TOPDIR)/rules.mk 文件定义,所以本 Makefile 必须引入$(TOPDIR)/rules.mk 文件。
        INSTALL_DIR :=install -d -m0755 意思是创建所属用戶可读写和执行,其他用戶可读可执行的目录。
        INSTALL_BIN:=install -m0755 意思编译好的文件存放到镜像文件目录。
        如果用戶空间的应用软件在 boot 时要自动运行, 则需要在安装方法说明中增加自动
运行的脚本文件安装和配置文件安裝方法。例如:
        define Package/mountd/install
                $(INSTALL_DIR) $(1)/sbin/ $(1)/etc/config/ $(1)/etc/init.d/
                $(INSTALL_BIN) $(PKG_BUILD_DIR)/mountd $(1)/sbin/
                $(INSTALL_DATA) ./files/mountd.config $(1)/etc/config/mountd
                $(INSTALL_BIN) ./files/mountd.init $(1)/etc/init.d/mountd
        endef
        安装文件放在 files 子目录下,不要与源代码文件目录 src 混在一起,以提高可读性。
        使用清晰的文件扩展名,更方便安装识別文件。
        Package/$(PKG_NAME)/preinst
        软件包安装前处理方法,使用脚本语言,因此定义的第一行需要下面的格式
        #!/bin/sh
        调用时带入的参数为嵌入式系統的镜像目录。
        Package/$(PKG_NAME)/postinst
        软件包安装后处理方法,使用脚本语言。
        Package/$(PKG_NAME)/prerm
        软件包删除前处理方法,使用脚本语言。
        Package/$(PKG_NAME)/postrm
        软件包删除后处理方法,使用脚本语言。

2). 内核驱动模块包定义

        Linux 分为内核空间和用户空间。开发者开发的内核部分可以直接加入 Linux 的 Kernel 程序,也可以生成内核模块以便需要时装入内核。OpenWrt 一般希望开发者生成内核模块,在 Linux 启动后自动装载或手工使用 insmod 命令装载。内核模块使用KernelPackage 开头,其他与一般应用软件包基本相同。
        在内核驱动模块定义中增加了:
        SUBMENU 表示子菜单位置,在 $(INCLUDE)/kernel.mk 对内核模块定义了
        CATEGORY 为 kernel modules,所以内核模块在 menuconfig 中的主菜单为 kernel
modules ,然后有下一级子菜单 $(SUBMENU) 。在子菜单下可以看到以 kmod-$(PKG_NAME)项目。
        DEFAULT 表示直接编入内核或产生内核模块, y 表示直接编入内核,m 表示产生
内核模块。
        AUTOLOAD 表示自动装入内核,一般表示方法为:
        AUTOLOAD:=$(call AutoLoad, $(PRIORITY),$(AUTOLOAD_MODS))
        AutoLoad 的第一个参数$(PRIORITY)为优先级,01 为最优先,99 为最后装载。有关自动装载可以在/etc/modules.d 目录下看到,第二个参数$(AUTOLOAD_MODS)模块名,每个模块名以空格符分隔。即可同时装载多个内核模块。
        在开发过程最好不要使用自动装载,经过严格调试后再使用,可以减轻调试的工作量。
使用定义
        完成前面定义后,必须使用 eval 函数实现各种定义。其格式为:
        对于一般应用软件包
        $(eval $(call Package,$(PKG_NAME)))
        或对于内核驱动模块
        $(eval $(call KernelPackage,$(PKG_NAME)))
        如果一个软件包有多个程序,例如:一个应用程序有自己的內核驱动模块,上面使用的PKG_NAME 需要灵活变通。eval 函数可以设计多个。也可以当成多个软件包处理。

八 字符设备驱动

1). 应用程序使用库提供的 open 函数打开代表 LED 的设备文件。
2). 库根据 open 函数传入的参数执行“SWI”指令,该指令会引起 CPU 异常,进入内核。
3). 内核的异常处理函数根据这些参数找到相应的驱动程序,返回一个文件句柄给库, 进而返回给应用程序。
4). 应用程序得到文件句柄后,使用库提供的 write 或 ioclt 函数发出控制命令。
5). 库根据 write 和 ioclt 函数传人的参数执行 “swi” 指令, 这条指令会引起 CPU 异常,进入内核。
6). 内核的异常处理函数根据这些参数调用驱动程序的相关函数,点亮 LED。
        库(比如 glibc)给应用程序提供的 open、read、write、ioctl、mmap 等接口函数被
称为系统调用,它们都是设置好相关寄存器后,执行某条指令引发异常进入内核。除系统调
用接口外, 库还提供其他函数, 比如字符串处理函数(strcpy、 strcmp 等)、 输入/输出函数(scanf、printf 等)、数学库,还有应用程序的启动代码等。
        在异常处理函数中,内核会根据传入的参数执行各种操作,比如根据设备文件名找到对
应的驱动程序,调用驱动程序的相关函数等。一般来说,当应用程序调用 open、read、
write、ioctl、mmap 等函数后,将会使用驱动程序中的 open、read、write、ioctl、mmap
函数来执行相关操作,比如初始化、读、写等。实际上,内核和驱动程序之间并没有界线,
因为驱动程序最终是要编进内核去的:通过静态链接和动态加载。
        从上面操作 LED 的过程可以知道,与应用程序不同,驱动程序从不主动运行,它是被动的:根据应用程序的要求进行初始化,根据应用程序的要求进行读写。驱动程序加载进内核时,只是告诉内核“我在这里,我能做这些工作” ,至于这些“工作”何时开始,取决于应用程序。当然,这不是绝对的,比如用户完全可以写一个系统时钟触发的驱动程序,让它自动点亮 LED。
        在 Linux 系统中,应用程序运行于“用户空间” ,拥有 MMU 的系统能够限制应用程序的权限(比如将它限制于某个内存块中),这可以避免应用程序的错误使整个系统崩溃。而驱动程序运行于“内核空间” ,它是系统“信任”的一部分,驱动程序的错误有可能导致整个系统崩溃。
Linux 驱动程序分类 
        Linux 的外设可以分为 3 类:字符设备、块设备和网络接口
字符设备
        字符设备是能够像字节流(比如文件)一样被访问的设备,就是说对它的读写是以字节为单位的。比如串口在进行收发数据时就是一个字节一个字节的进行的,我们可以在驱动程序内部使用缓冲区来存放数据以提高效率,但是串口本身对这并没有要求。字符设备的驱动程序中实现了 open、close、read、write 等系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0 等)来访问字符设备。
块设备
        块设备上的数据以块的形式存放,比如 NAND Flash 上的数据就是以页为单位存放的。 块设备驱动程序向用户层提供的接口与字符设备一样,应用程序也可以通过相应的设备文件(比如/dev/mtdblock0、/dev/hda1 等)来调用 open、close、read、write 等系统调用,与块设备传送任意字节的数据。对用户而言,字符设备和块设备的访问方式没有差别。块设备驱动程序的特别之处如下。
1). 操作硬件的接口实现方式不一样。
        块设备驱动程序先将用户发来的数据组织成块,再写入设备;或从设备中读出若干块数据,再从中挑出用户需要的。
2). 数据块上的数据可以有一定的格式(xml 、 json)
        通常在块设备中按照一定的格式存放数据,不同的文件系统类型就是用来定义这些格式的。内核中,文件系统的层次位于块设备驱动程序上面,这意味着块设备驱动程序除了向用户层提供与字符设备一样的接口外, 还要向内核其他部件提供一些接口, 这些接口用户是看不到的。这些接口使得可以在块设备上存放文件系统,挂载块设备。网络接口同时具有字符设备、块设备的部分特点,无法将它归入这两类中:如果说它是字符设备,他的输入/输出却是有结构的、成块的(报文、包、帧);如果说它是块设备,它的“块”又不是固定大小的,大到数百甚至数千字节,小到几字节。
网络接口
        UNIX 式的操作系统访问网络接口的方法是给它们分配一个惟一的名字(比如 eth0),但这个名字在文件系统中(比如/dev 目录下)不存在对应的节点项。应用程序、内核和网络驱动程序间的通信完全不同于字符设备、 块设备,库、 内核提供了一套和数据包传输相关的函数,而不是 open、read、write 等。
Linux 驱动程序开发步骤
        Linux 内核就是由各种驱动组成的, 内核源码中有大约 85%是各种驱动程序的代码。内核中驱动程序种类齐全,可以在同类驱动的基础上进行修改以符合具体单板。
        编写驱动程序的难点并不是硬件的具体操作,而是弄清楚现有驱动程序的框架,在这个框架中加入这个硬件。比如,x86 架构的内核对 IDE 硬盘的支持非常完善:首先通过 BIOS 得到硬盘的信息,或者使用默认 I/O 地址去枚举硬盘,然后识别分区、挂载文件系统。对于其他架构的内核,只是要指定了硬盘的访问地址和中断号,后面的枚举、识别和挂接的过程完全是一样的。也许修改的代码不超过 10 行,花费精力的地方在于:了解硬盘驱动的框架,找到修改的位置。
        编写驱动程序还有很多需要注意的地方,比如:驱动程序可能同时被多个进程使用,这需要考虑并发的问题;尽可能发挥硬件的作用以提高性能。比如在硬盘驱动程序中既可以使用 DMA 也可以不用,使用 DMA 时程序比较复杂,但是可以提高效率;处理硬件的各种异常情况(即使效率低),否则出错时可能导致整个系统崩溃。
一般来说,编写一个 Linux 设备驱动程序的大致流程如下。
        1). 查看原理图、数据手册,了解设备的操作方法。
        2). 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始。
        3). 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序。
        4). 设计所要实现的操作,比如 open、close、read、write 等函数。
        5). 实现中断服务(中断并不是每个设备驱动所必须的)。
        6). 编译该驱动程序到内核中,或者用 insmod 命令加载。
        7). 测试驱动程序
驱动程序的加载和卸载
        可以将驱动程序静态编译进内核中,也可以将它作为模块在使用时再加载。在配置内核时,如果某个配置选项被设为 m,就表示它将会被编译成一个模块。在 2.6 的内核中,模块的扩展名为.ko,可以使用 insmod 命令加载,使用 rmmod 命令卸载,使用 lsmod 命令查看内核中已经加载了哪些模块
        当使用 insmod 加载模块时,模块的初始化函数被调用,它用来向内核注册驱动程序;
当使用 rmmod 卸载模块时,模块的清除函数被调用。在驱动代码中,这两个函数要么取
固定的名字:init_module 和 cleanup_module,要么使用以下两行来标记它们(假设初始
化函数、 清除函数为 my_init 和 my_cleanup)。
        moudle_init(my_init);
        module_exit(my_cleanup);

九 字符设备驱动框架介绍与实现

字符设备驱动程序框架简介
        我们在学习 C 语言的时候, 知道每个应用程序的入口函数, 即第一个被执行的函数是 main 函数,那么,我们自己的驱动程序,哪个函数是入口函数呢?
        在写驱动程序时,驱动函数的名字可以任意取,常常为 xxxx_init(),当实现好这个xxxx_init()函数以后,内核其实并不知道这个就是我们驱动的入口函数,因此我们要想办法告诉内核,我们的入口函数是哪个?我们通过 module_init()函数来告诉内核,具体如下。
        module_init(drv_init);
        通过上面的修饰以后,drv_init()这个函数就变成了我们的驱动程序的入口函数了。当然,有入口函数,自然还需要一个出口函数,我们通过 module_exit()函数来告诉内核,具体如下。
        module_exit(drv_exit);
/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动
	   主设备号
	   设备名
	   指针指向实现的file_operations结构体 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动  释放主设备号*/
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
        从上一章中,我们知道,应用程序是通过 open、read、write ...函数来和我们的驱动程序进行交互的,那么我们的驱动程序是怎么给应用程序提供这些接口的呢?我们在写驱动程序的时候,我们首先需要定义出一个 file_operations 结构体,该结构体是驱动和应用程序交互的接口。具体定义如下
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long,loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long,loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,
    unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,
size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,                                                                                         
size_t,unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
};
        我们的驱动程序要给应用程序提供哪些接口,就只需要填充相应的成员即可。比如我们想提供 open、close、read、write、ioctl 这三个接口,就应该如下定义。
/*
 * 设备操作函数结构体 linux应用程序就是实现file_operations结构体内的各种方法
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open, //打开设备
	.read = chrdevbase_read, //从设备读取数据
	.write = chrdevbase_write, //向设备写数据 
	.release = chrdevbase_release, //关闭/释放设备
};
        当 file_operations 结构体定义、设置好以后,我们只需要通过 register_chrdev()函数将该结构体注册进内核即可。
字符设备驱动程序框架实现
#
#
#

include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
include $(INCLUDE_DIR)/package.mk

PKG_NAME:=mydrv
PKG_RELEASE:=1

define KernelPackage/mydrv
  SUBMENU:=Other modules
  TITLE:=mydrv
  FILES:=$(PKG_BUILD_DIR)/mydrv.ko
  KCONFIG:=
endef

define KernelPackage/mydrv/description
  This is a mydrv drivers
endef

MAKE_OPTS:= \
	ARCH="$(LINUX_KARCH)" \
	CROSS_COMPILE="$(TARGET_CROSS)" \
	SUBDIRS="$(PKG_BUILD_DIR)"

define Build/Prepare
	mkdir -p $(PKG_BUILD_DIR)
	$(CP) ./src/* $(PKG_BUILD_DIR)/
endef

define Build/Compile
	$(MAKE) -C "$(LINUX_DIR)" \
	$(MAKE_OPTS) modules
endef

$(eval $(call KernelPackage,mydrv))
/***************************** 
*
*   驱动程序模板
*   版本:V1
*   使用方法(末行模式下):
*   :%s/mydrv/"你的驱动名称"/g
*
*******************************/


#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/crash_dump.h>
#include <linux/backing-dev.h>
#include <linux/bootmem.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/aio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>


/****************  基本定义 **********************/
//内核空间缓冲区定义
#if 0
	#define KB_MAX_SIZE 20
	#define kbuf[KB_MAX_SIZE];
#endif


//加密函数参数内容: _IOW(IOW_CHAR , IOW_NUMn , IOW_TYPE)
//加密函数用于mydrv_ioctl函数中
//使用举例:ioctl(fd , _IOW('L',0x80,long) , 0x1);
//#define NUMn mydrv , if you need!
#define IOW_CHAR 'L'
#define IOW_TYPE  long
#define IOW_NUM1  0x80


//初始化函数必要资源定义
//用于初始化函数当中
//device number;
	dev_t dev_num;
//struct dev
	struct cdev mydrv_cdev;
//auto "mknode /dev/mydrv c dev_num minor_num"
struct class *mydrv_class = NULL;
struct device *mydrv_device = NULL;


/**************** 结构体 file_operations 成员函数 *****************/
//open
static int mydrv_open(struct inode *inode, struct file *file)
{
	printk("mydrv drive open...\n");


	return 0;
}

//close
static int mydrv_close(struct inode *inode , struct file *file)
{
	printk("mydrv drive close...\n");


	return 0;
}

//read
static ssize_t mydrv_read(struct file *file, char __user *buffer,
			size_t len, loff_t *pos)
{
	int ret_v = 0;
	printk("mydrv drive read...\n");


	return ret_v;
}

//write
static ssize_t mydrv_write( struct file *file , const char __user *buffer,
			   size_t len , loff_t *offset )
{
	int ret_v = 0;
	printk("mydrv drive write...\n");


	return ret_v;
}

//unlocked_ioctl
static int mydrv_ioctl (struct file *filp , unsigned int cmd , unsigned long arg)
{
	int ret_v = 0;
	printk("mydrv drive ioctl...\n");

	switch(cmd)
	{
		//常规:
		//cmd值自行进行修改
		case 0x1:
		{
			if(arg == 0x1) //第二条件;
			{

			}
		}
		break;

		//带密码保护:
		//请在"基本定义"进行必要的定义
		case _IOW(IOW_CHAR,IOW_NUM1,IOW_TYPE):
		{
			if(arg == 0x1) //第二条件
			{
				
			}

		}
		break;

		default:
			break;
	}

	return ret_v;
}


/***************** 结构体: file_operations ************************/
//struct
static const struct file_operations mydrv_fops = {
	.owner   = THIS_MODULE,
	.open	 = mydrv_open,
	.release = mydrv_close,	
	.read	 = mydrv_read,
	.write   = mydrv_write,
	.unlocked_ioctl	= mydrv_ioctl,
};


/*************  functions: init , exit*******************/
//条件值变量,用于指示资源是否正常使用
unsigned char init_flag = 0;
unsigned char add_code_flag = 0;

//init
static __init int mydrv_init(void)
{
	int ret_v = 0;
	printk("mydrv drive init...\n");

	//函数alloc_chrdev_region主要参数说明:
	//参数2: 次设备号
	//参数3: 创建多少个设备
	if( ( ret_v = alloc_chrdev_region(&dev_num,0,1,"mydrv") ) < 0 )
	{
		goto dev_reg_error;
	}
	init_flag = 1; //标示设备创建成功;

	printk("The drive info of mydrv:\nmajor: %d\nminor: %d\n",
		MAJOR(dev_num),MINOR(dev_num));

	cdev_init(&mydrv_cdev,&mydrv_fops);
	if( (ret_v = cdev_add(&mydrv_cdev,dev_num,1)) != 0 )
	{
		goto cdev_add_error;
	}

	mydrv_class = class_create(THIS_MODULE,"mydrv");
	if( IS_ERR(mydrv_class) )
	{
		goto class_c_error;
	}

	mydrv_device = device_create(mydrv_class,NULL,dev_num,NULL,"mydrv");
	if( IS_ERR(mydrv_device) )
	{
		goto device_c_error;
	}
	printk("auto mknod success!\n");

	//------------   请在此添加您的初始化程序  --------------//
       



        //如果需要做错误处理,请:goto mydrv_error;	

	 add_code_flag = 1;
	//----------------------  END  ---------------------------// 

	goto init_success;

dev_reg_error:
	printk("alloc_chrdev_region failed\n");	
	return ret_v;

cdev_add_error:
	printk("cdev_add failed\n");
 	unregister_chrdev_region(dev_num, 1);
	init_flag = 0;
	return ret_v;

class_c_error:
	printk("class_create failed\n");
	cdev_del(&mydrv_cdev);
 	unregister_chrdev_region(dev_num, 1);
	init_flag = 0;
	return PTR_ERR(mydrv_class);

device_c_error:
	printk("device_create failed\n");
	cdev_del(&mydrv_cdev);
 	unregister_chrdev_region(dev_num, 1);
	class_destroy(mydrv_class);
	init_flag = 0;
	return PTR_ERR(mydrv_device);

//------------------ 请在此添加您的错误处理内容 ----------------//
mydrv_error:
		



	add_code_flag = 0;
	return -1;
//--------------------          END         -------------------//
    
init_success:
	printk("mydrv init success!\n");
	return 0;
}

//exit
static __exit void mydrv_exit(void)
{
	printk("mydrv drive exit...\n");	

	if(add_code_flag == 1)
 	{   
           //----------   请在这里释放您的程序占有的资源   ---------//
	    printk("free your resources...\n");	               





	    printk("free finish\n");		               
	    //----------------------     END      -------------------//
	}					            

	if(init_flag == 1)
	{
		//释放初始化使用到的资源;
		cdev_del(&mydrv_cdev);
 		unregister_chrdev_region(dev_num, 1);
		device_unregister(mydrv_device);
		class_destroy(mydrv_class);
	}
}


/**************** module operations**********************/
//module loading
module_init(mydrv_init);
module_exit(mydrv_exit);

//some infomation
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("from Jafy");
MODULE_DESCRIPTION("mydrv drive");


/*********************  The End ***************************/

驱动开机自动加载
说明:
        如果需要自己的驱动程序软件包和应用程序软件包,只需要修改 Makefile 和 源码中的名字即可,直接替换名称!

十 LED驱动程序和应用程序的编写

通过LED点灯实验,教学了linux最原始的对寄存器操作实现目的

通过查看芯片手册,使能GPIO,设置输出模式,设置寄存器

具体看程序把,我感觉后续不太能对寄存器直接操作

十一 openwrt的启动流程

        在openwrt的官网上面下载的源码,其中包括了一些内核补丁,这里究竟为什么要给内核做补丁呢?因为openwrt为了支持更多的路由器,更多的操作和openwrt特有的一些内核功能,linux源码是不具备的,这样openwrt为了增加这些功能,就需要在linux官网上面下载的源代码中做一些修改,在这里体现为给linux源码打补丁。

Openwrt源码中的linux补丁文件放在openwrt/trunk$cd target/linux/generic/文件下面 

总结:从上面的分析我们来总结一下openwrt的启动流如下:

        

十二 openwrt串口的使用

十三 openwrt 安卓端与开发板通讯

十四 安卓通过开发板控制zigbee网络

十五 配置开发板支持U盘

通过 make menuconfig

        make kernel_menuconfig

配置- 编译- 烧写- 生效

U盘的挂载:mount/dev/sda/mnt

十六 配置openwrt支持网络摄像头

        最好选择免驱USB摄像头,并支持输出MJPEG压缩格式,安装mipg-streamer 软件,支持视频数据采集、视频数据格式转换、视频数据传输。

然后运行mipg-streamer软件:
一般摄像头:
        mjpg_streamer -i "input_uvc.so -d /dev/video0 -y"-o "output_http.so -w www"
支持MJPEG的摄像头:
        mjpg_streamer-i "input_uvc.so -d /dev/video0"-o "output_http.so -w www"
        其中:-i表示指定输入,这里输入为:input_uvc.so即uvc(usb video),-d是设备位置,我们摄像头的设备位置在:/dev/video0,-y用于区分一般摄像头和支持M)PEG的摄像头,-o指定输出,这里输出到output_http.so即http(何以理解为输出到网页上),-w指定web服务器为www。

十七 编写应用程序交叉编译

        mips-openwrt-linux-gcc 与 mipsel-openwrt-linux-gcc

十八 DS18B20温度传感器

单总线的概念
        目前常用的微机与外设之间进行数据传输的串行总线主要有 I2C 总线、SPI 总线和 SCI 总线。
I2C 总线以同步串行 2 线方式进行通信(一条时钟线,一条数据线)
SPI 总线则以同步串行 3 线方式进行通信(一条时钟线,一条数据输入线,一条数据输出线)
SCI 总线是以异步2线方式进行通信(一条数据输入线,一条数据输出线)。这些总线至少需要两条或两条以上的信号线。
        

十九 DHT11温湿度传感器

        DHT11数字温湿度传感器 是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能 8 位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11 传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测型号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。

二十 openwrt防火墙介绍

        防火墙文件总会在/etc/init.d/firewall 启动的时候由 UCI 进行解码并且生成 iptables 规则生效。因此使用者不需要了解 iptables 即可通过配置文件实现防火墙控制。 防火墙的修改生效,需要重启防火墙执行以下指令:
        root@OpenWrt:/# /etc/init.d/firewall reload
或执行:
        root@OpenWrt:/# /etc/init.d/firewall restart
查看当前 iptables 的已启用策略语法为:
        root@OpenWrt:/# iptables –L
打开防火墙文件查看一下:
        root@OpenWrt:/# vi /etc/config/firewall
第一部分的内容(默认参数表):
防火墙的第二个内容(域):
防火墙的第三部分内容(转发):
防火墙的第四部分内容(规则):
防火墙的第五部分内容(重定向):

二十一 远程访问开发板和zigbee通讯

二十二 openwrt实现PPPoE上网

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值