linux 内核定时器timer_list用法
作者 codercjg 在 30 十月 2015, 2:27 下午
实现了一个秒定时器,每秒更新计数值。驱动源码second.c:
include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/miscdevice.h>#include <linux/types.h>#include <linux/timer.h>#include <asm/atomic.h>#include <asm/uaccess.h>struct second_t{ atomic_t counter; struct timer_list timer;};struct second_t second;void second_timer_handler(unsigned long arg){ mod_timer(&second.timer, jiffies+HZ); atomic_inc(&second.counter); printk("second timer: %d\n", atomic_read(&second.counter));}int second_open(struct inode *inode, struct file *filp){ init_timer(&second.timer); second.timer.expires = jiffies + HZ; second.timer.function = second_timer_handler; atomic_set(&second.counter, 0); add_timer(&second.timer); return 0;}int second_release(struct inode *inode, struct file *filp){ del_timer(&second.timer); return 0;}static ssize_t second_read(struct file *filp, char __user *buf, size_t count, loff_t ppos){ int counter = atomic_read(&second.counter); if(put_user(counter, (int)buf)){ return -EFAULT; }else { return sizeof(int); }}static struct file_operations fops = { .owner = THIS_MODULE, .open = second_open, .release = second_release, .read = second_read,};static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = "second", .fops = &fops,};static int __init second_init(void){ misc_register(&misc); return 0;}static void __exit second_exit(void){ misc_deregister(&misc);}MODULE_AUTHOR("codercjg");MODULE_LICENSE("Dual BSD/GPL");module_init(second_init);module_exit(second_exit);
测试源码secondapp.c:
include <sys/types.h>#include <unistd.h>#include <fcntl.h>#include <stdlib.h>#include <errno.h>#include <stdio.h>int main(int argc, char *argv[]){ int second; int fd = open("/dev/second", O_RDONLY); if(fd<0){ perror("open"); exit(errno); } while(1){ read(fd, &second, 4); printf("%d\n", second); sleep(1); } close(fd); return 0;}
mini2440 led驱动
作者 codercjg 在 28 十月 2015, 8:42 下午
datasheet上外设寄存器地址都是物理地址,而cpu访问的是虚拟地址,通过mmu映射到实际物理地址。
在驱动中要想读写外设寄存器首先要找到物理地址对应的虚拟地址,这通过io内存函数request_mem_region()和ioremap()实现物理地址到虚拟地址的转换。
request_mem_region()用于判断物理地址是否被占用,ioremap()用于创建页表实现物理地址到虚拟地址的映射。
led1 led2 led3 led4 分别对应io口GPB5 GPB6 GPB7 GPB8,需要设置寄存器GPBCON设置它们为输出,设置GPBUP禁用上拉电阻。
GPBDAT寄存器可用于点亮和熄灭led。GPB IO口寄存器物理基地址为0×56000010,共占16字节。
驱动源码ledstest.c:
/* * for mini2440 board leds, GPB5-led1 GPB6-led2 GPB7-led3 GPB8-led4 */#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/ioport.h>#include <asm/io.h>MODULE_AUTHOR("codercjg");MODULE_LICENSE("Dual BSD/GPL");#define GPB_REG_BASE 0x56000010#define GPB_REG_SIZE 16 static void __iomem *gpbbase;static volatile unsigned int *gpbcon, *gpbdata, *gpbup;static struct resource *iomem; int leds_ioctl(struct inode *node, struct file *file, unsigned int cmd, unsigned long arg){ switch (cmd) { case 0: if (arg == 1) { *gpbdata |= (1 << 5); } else if (arg == 2) { *gpbdata |= (1 << 6); } else if (arg == 3) { *gpbdata |= (1 << 7); } else if (arg == 4) { *gpbdata |= (1 << 8); } else { return -EINVAL; } break; case 1: if (arg == 1) { gpbdata &= ~(1 << 5); } else if (arg == 2) { gpbdata &= ~(1 << 6); } else if (arg == 3) { gpbdata &= ~(1 << 7); } else if (arg == 4) { gpbdata &= ~(1 << 8); } else { return -EINVAL; } break; default: return -EINVAL; } return 0;}static struct file_operations fops = { .owner = THIS_MODULE, .ioctl= leds_ioctl,};static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = "ledstest", .fops = &fops,}; static int leds_driver_init(void){ iomem = request_mem_region(GPB_REG_BASE, GPB_REG_SIZE, "leds"); if (iomem) { gpbbase = ioremap(GPB_REG_BASE, GPB_REG_SIZE); gpbcon = (unsigned int)gpbbase; gpbdata = (unsigned int)(gpbbase + 4); gpbup = (unsigned int)(gpbbase + 8); / config GPB5 GPB6 GPB7 GPB8 output */ *gpbcon &= ~(3 << 10) & ~(3 << 12) & ~(3 << 14) & ~(3 << 16); gpbcon |= (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16); / disable pullup */ gpbup |= (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8); / leds off */ *gpbdata |= (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8); misc_register(&misc); printk("leds_driver_init\n"); return 0; } else { return -EBUSY; }}static void leds_driver_exit(void){ if (iomem) { misc_deregister(&misc); iounmap(gpbbase); release_mem_region(GPB_REG_BASE, GPB_REG_SIZE); iomem = NULL; } printk("leds_driver_exit\n");}module_init(leds_driver_init);module_exit(leds_driver_exit);
测试代码ledsapp.c:
include <stdio.h>
include <sys/types.h>
include <sys/ioctl.h>
include <unistd.h>
include <fcntl.h>
include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
int cmd;
int no;
if(argc != 3 || sscanf(argv[1], "%d", &cmd) !=1 || sscanf(argv[2], "%d", &no) !=1){
perror("usage: ledsapp x x");
exit(1);
}
fd = open("/dev/ledstest", O_RDWR);
if(fd < 0){
perror("open /dev/ledstest");
exit(1);
}
if(ioctl(fd, cmd, no) < 0){
perror("ioctl");
exit(1);
}
close(fd);
return 0;
}
驱动名为leds时会冲突,所以改成ledstest。
加载led驱动后,发现还是流水灯。要想用ledapp控制它,就得先停止控制led的程序。
/etc/rc.d/init.d/leds stop
之后ledsapp 1 1就可以点亮led1, ledsapp 0 1就可以熄灭led1。
gprs使用外置协议栈lwip
作者 codercjg 在 23 十月 2015, 4:53 下午
MG323 GPRS模块,内置协议栈只支持TCP和UDP等上层协议,而我需要完成一个gprs ping 远程主机测延时的功能,所以需要一个外置协议栈来发送icmp ping request。一开始以为只要直接发送ip数据报就行了,后来发现还需要实现ppp。因为需要ppp,icmp和dns等功能,偷懒采用了freertos+lwip1.3.2。
使用过程中走过的坑:
1.gprs的AT命令解析主要用到sscanf()和sprintf(),当任务栈分配比较小时,这两个函数很可能导致栈溢出。
2.lwip中用到动态内存分配时,要注意下pbufalloc()和pbuffree()的使用,发现当ppp收到一个IP包后,会为它分配内存,当是tcp和udp时处理完成后都会释放该内存,而当识别出是icmp报文,做了相关处理后居然没有释放,直接导致内存泄漏。解决方法是在raw.c 中rawinput()里加上一句pbuffree()。
/* receive callback function available /if (pcb->recv != NULL) {/ the receive callback function did not eat the packet? /if (pcb->recv(pcb->recv_arg, pcb, p, &(iphdr->src)) != 0) {* pbuf_free(p); / add by cjg, to fix ping memory leak bug // receive function ate the packet /p = NULL;eaten = 1;
3.当发生内存泄露后,最终会导致分配内存失败,此时调用memcpy(),传入空指针,直接导致访存错误。
4.创建lwip相关的任务要调用systhreadnew()因为它会同时创建一个定时器,某些时刻会检查,要是你不小心用到它而却用xTaskCreate()创建的任务,跑着跑着肯定出异常。
最后附一个流程:
拨号入网:
gprssendcmd(“AT\r”);
gprssendcmd(“ATE0\r”);
gprssendcmd(“ATS0=0\r”);
gprssendcmd(“AT+CGDCONT=1,\”IP\”,\”CMNET\”\r”); /* 加入移动网络,移动卡联通卡都可以加入这个网络 /
gprssendcmd(“ATD99#\r”); /* 拨号上网/
之后不再发AT指令,而是用lwip ppp发数据了。
ppp获取IP:
主要是为pppOpen()实现一个回调函数linkStatusCB()用于获取ip和dns server等
pppInit();pppSetAuth(PPPAUTHTYPE_CHAP, NULL, NULL);pppOpen(fd, linkStatusCB, NULL);/send lcp req to peer// ppp callback /void linkStatusCB(void ctx, int errCode, void arg){struct ppp_addrs addrs = arg;if (errCode == PPPERR_NONE) {/ connect ok. /GPRSDEBUG(("connected\n"));GPRSDEBUG(("ipaddr = %s ", inetntoa(addrs->our_ipaddr)));GPRSDEBUG(("netmask = %s ", inet_ntoa(addrs->netmask)));GPRSDEBUG(("dns1 = %s ", inet_ntoa(addrs->dns1)));GPRSDEBUG(("dns2 = %s \n", inet_ntoa(addrs->dns2)));pppaddrs = (struct ppp_addrs)arg;gprsstate = IP_GOT;} else {gprsstate = PPP_ERR;/* connect lost /GPRSDEBUG(("connection lost\n"));}}
dns解析:
主要是为dnsgethostbyname()实现一个回调函数dnsfound()用于解析成功时获取IP地址
dns_init();dns_setserver(0, &pppaddrs.dns1); / 设置dns 服务器地址,由ppp获得 /dnsresolve(hostname, &targetip); / 解析域名 // dns resolve callback /void dnsfound(const char name, struct ipaddr ipaddr, void callback_arg){if (!ipaddr) { / 解析成功时该值非空,解析失败时该值为空/GPRSDEBUG(("dns resolve:%s failed\n", name));gprsstate = DNSRESOLVEDERR;} else {GPRSDEBUG(("dns resolve: %s===>%s\n", name, inet_ntoa(ipaddr)));dnsip = ipaddr;gprsstate = DNSRESOLVEDOK;}}/* dns resolve /errt dnsresolve(char hostname, struct ipaddr ip){errt err = dnsgethostbyname(hostname, ip, dns_found, NULL);switch (err) {case ERR_OK:GPRSDEBUG(("dns resolve: %s===>%s\n", hostname, inet_ntoa(ip)));dnsip = ip;gprsstate = DNSRESOLVEDOK;break;case ERR_INPROGRESS:/ need to udp ask, and then return result via callback function /GPRSDEBUG(("dns resolving...\n"));gprsstate = DNS_RESOLVING;break;default:GPRSDEBUG(("dns resolve error\n"));gprsstate = DNSRESOLVED*ERR;break;}return err;}
接下来就是ping、tcp和udp等应用了。
lwip dns
作者 codercjg 在 16 十月 2015, 10:28 上午
lwIP DNS OverviewThe lwIP stack provides a basic DNS client (introduced in 1.3.0) to allow other applications to resolve host names to addresses using the DNS (Domain Name System) protocol. LWIP_DNS must be #defined in lwipopts.h to a nonzero value to enable DNS support in your lwIP platform.
if DHCP is used in conjunction with the lwIP DNS client, DNS will automatically be configured to use the offered DNS server (if the DHCP server offers one).
Application DNS requests with Raw/Native APIRaw API applications can use the dns_gethostbyname() function to request a lookup, and specify a callback function to notify the application when the lookup is complete. As you can see from its header bellow, this function will return immediately. If the requested address is already known, it will be returned via the passed argument pointer. If a request to a DNS server needs to be made, your callback function will be called when it has finshed.
- err_t* dns_gethostbyname(const char hostname, struct ip_addr addr, dns_found_callback found, void callback_arg) Resolve a hostname (string) into an IP address.* NON-BLOCKING callback version for use with raw API!!!** Returns immediately with one of err_t return codes:* – ERR_OK if hostname is a valid IP address string or the host* name is already in the local names table.* – ERR_INPROGRESS enqueue a request to be sent to the DNS server* for resolution if no errors are present.** @param hostname the hostname that is to be queried* @param addr pointer to a struct ip_addr where to store the address if it is already* cached in the dns_table (only valid if ERR_OK is returned!)* @param found a callback function to be called on success, failure or timeout (only if* ERR_INPROGRESS is returned!)* @param callback_arg argument to pass to the callback function* callback function and argument defined as follows:* A function of this type must be implemented by the application using the DNS resolver.* @param name pointer to the name that was looked up.* @param ipaddr pointer to a struct ip_addr containing the IP address of the hostname,* or NULL if the name could not be found (or on any other error).* @param callback_arg a user-specified callback argument passed to dns_gethostbyname:** typedef void (*dns_found_callback)(const char *name, struct ip_addr *ipaddr, void *callback_arg);
A sample call:
struct ip_addr resolved;switch(dns_gethostbyname("www.lwIP.com", &resolved, smtp_serverFound, NULL)){case ERR_OK:// numeric or cached, returned in resolvedsmtp.serverIP.addr = resolved->addr;smtp.state = SMTP_NAME_RESOLVED;break;case ERR_INPROGRESS:// need to ask, will return data via callbacksmtp.state = SMTP_NAME_RESOLVING;break;default:// bad arguments in function callbreak;}A sample DNS callback function:void smtp_serverFound(const char *name, struct ip_addr *ipaddr, void *arg){if ((ipaddr) && (ipaddr->addr)){smtp.serverIP.addr = ipaddr->addr;smtp.state = SMTP_NAME_RESOLVED;if (smtp_connect() == ERR_OK)return;smtp.lastError = SMTP_CONNECT_FAILED;}elsesmtp.lastError = SMTP_UNKNOWN_HOST;smtp.state = SMTP_IDLE;}
Application DNS requests with Netconn APIerr_t netconn_gethostbyname(const char *name, ip_addr_t *addr)
See also netconn_gethostbyname().
Application DNS requests with Sockets APIFor socket based apps, a gethostbyname() wrapper function is provided that blocks during the lookup if necessary. Use the following functions:
gethostbyname() – standard implementation, blocks if necessary until lookup complete or failsgethostbyname_r() – thread-safe version of gethostbyname (separate buffer for each invokation)
http://lwip.wikia.com/wiki/DNS
MINI2440 开发环境搭建
作者 codercjg 在 13 十月 2015, 10:19 下午
从搭好交叉编译环境到在板子上跑第一个hello driver,其中的波折真是一言难尽。
其中要注意的地方有:
1.重新编译光盘里的linux内核源码生成zImage, 并下载到板子上。否则编译出的linux 驱动可能会和板子上跑的内核不匹配,
从而导致insmod时出现莫名其妙的错误。
make distcelan
cp config_mini2440_w35 .config
make zImage
2.下载zImage到板子上时,安装usb驱动折腾了好久。其实我的板子从Nor Flash启动时跑的是superboot, 不是supervivi,只需要
安装光盘里的MiniTools-USB就可以。
3.然后用minitool下载之前编译内核生成的zImage
4.最后挂载nfs,我在vmware Ubuntu虚拟机里开发,省得下载编译后的驱动麻烦
ifconfig eth0 192.168.3.200 broadcast 192.168.3.255 netmask 255.255.255.0
mkdir /mnt/nfs
mount -t nfs -o nolock 192.168.3.102:/home/user/nfs /mnt/nfs
之后就可以直接insmod /mnt/nfs下的hello driver了。
lwip ppp
作者 codercjg 在 13 十月 2015, 11:04 上午
PPP from an application perspectiveThere are two ways to use lwIP with PPP support. Either PPPoE (PPP over Ethernet) or PPP-over-serial. lwIP supports being run in a threaded environment, where ppp is a separate task that runs alongside the main lwIP thread. lwIP also supports being run from a main loop, with lwIP functions being called from the main loop.****
PPP over serialTo setup a PPP connection over a serial link you will need to provide the Serial IO functions.Both the thread environment and a main loop require implemented sio_write() to be implemented./*** Writes to the serial device.** @param fd serial device handle* @param data pointer to data to send* @param len length (in bytes) of data to send* @return number of bytes actually sent** @note This function will block until all data can be sent./u32_t sio_write(sio_fd_t fd, u8_t data, u32_t len);
With Task SupportIn addition to sio_write(), sio_read(), sio_read_abort() and the linkStatusCB are required to be implemented by you. The first 2 functions are statically linked, while the last function is defined through a function pointer (so the name can be different, and could be dynamically created as well).The function sio_read() is called from the pppInputThread, repeatedly. It blocks until it fills the requested buffer size or times-out, reading data from the serial device. It must abort itself if there is a call to sio_read_abort(). The soft link between sio_read and sio_read_abort must be implemented by you, either via a global variable, RTOS event, etc. The timeout should be relatively small, just large enough so that data is actually buffered in more than 1 byte chunks but small enough to make the ppp stack responsive. A timeout of about 2 ms is reasonable./* Reads from the serial device.** @param fd serial device handle* @param data pointer to data buffer for receiving* @param len maximum length (in bytes) of data to receive* @return number of bytes actually received – may be 0 if aborted by sio_read_abort/u32_t sio_read(sio_fd_t fd, u8_t data, u32_t len);The sio_read_abort() function must cause the sio_read command to exit immediately. This command is called by the tcpip_thread, mostly when ending a PPP session (terminated, link down or explicit close)./ Aborts a blocking sio_read() call.** @param fd serial device handle*/void sio_read_abort(sio_fd_t fd);****
**Required callback function *The callbackvoid (linkStatusCB)(void *ctx, int errCode, void arg)is called under the following events:Link Terminated. errCode is nonzero, arg is null.sifup (Interface Up).errCode is PPPERR_NONE, and arg is pointer to ppp_addrs structure, which contains IP address.sifdown (Interace Down). errCode is PPPERR_CONNECT, and arg is null.The errCode can be one of the following#define PPPERR_NONE 0 / No error. /#define PPPERR_PARAM -1 / Invalid parameter. /#define PPPERR_OPEN -2 / Unable to open PPP session. /#define PPPERR_DEVICE -3 / Invalid I/O device for PPP. /#define PPPERR_ALLOC -4 / Unable to allocate resources. /#define PPPERR_USER -5 / User interrupt. /#define PPPERR_CONNECT -6 / Connection lost. /#define PPPERR_AUTHFAIL -7 / Failed authentication challenge. /#define PPPERR_PROTOCOL -8 / Failed to meet protocol. /The ctx pointer is an optional user defined pointer that is defined as an argument in the call to pppOverSerialOpen, which can point to user defined data.
With no Task SupportYou need to receive the serial data in your main thread, then from your main thread callpppos_input(int pd, u_char data, int len);****
Example Task Application codeThis example shows the initialization of the TCP and PPP threads. It assumes that you will do your socket handling in the context of the main() function.
include "ppp/ppp.h"#define PPP_SERIAL_PORT 0static void linkStatusCB(void* ctx, int errCode, void* arg);int main(void) { int connected = 0; int setup = 0; int pd; const char username = "myuser"; const char password = "mypassword"; / initialise lwIP. This creates a new thread, tcpip_thread, that * communicates with the pppInputThread (see below) / tcpip_init(tcpip_init_done, &setup); while (!setup) { sleep(1); } / initialise PPP. This needs to be done only once after boot up, to * initialize global variables, etc. / pppInit(); / set the method of authentication. Use PPPAUTHTYPE_PAP, or * PPPAUTHTYPE_CHAP for more security . * If this is not called, the default is PPPAUTHTYPE_NONE. / pppSetAuth(PPPAUTHTYPE_ANY, username, password); / call the board specific function that will open the specific serial * port hardware, for example, configuring pins, I/Os, UART, USART, * clocks, etc. This function is not part of lwip or ppp. You need to * supply this or find an example for your hardware. / OpenMySerialPortOrUART(PPP_SERIAL_PORT, ...); / call the board specific function that will connect to the modem, * ie dialing phone numbers, sending configuration AT commands, etc. * This function is not part of lwip or ppp. You need to supply this if (DialOutMyModem(PPP_SERIAL_PORT, ...) != 0) { printf("can't dial out"); } else { /* Open a new PPP connection using the given I/O device. * This initializes the PPP control block but does not * attempt to negotiate the LCP session. * Return a new PPP connection descriptor on success or * an error code (negative) on failure. * This call creates one new thread per call, to service the particular * serial port, serviced by the pppInputThread function. / pd = pppOverSerialOpen(PPP_SERIAL_PORT, linkStatusCB, &connected); if (pd >= 0) { // the thread was successfully started. while (!connected && timeout(60 seconds)) { sleep(100 ms); } if (!timeout) { // We are connected on lwIP over PPP! while (working) { / create some socket connections, * do writes and reads on the sockets, close them, etc / } } / calling pppClose will end the pppInputThread associated with pd*/ pppClose(pd); } // shut down the rest of your hardware.}static void tcpip_init_done(void *arg){ if (arg) { *((bool *)arg) = 1; }}static void linkStatusCB(void *ctx, int errCode, void *arg) { //DTRACE("ctx = 0x%04X, errCode = %d arg = 0x%04X", ctx, errCode, arg); int *connected = (int *) ctx; struct ppp_addrs addrs = arg; if (errCode == PPPERR_NONE) { / We are connected */ connected = 1; syslog(LOG_DEBUG, "ip_addr = %s", inet_ntoa(addrs->our_ipaddr)); syslog(LOG_DEBUG, "netmask = %s", inet_ntoa(addrs->netmask)); syslog(LOG_DEBUG, "dns1 = %s", inet_ntoa(addrs->dns1)); syslog(LOG_DEBUG, "dns2 = %s", inet_ntoa(addrs->dns2)); } else { / We have lost connection */ }}
**Debug Support **Add this to lwipopts.h#define PPP_THREAD_NAME “ppp”#define PPP_THREAD_STACKSIZE 200#define PPP_THREAD_PRIO 2#define LWIP_DEBUG 1#define PPP_DEBUG LWIP_DBG_ONPPP packets all start and end with 0x7E. If you don’t see these, something is wrong with your low level driverFurther packet debugging hints are here: http://lists.nongnu.org/archive/html/lwip-users/2011-06/msg00036.htmlIf you have trouble with the RTOS crashing on pppClose, look here :
http://lists.gnu.org/archive/html/lwip-users/2012-02/msg00124.html
quote from: http://lwip.wikia.com/wiki/PPP