驱动程序设计:
linux操作系统的驱动与bootloader的驱动区别:
要考虑与应用层的接口;
考虑多用户;
考虑其他协议;
设备驱动的作用:读数据,写数据;
初始化设备,读写设备; 将设备的数据分配给应用; 将应用的数据分配给设备;
操作系统中驱动和设备的关系是一一对应的;
应用和驱动的关系是一对多的;
内核的主要功能:进程管理, 内存管理, 文件系统, 设备控制, 网络;
linux驱动的分类:字符设备, 块设备, 网络设备;
大部分情况:主编号标识相应的驱动程序(现代linux允许多个驱动程序共享主编号), 次设备号标识哪个设备;
设备文件的cp:$ sudo cp -a /dev/mouse /tmp/ (此时是fopen打开设备文件)
$ sudo cp /dev/mouse /tmp/ (此时是open打开设备)
linux设备驱动信息的查看:
/proc/devices 查看系统支持的字符设备和块设备驱动;
/proc/pci 查看系统的PCI设备;
/proc/ioports 查看设备的IO端口;
/proc/interrupts 查看正在使用的中断号,中断次数;
/proc/net/dev 查看网络硬件设备,包括被down的网卡;
/proc/kallsyms 查看模块符号;
/proc/jitimer 查看定时器;
dmesg 查看系统的启动信息,可以看到系统支持的一些驱动的打印信息;
lspci
lsusb -v
uname -a
ifconfig -a 查看所有网卡
模块不全是驱动,但大多数模块都是驱动;
那些函数可以在模块中使用,关于kernel的API,可以查看
模块的重新编译:
在/usr/src/linux下解压linux源码包;
拷贝配置文件:cp /boot/config-2.6.20-16.generic /usr/src/linux/.config;
重新编译内核:sudo make
在模块的工程目录下编译模块:make
模块的参数传递:
module_param(name, type, perm);
static int count;
module_param(count,int,0);
#insmod driver.ko count=10
#lsmod
#rmmod driver
模块的交叉编译(arm开发板):
重新编译2.6.17.14内核,把zImage传给开发板;
将module工程目录拷贝到rootfs/usr/module下;
将module里的Makefile的源码路径变成2.6.17.14内核所在路径;
重新编译sudo make;
应用层--->TELNET,FTP,EMAIL
运输层--->TCP,UDP
网络层--->IP,ICMP,IGMP
链路层--->设备驱动程序及接口卡
USB设备驱动:
一些重要网站:
linux驱动的相关知识:下载Linux Device Drivers,2nd Edition
USB的相关资料:
http://www.usbman.com/developer.htm
usb1.1规范: http://www.usb.org/developer/docs/usbspec.zip
ohci协议:
uhci协议: http://developer.inter.com/technology/usb/UHCI11D.pdf
lsusb -v 查看USB设备信息;
Vendor:Product =1241:1111 这是USB设备的ID号需要向USB.ORG申请获得;
NumConfigurations = 1 表明配置描述符有1个;
bEndpointAddress = 81(in) 意思是1000 0001,最高位1是读, 后面1是端点号;
bmAttributes = 03(interrupt) 表明是中断类型的传送;因此有bInterval = 08(8ms) 表明间隔时间8ms;
工作过程:
USB主机控制器通过DMA方式访问所有设备;
对设备的读写是通过设备的端点来实现的;
USB主机通过协议链表来轮询这些端点,每个端点想要正常工作就要填充好端点格式,并放到协议链表中;
控制,实时,中断,大量传输需要不同的方式来填充协议链表;
当硬件读写完成通过中断方式来告诉CPU,并由CPU来处理剩下的工作;
字符设备C 块设备B 网络设备(无设备文件)
设备文件有文件节点, 但没有真正的内容;
主设备号标识与设备相连的驱动, 次设备号用来决定引用那个设备(一般如此, 非绝对);
设备号的内核类型dev_t
MAJOR( dev_t dev); MINOR( dev_t dev);
MKDEV( int major, int minor);
sudo cp -a /dev/input/mice /tmp/ #fopen 拷贝文件
sudo cp /dev/input/mice /tmp/ #open 打开设备
cat /proc/devices pci ioports interrupts
lspci //lsusb -v //cpuinfo partitions //dmesg 可查看模块输出信息
kallsyms 模块加载位置???
ln -s sda/c/ cc 建符号连接
sudo aptitude install linux-source-2.6.20
模块的写法 头文件 参数的使用 延时参数的使用 用户空间与kernel空间的数据互传;
copy_to_user/copy_from_user put_user/get_user
第一个linuxkernel模块实验:
uname -a //查看当前版本
查看/user/src/ 下是否有相应的linux source, 若无, 下载安装linux-source-2.6.20
sudo cp /boot/config-2.6.20-15.generic /usr/src/linux-2.6.20/.config
sudo make
在module的编写目录下, make
再使用insmod rmmod lsmod
dmesg查看模块进出信息;
com2设置:
ucon1<-0x5 轮询收发
ulcon1<-0x3 8N1
ubrdiv1<-26 115200bps
状态寄存器:
UTRSTAT1, bit[1 : 0]判断收发ready;
UTXH1->L(v) B(x)???发 URXH1->L(v) B(x)收;
对物理地址的读写, 有两种方法:
1. #define UCON1 (*((volatile unsigned *)ioremap(0x500c4004, 4)))
2. include
#define iobase S3C24XX_VA_UART1
#define UCON1 (iobase + 0x04)
__raw_writel( 5, UCON1);
state = __raw_readb( UTRSTAT1);
\n换行\r回车 ~/.trash回收站 function(void) void定义时不可少;
需包含一些
实验主要流程:
将linux2.6.17.14代码解压到~/.目录下, 重新make menuconfig设置arm, 增加module support;
重新编译后, 下载内核zimage到2410板子上,
在driver编辑目录, 将makefile的src路径变为~/.linux2.6.17.14;
make, 获得driver. ko文件, 将他拷贝到rootfs/usr/driver/下
开发板用NFS方式启动, 在minicom中, 运行insmod driver.ko ,lsmod ,rmmod driver 观察结果;
SCULL设备驱动:
在编写设备驱动时遇到了以下问题:
1. 收发相同->buf[]指向了一个固定的字符串, 需要收发分开数组;
2. 发可以, 收不到->在驱动中收发用不同数组, 写可以动态申请, 收用数组是成功的???
3. 收发用数组指针如*buf++, 结果已经被移动, 用buf[tmp++]即可;
4. 在count经count--后, 却用在copy_to_user(buf, kbuf, cout)中, count已经为0了;
5. recieve信息在终端显示被char release打断, 两种处理方式可行: close之前sleep(1); release中不打印信息;
6. tmp++后, return tmp正好是所读个数, 而不是tmp+1;
sudo -s /su username
字符设备驱动, GPIO驱动:
一些概念:
dev_t为32位设备号(12+20); scull_dev结构, cdev结构
老注册方法: register_chrdev(major, &name, &fops)
unregister_chrdev(major, &name)
新注册方法: regist_chrdev_region(dev_t, count, &name);
alloc_chrdev_region(&dev, firstminor, count, &name);
major = MAJOR(dev);
cdev_init(&cdev, &fops); cdev. owner=THIS_MODULE; cdev.ops=&fops;
cdev_add(&cdev, devno, 1); cdev_del(&cdev);
unregister_chrdev_region( first, count);
关于中断:
处理器管理设备的方式, 轮询, 中断+DMA;
S3C2410支持56个中断设备, 32个中断号; SRCPND, INTPND, INTMSK 写1清0;
request_irq(irq, &handler, flags, &dev_name, &dev_id); free_irq(irq, &dev_id);
GPIO实验:
注意这些目录: asm/arch-s3c2410/regs-irq.h asm/arc-s3c2410/regs-serial.h
寄存器名称: S3C2410_EINTPEND
寄存器读写函数: __raw_writel() __raw_writeb() __raw_readl() __raw_readb()
寄存器SRCPND, INTPND, EINTPENT均写1清0;
irq_return_t 是中断服务函数的返回类型;
static struct semaphore key_sem;
up ( &key_sem); down_interruptible ( &key_sem);
open时开中断, release时关中断, rmmod时free(irq);
set_irq_type 设置中断类型;
. bashrc: +PS1='$' 可改变shell提示符的显示内容;
推荐书籍: linux内核设计与实现==陈莉君, linux驱动 倪继利
网卡驱动:
网卡: 实模式, 286, 1M;/保护模式, 386, 4G
BootRom: 启动novell网, IPX协议, 内存上虚拟一个盘;
MAC协议层+PHY&传输层,物理层
MAC向IEEE申请6个16进制, 后6个16进制公司自配置; 00.xx.xx. yy.yy.yy
DEVICE ID标识厂家, 型号; 保存在设备列表中, 靠PCI, USB等协议维护;
网线, 4线, 8线有一组备用;
LCD, TVout, 若各自用单独振源, 将导致冲突;
网卡的调试方法: 硬件(电源, 晶振. . .); 软件(. . .);
将arm开发板的网卡去除后, 通过串口传程序的方法:
1. 启动pc端minicom, 启动开发板
2. 进入linux, cd /tmp
3. rx文件名; C + A, S, 选择xmodem;
4. 选文件(双空格->进目录; 空格+回车);
PC端若不工作, 安装Lrzsz.
定义类型的几种情形: typedef, struct, #define等; 查找方法: grep str key$, key{, key空{ 等方法;
S3C2410_IRQREG(x) ->C(x) + S3C24XX_VA_IRQ)
Soket系统调用来操作网络设备, 而不是open一个设备文件;
看网卡硬件的方法, 确定有几个网卡; cat /proc/net/dev 或ifconfig -a
看本机ip的一个方法, ping 一个无法联通的ip; (在没有ifconfig);
ifconfig up-->open ifconfig down-->close;
register_netdev时会初始化net_device, 并调用init函数;
net_device的主要成员: init, open(request_irq), stop(free_irq), hard_start_xmit, net_stats;
收发数据后, netif_rx把接收的数据交给协议层, netif-start/stop/wake-queue
#define SMDK2410_ETH_IRQ IRQEINT8
SMDK2410存在于四个目录: include / asm ( arm-arm)/arch (arch-s3c2410);
双索引寄存器的操作, 可扩展存储空间;
lxr & source insight 两种方便用于源代码查看的工具;
网卡实验:
两个抓包工具: tcpdump -i eth1 // wire shark
主要步骤:
1. 启动无网络的linux;
2. driver8900.ko, server, client下载到板子;
3. mknod /dev/mynet c 242 0
4. insmod driver8900.ko
5. . /server or . /client
硬件部分1015:
AGND-----L----DGND
存储模式, 带缓存 IO模式, 不带缓存;
Nandflash D[7: 0 ] 数据/命令/地址复用
RAM用了6个管, SDRAM用了一个管子和一个电容;
candence兼并了orcad, 工具好用;
网卡实验:
1. write(pp_txtmd), write( pp_txlength) read(pp_busst); 若顺序反, 无法写成功;
2. 收到的ping包有64字节, 最后4字节有可能是CRC校验码;
3. 可以将buf={ 0x11, 0x12 . . .}放入头文件, 程序引用, 作为数据发送;
4. open若用RDONLY会无法写成功;
可查看cat /proc/ioports/ 观察IO空间;
EINT8->0XF0000000+36
USB1.1, USB2.0/主, 从, OTG设备(5芯)
USB缺点: 不稳定, 线<=5m;
北桥一般挂接高速设备, 如memory等;
PCI设备启动时要获取: 中断号, IO空间, memory空间;
启动ip, . /app -qws (qt界面程序)
给2410上拷贝文件: scp来实现;
sybian 赛班; arm-elf-gcc v2.95.3
一般错误返回-1, 成功返回0;
Qimage可先生图形/setitimer在uclinux中定时;
"hello" + 1==>"ello";
if(defined(x))可以为变量; ifdef x 只能是宏;
2.0/3.0输出结果不同于2/3, 前者是float数据;
dpkg -L nfs-kernel-server 显示安装目录
通常服务器配置问题可以通过拷贝正确的配置文件解决;
编程中用些宏定义, 条件编译, 注释说明等;
驱动的一些例程:
#driver makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m :=driver.o
else
KERNELSRC:=/home/username/linux-2.6.17.14
modules:
make -C $(KERNELSRC) SUBDIRS=$(PWD) $@
clean:
rm -f *.o *.ko *.mod.c *~
endif
/* driver.c
* module example
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define iobase S3C24XX_VA_UART1
#define ULCON1 (iobase + 0x00)
#define UCON1 (iobase + 0x04)
#define UTRSTAT1 (iobase + 0x10)
#define UTXH1 (iobase + 0x20)
#define URXH1 (iobase + 0x24)
#define UBRDIV1 (iobase + 0x28)
MODULE_AUTHOR("xxxxxx");
MODULE_DESCRIPTION("com2 module");
MODULE_LICENSE("GPL");
int delay(int n)
{
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(n);
return 0;
}
int s3c2410_serial_read(void)
{
char c = 'z';
int state;
printk("begin read...\n");
while (c != 'Q')
{
state = __raw_readb(UTRSTAT1);
if (state & 0X1)
{
c = __raw_readb(URXH1);
printk("%c", c);
}
delay(1);
}
printk("\n");
return 0;
}
int s3c2410_serial_write(void)
{
int state;
printk("begin write...\n");
state = __raw_readb(UTRSTAT1);
if (state & 0x2)
{
__raw_writel('*', UTXH1);
}
return 0;
}
int __init
s3c2410_serial_init(void)
{
printk("comm2 init\n");
__raw_writel(5, UCON1);
__raw_writel(3, ULCON1);
__raw_writel(26, UBRDIV1);
s3c2410_serial_write();
s3c2410_serial_read();
return 0;
}
void __exit
s3c2410_serial_exit(void)
{
printk("comm2 exit\n");
return;
}
module_init(s3c2410_serial_init);
module_exit(s3c2410_serial_exit);
/*gpio_mod.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define keyirq_MAJOR 242
#define NOKEY '0'
#define KEY1 '1'
#define KEY2 '2'
#define KEY3 '3'
#define KEY4 '4'
#define KEY5 '5'
static struct semaphore key_sem;
static char key=NOKEY;
static void inline clearIrq(void)
{
/*respond to EINT4-EINT7*/
__raw_writel(0xf0, S3C2410_EINTPEND);
__raw_writel(0x13, S3C2410_SRCPND);
__raw_writel(0x13, S3C2410_INTPND);
}
static void key1_irq_isr(int irq, void *dev_id, struct pt_regs *regs)
{
clearIrq();
key = KEY1;
up(&key_sem);
// return 0;
}
static irqreturn_t key2_irq_isr(int irq, void *dev_id, struct pt_regs *regs)
{
clearIrq();
key = KEY2;
up(&key_sem);
return 0;
}
static irqreturn_t key3_irq_isr(int irq, void *dev_id, struct pt_regs *regs)
{
clearIrq();
key = KEY3;
up(&key_sem);
return 0;
}
static ssize_t keyirq_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
down_interruptible(&key_sem);
put_user(key, buf);
key = NOKEY;
return 1;
}
int keyirq_open(struct inode *inode, struct file *file)
{
clearIrq();
#if 1
enable_irq(IRQ_EINT0);
enable_irq(IRQ_EINT1);
enable_irq(IRQ_EINT6);
#endif
sema_init(&key_sem, 0);
return 0;
}
int keyirq_release(struct inode *inode, struct file *filp)
{
#if 1
disable_irq(IRQ_EINT0);
disable_irq(IRQ_EINT1);
disable_irq(IRQ_EINT6);
#endif
return 0;
}
static int keyirq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
struct file_operations keyirq_fops = {
.owner= THIS_MODULE,
.read = keyirq_read,
.open = keyirq_open,
.release = keyirq_release,
.ioctl = keyirq_ioctl,
};
int __init keyirq_init(void)
{
int rc;
printk("_SDK2410 gpio key driver irq=%d\n", IRQ_EINT0);
set_irq_type(IRQ_EINT0, IRQT_FALLING);
set_irq_type(IRQ_EINT1, IRQT_FALLING);
set_irq_type(IRQ_EINT6, IRQT_FALLING);
rc = request_irq(IRQ_EINT0, key1_irq_isr, SA_INTERRUPT, "key2345irq", NULL);
if (rc) {
printk ("<1>keyirq 1 irq not registered. Error:%d\n", rc);
}
rc = request_irq(IRQ_EINT1, key2_irq_isr, SA_INTERRUPT, "key2345irq", NULL);
if (rc) {
printk ("<1>keyirq 2 irq not registered. Error:%d\n", rc);
}
rc = request_irq(IRQ_EINT6, key3_irq_isr, SA_INTERRUPT, "key2345irq", NULL);
if (rc) {
printk ("<1>keyirq 4 irq not registered. Error:%d\n", rc);
}
#if 1
disable_irq(IRQ_EINT0);
disable_irq(IRQ_EINT1);
disable_irq(IRQ_EINT6);
#endif
/*Register myirq as character device*/
if ((rc = register_chrdev(keyirq_MAJOR, "key", &keyirq_fops)) < 0){
printk("keyirq: can't get major %d\n", keyirq_MAJOR);
return 1;
}
return 0;
}
void keyirq_exit(void)
{
#if 1
disable_irq(IRQ_EINT0);
disable_irq(IRQ_EINT1);
disable_irq(IRQ_EINT6);
#endif
free_irq(IRQ_EINT0, NULL);
free_irq(IRQ_EINT1, NULL);
free_irq(IRQ_EINT6, NULL);
unregister_chrdev(keyirq_MAJOR, "key");
}
module_init(keyirq_init);
module_exit(keyirq_exit);
/*Discription: this program is used to test the driver gpio_mod.o
*gpio_test.c
*/
#include
#include
#include
#include
#define NOKEY 0
int main()
{
int keys4_fd;
char ret[2];
keys4_fd = open("/dev/keys4", O_RDONLY);
if(keys4_fd <= 0)
{
printf("open keys4 device error!\n");
return 0;
}
while(1)
{
read(keys4_fd, ret, 1);
if(ret[0] != NOKEY)
{
printf("key = %c\n", ret[0]);
}
usleep(100000);
}
close(keys4_fd);
return 0;
}
#driver makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m :=gpio_mod.o
else
#KERNELSRC:=/usr/src/linux-headers-2.6.20-15-generic
KERNELSRC:=/home/username/linux-2.6.17.14
modules:
make -C $(KERNELSRC) SUBDIRS=$(PWD) $@
clean:
rm -f *.o *.ko *.mod.c *~
endif
test: gpio_test.c
arm-linux-gcc -o gpio_test gpio_test.c
module: gpio_mod.c
arm-linux-gcc -o gpio_mod gpio_mod.c
#driver makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m :=char_dev.o
else
KERNELSRC:=/home/username/linux-2.6.17.14
modules:
make -C $(KERNELSRC) SUBDIRS=$(PWD) $@
clean:
rm -f *.o *.ko *.mod.c *~
endif
/*test_char.c*/
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
int n;
char buf[35] = "hellocc\n\r";
fd = open("/dev/drvtst", O_RDWR);
if (fd < 0)
{
perror ("open device err\n\r");
exit(1);
}
write(fd, buf, strlen(buf));
n = read(fd, buf, 30);
buf[n] = '\0';
printf ("%s\n", buf);
// printf ("test over!\n");
//sleep(1);
close(fd); //call release();
return 0;
}
/*char_dev.h -- definitions for the char module
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
* $Id: scull.h,v 1.15 2004/11/04 17:51:18 rubini Exp $
*/
#ifndef _SCULL_H_
#define _SCULL_H_
#include /* needed for the _IOW etc stuff used later */
/*
* Macros to help debugging
*/
#undef PDEBUG /* undef it, just in case */
#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0 /* dynamic major by default */
#endif
#ifndef SCULL_NR_DEVS
#define SCULL_NR_DEVS 4 /* scull0 through scull3 */
#endif
/*
* The bare device is a variable-length region of memory.
* Use a linked list of indirect blocks.
*
* "scull_dev->data" points to an array of pointers, each
* pointer refers to a memory area of SCULL_QUANTUM bytes.
*
* The array (quantum-set) is SCULL_QSET long.
*/
#ifndef SCULL_QUANTUM
#define SCULL_QUANTUM 4000
#endif
#ifndef SCULL_QSET
#define SCULL_QSET 1000
#endif
struct scull_dev {
struct cdev cdev; /* Char device structure */
};
#endif /* _SCULL_H_ */
/*
* char_dev.c -- the bare scull char module
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
#include
#include
#include
#include /* printk() */
#include /* kmalloc() */
#include /* everything... */
#include /* error codes */
#include /* size_t */
#include
#include /* O_ACCMODE */
#include
#include
#include
#include
#include /* cli(), *_flags */
#include /* copy_*_user */
#include
#include
#include
#include
#define SCULL_MAJOR 241
#include "char_dev.h" /* local definitions */
///////////////////////////////////////////////////////////////////
#define UCON1 (*((volatile unsigned*)ioremap(0x50004004, 1)))
#define ULCON1 (*((volatile unsigned*)ioremap(0x50004000, 1)))
#define UBRDIV1 (*((volatile unsigned*)ioremap(0x50004028, 1)))
#define UTXH1 (*((volatile unsigned*)ioremap(0x50004020, 1)))
#define URXH1 (*((volatile unsigned*)ioremap(0x50004024, 1)))
#define UTRSTAT1 (*((volatile unsigned*)ioremap(0x50004010, 1)))
///////////////////////////////////////////////////////////////////
/*
* Our parameters which can be set at load time.
*/
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
//MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
//MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("user");
MODULE_DESCRIPTION("com2 module");
MODULE_LICENSE("GPL");
char *kbuf = 0;
char sbuf[100];
int delay(int n)
{
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(n);
return 0;
}
struct scull_dev *scull_devices; /* allocated in scull_init_module */
/*
* Open and close
*/
int scull_open(struct inode *inode, struct file *filp)
{
printk ("char open\n");
return 0; /* success */
}
int scull_release(struct inode *inode, struct file *filp)
{
printk ("char release \n");
return 0;
}
/*
* Data management: read and write
*/
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
char c='z';
int tmp = 0;
printk ("read \n");
//kbuf = kmalloc(count, 0);
while ( count > 0)
{
delay(1);
if (UTRSTAT1 & 0X1)
{
c = URXH1;
sbuf[tmp++] = c;
count--;
}
}
copy_to_user(buf, sbuf, tmp );
//kfree(kbuf);
printk("read over\n");
return tmp;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
int tmp = 0;
printk ("write \n");
kbuf = kmalloc(count, 0);
if (kbuf < 0)
{
printk(KERN_WARNING "scull: kmalloc failure %d\n", scull_major);
return -1;
}
copy_from_user(kbuf, buf, count);
while (count > 0)
{
if (UTRSTAT1 & 0x2)
{
UTXH1 = kbuf[tmp++];
count--;
}
}
kfree(kbuf);
printk("write over\n");
return 1;
}
/*
* The ioctl() implementation
*/
int scull_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
return 0;
}
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
/*
* Finally, the module stuff
*/
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
/* Get rid of our char dev entries */
if (scull_devices) {
for (i = 0; i < scull_nr_devs; i++) {
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);
printk ("cleanup_module char\n");
}
/*
* Set up the char_dev structure for this device.
*/
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;
printk("comm2 init\n");
UCON1 = 5;
ULCON1 = 3;
UBRDIV1 = 26;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "drvtst0");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"drvtst1");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_setup_cdev(&scull_devices[i], i);
}
printk ("init ok\n");
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
#driver makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m := driver8900.o
else
#KERNELSRC:=/usr/src/linux-headers-2.6.20-15-generic
KERNELSRC:=/home/username/linux-2.6.17.14
modules:
make -C $(KERNELSRC) SUBDIRS=$(PWD) $@
clean:
rm -f *.o *.ko *.mod.c *~
endif
server: server.c
arm-linux-gcc -o $@ $^
client: client.c
arm-linux-gcc -o $@ $^
/*server.c*/
#include
#include
#include
#include
#include
#include
int main()
{
int fd, i, n;
char buf[1024];
fd = open("/dev/akaenet", O_RDWR);
if (fd < 0)
{
perror ("open device err\n\r");
exit(1);
}
while(1)
{
n = read(fd, buf, 1024);
printf ("n = %d\n", n);
for (i = 0; i < n; i++)
{
printf ("0x%02x, ", buf[i]);
if ((i%8) == 7)
printf("\n");
}
printf ("\nreceived: %s\n", buf);
}
close(fd);
return 0;
}
/*ping_p.h*/
char ping_p[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xe0,
0x4d, 0x1f, 0x46, 0xb2, 0x08, 0x06, 0x00, 0x01,
0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x00, 0xe0,
0x4d, 0x1f, 0x46, 0xb2, 0xc0, 0xa8, 0x01, 0x7d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8,
0x01, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2d, 0x71, 0x35, 0xc8,
};
/*client.c*/
#include
#include
#include
#include
#include
#include
#include "ping_p.h"
int main()
{
int fd;
int n;
//char buf[1024] = "666666666666666666666666666666666666666666666666666666";
fd = open("/dev/akaenet", O_RDWR);
if (fd < 0)
{
perror ("open device err\n\r");
exit(1);
}
while(1)
{
//gets(buf);
write(fd, ping_p, 60);
// write(fd, buf, strlen(buf));
// printf("ping_p[]...64\n");
sleep(1);
}
close(fd); //call release();
return 0;
}
/*cs8900.h*/
#ifndef CS8900_H
#define CS8900_H
/*
* linux/drivers/net/cs8900.h
*
* Author: Abraham van der Merwe
*
* A Cirrus Logic CS8900A driver for Linux
* based on the cs89x0 driver written by Russell Nelson,
* Donald Becker, and others.
*
* This source code is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/*
* Ports
*/
#define PP_Address 0x0a /* PacketPage Pointer Port (Section 4.10.10) */
#define PP_Data 0x0c /* PacketPage Data Port (Section 4.10.10) */
/*
* Registers
*/
#define PP_ProductID 0x0000 /* Section 4.3.1 Product Identification Code */
#define PP_MemBase 0x002c /* Section 4.9.2 Memory Base Address Register */
#define PP_IntNum 0x0022 /* Section 3.2.3 Interrupt Number */
#define PP_EEPROMCommand 0x0040 /* Section 4.3.11 EEPROM Command */
#define PP_EEPROMData 0x0042 /* Section 4.3.12 EEPROM Data */
#define PP_RxCFG 0x0102 /* Section 4.4.6 Receiver Configuration */
#define PP_RxCTL 0x0104 /* Section 4.4.8 Receiver Control */
#define PP_TxCFG 0x0106 /* Section 4.4.9 Transmit Configuration */
#define PP_BufCFG 0x010a /* Section 4.4.12 Buffer Configuration */
#define PP_LineCTL 0x0112 /* Section 4.4.16 Line Control */
#define PP_SelfCTL 0x0114 /* Section 4.4.18 Self Control */
#define PP_BusCTL 0x0116 /* Section 4.4.20 Bus Control */
#define PP_TestCTL 0x0118 /* Section 4.4.22 Test Control */
#define PP_ISQ 0x0120 /* Section 4.4.5 Interrupt Status Queue */
#define PP_TxEvent 0x0128 /* Section 4.4.10 Transmitter Event */
#define PP_BufEvent 0x012c /* Section 4.4.13 Buffer Event */
#define PP_RxMISS 0x0130 /* Section 4.4.14 Receiver Miss Counter */
#define PP_TxCOL 0x0132 /* Section 4.4.15 Transmit Collision Counter */
#define PP_SelfST 0x0136 /* Section 4.4.19 Self Status */
#define PP_BusST 0x0138 /* Section 4.4.21 Bus Status */
#define PP_TxCMD 0x0144 /* Section 4.4.11 Transmit Command */
#define PP_TxLength 0x0146 /* Section 4.5.2 Transmit Length */
#define PP_IA 0x0158 /* Section 4.6.2 Individual Address (IEEE Address) */
#define PP_RxStatus 0x0400 /* Section 4.7.1 Receive Status */
#define PP_RxLength 0x0402 /* Section 4.7.1 Receive Length (in bytes) */
#define PP_RxFrame 0x0404 /* Section 4.7.2 Receive Frame Location */
#define PP_TxFrame 0x0a00 /* Section 4.7.2 Transmit Frame Location */
/*
* Values
*/
/* PP_IntNum */
#define INTRQ0 0x0000
#define INTRQ1 0x0001
#define INTRQ2 0x0002
#define INTRQ3 0x0003
/* PP_ProductID */
#define EISA_REG_CODE 0x630e
#define REVISION(x) (((x) & 0x1f00) >> 8)
#define VERSION(x) ((x) & ~0x1f00)
#define CS8900A 0x0000
#define REV_B 7
#define REV_C 8
#define REV_D 9
/* PP_RxCFG */
#define Skip_1 0x0040
#define StreamE 0x0080
#define RxOKiE 0x0100
#define RxDMAonly 0x0200
#define AutoRxDMAE 0x0400
#define BufferCRC 0x0800
#define CRCerroriE 0x1000
#define RuntiE 0x2000
#define ExtradataiE 0x4000
/* PP_RxCTL */
#define IAHashA 0x0040
#define PromiscuousA 0x0080
#define RxOKA 0x0100
#define MulticastA 0x0200
#define IndividualA 0x0400
#define BroadcastA 0x0800
#define CRCerrorA 0x1000
#define RuntA 0x2000
#define ExtradataA 0x4000
/* PP_TxCFG */
#define Loss_of_CRSiE 0x0040
#define SQErroriE 0x0080
#define TxOKiE 0x0100
#define Out_of_windowiE 0x0200
#define JabberiE 0x0400
#define AnycolliE 0x0800
#define T16colliE 0x8000
/* PP_BufCFG */
#define SWint_X 0x0040
#define RxDMAiE 0x0080
#define Rdy4TxiE 0x0100
#define TxUnderruniE 0x0200
#define RxMissiE 0x0400
#define Rx128iE 0x0800
#define TxColOvfiE 0x1000
#define MissOvfloiE 0x2000
#define RxDestiE 0x8000
/* PP_LineCTL */
#define SerRxON 0x0040
#define SerTxON 0x0080
#define AUIonly 0x0100
#define AutoAUI_10BT 0x0200
#define ModBackoffE 0x0800
#define PolarityDis 0x1000
#define L2_partDefDis 0x2000
#define LoRxSquelch 0x4000
/* PP_SelfCTL */
#define RESET 0x0040
#define SWSuspend 0x0100
#define HWSleepE 0x0200
#define HWStandbyE 0x0400
#define HC0E 0x1000
#define HC1E 0x2000
#define HCB0 0x4000
#define HCB1 0x8000
/* PP_BusCTL */
#define ResetRxDMA 0x0040
#define DMAextend 0x0100
#define UseSA 0x0200
#define MemoryE 0x0400
#define DMABurst 0x0800
#define IOCHRDYE 0x1000
#define RxDMAsize 0x2000
#define EnableRQ 0x8000
/* PP_TestCTL */
#define DisableLT 0x0080
#define ENDECloop 0x0200
#define AUIloop 0x0400
#define DisableBackoff 0x0800
#define FDX 0x4000
/* PP_ISQ */
#define RegNum(x) ((x) & 0x3f)
#define RegContent(x) ((x) & ~0x3d)
#define RxEvent 0x0004
#define TxEvent 0x0008
#define BufEvent 0x000c
#define RxMISS 0x0010
#define TxCOL 0x0012
/* PP_RxStatus */
#define IAHash 0x0040
#define Dribblebits 0x0080
#define RxOK 0x0100
#define Hashed 0x0200
#define IndividualAdr 0x0400
#define Broadcast 0x0800
#define CRCerror 0x1000
#define Runt 0x2000
#define Extradata 0x4000
#define HashTableIndex(x) ((x) >> 0xa)
/* PP_TxCMD */
#define After5 0
#define After381 1
#define After1021 2
#define AfterAll 3
#define TxStart(x) ((x) << 6)
#define Force 0x0100
#define Onecoll 0x0200
#define InhibitCRC 0x1000
#define TxPadDis 0x2000
/* PP_BusST */
#define TxBidErr 0x0080
#define Rdy4TxNOW 0x0100
/* PP_TxEvent */
#define Loss_of_CRS 0x0040
#define SQEerror 0x0080
#define TxOK 0x0100
#define Out_of_window 0x0200
#define Jabber 0x0400
#define T16coll 0x8000
#define TX_collisions(x) (((x) >> 0xb) & ~0x8000)
/* PP_BufEvent */
#define SWint 0x0040
#define RxDMAFrame 0x0080
#define Rdy4Tx 0x0100
#define TxUnderrun 0x0200
#define RxMiss 0x0400
#define Rx128 0x0800
#define RxDest 0x8000
/* PP_RxMISS */
#define MissCount(x) ((x) >> 6)
/* PP_TxCOL */
#define ColCount(x) ((x) >> 6)
/* PP_SelfST */
#define T3VActive 0x0040
#define INITD 0x0080
#define SIBUSY 0x0100
#define EEPROMpresent 0x0200
#define EEPROMOK 0x0400
#define ELpresent 0x0800
#define EEsize 0x1000
/* PP_EEPROMCommand */
#define EEWriteEnable 0x00F0
#define EEWriteDisable 0x0000
#define EEWriteRegister 0x0100
#define EEReadRegister 0x0200
#define EEEraseRegister 0x0300
#define ELSEL 0x0400
#endif /* #ifndef CS8900_H */
/*driver8900.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#include
#include
#include
#include
#include
//#include
#include
#include
#include
// Added BSt
#include
#include "asm/arch/smdk2410.h"
#include "cs8900.h"
//#define FULL_DUPLEX
//#define DEBUG
#define netirq_MAJOR 242
#define NET_MAXLEN 1024
#define DEVICENAME "net8900"
static struct semaphore net_sem;
static u_char krbuf[NET_MAXLEN];
static u_char ktbuf[NET_MAXLEN];
static u32 cs8900_base_addr;
static int net_irq;
static int rxlen;
/********************************************************************************/
/*
* I/O routines
*/
static inline u16 cs8900_read (u32 base_addr,u16 reg)
{
outw (reg,base_addr + PP_Address);
return (inw (base_addr + PP_Data));
}
static inline void cs8900_write (u32 base_addr,u16 reg,u16 value)
{
outw (reg,base_addr + PP_Address);
outw (value,base_addr + PP_Data);
}
static inline void cs8900_set (u32 base_addr,u16 reg,u16 value)
{
cs8900_write (base_addr,reg,cs8900_read (base_addr,reg) | value);
}
static inline void cs8900_clear (u32 base_addr,u16 reg,u16 value)
{
cs8900_write (base_addr,reg,cs8900_read (base_addr,reg) & ~value);
}
static void cs8900_frame_read (u32 base_addr,u_char *buf,u16 length)
{
#if 1
insw (base_addr,buf,(length + 1) / 2);
#else
u16 tmp;
int k;
for (k = 0; k < length-1; k += 2)
{
//tmp = cs8900_read (cs8900_base_addr, PP_RxFrame);
tmp = cs8900_read (cs8900_base_addr, PP_RxFrame + k);
buf[k] = tmp & 0xff;
buf[k+1] = tmp >> 8;
}
if (k == length-1)
{
tmp = cs8900_read (cs8900_base_addr, PP_RxFrame + k);
buf[k++] = tmp & 0xff;
}
#endif
}
static inline void cs8900_frame_write (u32 base_addr,u_char *buf, u16 length)
{
outsw (base_addr,buf,(length + 1) / 2);
}
/********************************************************************************/
/*
* Driver functionsdev
*/
/*
static void inline clearIrq(void)
{
__raw_writel(0x100, S3C2410_EINTPEND);
__raw_writel(0x20, S3C2410_SRCPND);
__raw_writel(0x20, S3C2410_INTPND);
}
*/
static int cs8900_recv(void)
{
u16 status,length;
printk("in cs8900_recv ->0\n");
status = cs8900_read (cs8900_base_addr,PP_RxStatus);
length = cs8900_read (cs8900_base_addr,PP_RxLength);
rxlen = length;
if (!(status & RxOK)) {
return 1;
}
printk("in cs8900_recv ->1\n");
cs8900_frame_read (cs8900_base_addr,krbuf,length);
up(&net_sem);
printk("in cs8900_recv ->2\n");
printk("recv length %d\n", length);
return 0;
}
static int cs8900_send(int length)
{
spinlock_t slock;
u16 status;
printk("in sending...\n");
spin_lock_irq(&slock);
cs8900_write (cs8900_base_addr,PP_TxCMD,TxStart (After5));
cs8900_write (cs8900_base_addr,PP_TxLength, length);
status = cs8900_read (cs8900_base_addr,PP_BusST);
if ((status & TxBidErr)) {
spin_unlock_irq(&slock);
printk (KERN_WARNING "%s: Invalid frame size!\n",DEVICENAME);
return (1);
}
if (!(status & Rdy4TxNOW)) {
spin_unlock_irq(&slock);
printk (KERN_WARNING "%s: Transmit buffer not free!\n",DEVICENAME);
return (1);
}
cs8900_frame_write (cs8900_base_addr,ktbuf, length);
spin_unlock_irq(&slock);
printk("send length %d\n", length);
printk("out sending...\n");
return (0);
}
static irqreturn_t cs8900_irq_isr(int irq, void *dev_id, struct pt_regs *regs)
{
volatile u16 status;
irqreturn_t handled = 0;
printk("into irq...\n");
//clearIrq();
while ((status = cs8900_read (cs8900_base_addr, PP_ISQ))) {
handled = 1;
switch (RegNum (status)) {
case RxEvent:
printk("receive Event\n");
cs8900_recv();
break;
case TxEvent:
printk("send Event\n");
if (!(RegContent (status) & TxOK)) {
printk("send error\n");
}else{
printk("send success\n");
}
break;
case BufEvent:
printk("Buf Event\n");
break;
case TxCOL:
printk("TxCOL Event\n");
break;
case RxMISS:
printk("RxMiss Event\n");
break;
default:
printk ("interrupt not success!\n");
break;
}
printk ("out switch...\n");
}
printk ("out while...\n");
return IRQ_RETVAL(handled);
}
static int cs8900_probe(void)
{
int i,result;
u16 value;
spinlock_t slock;
u_char dev_addr[ETH_ALEN];
printk("into probe...\n");
dev_addr[0] = 0x00;
dev_addr[1] = 0x12;
dev_addr[2] = 0x34;
dev_addr[3] = 0x56;
dev_addr[4] = 0x78;
dev_addr[5] = 0x9a;
//dev_addr[5] = 0x9b;
spin_lock_init(&slock);
cs8900_base_addr = vSMDK2410_ETH_IO + 0x300;
net_irq = SMDK2410_ETH_IRQ;
if ((result = check_mem_region (cs8900_base_addr, 16))) {
printk (KERN_ERR "%s: can't get I/O port address 0x%lx\n", DEVICENAME, (unsigned long)cs8900_base_addr);
return (result);
}
request_mem_region (cs8900_base_addr, 16, DEVICENAME);
/* verify EISA regiscs8900_base_addrtration number for Cirrus Logic */
if ((value = cs8900_read (cs8900_base_addr,PP_ProductID)) != EISA_REG_CODE) {
printk (KERN_ERR "%s: incorrect signature 0x%.4x\n", DEVICENAME, value);
return (-ENXIO);
}
/* verify chip version */
value = cs8900_read (cs8900_base_addr,PP_ProductID + 2);
if (VERSION (value) != CS8900A) {
printk (KERN_ERR "%s: unknown chip version 0x%.8x\n", DEVICENAME, VERSION (value));
return (-ENXIO);
}
/* setup interrupt number */
cs8900_write (cs8900_base_addr,PP_IntNum,0);
/*setup Mac address*/
for (i = 0; i < ETH_ALEN; i += 2)
cs8900_write (cs8900_base_addr,PP_IA + i,dev_addr[i] | (dev_addr[i + 1] << 8));
spin_lock_init(&slock);
printk ("Mac addr:");
for (i = 0; i < ETH_ALEN; i += 2)
{
u16 mac = cs8900_read (cs8900_base_addr,PP_IA + i);
printk ("%c%02X:%2X", (i==0)?' ':':', mac & 0xff, (mac >> 8));
}
printk ("\n");
return (0);
}
static ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
down_interruptible(&net_sem);
printk("ready to copy_to_user...\n");
copy_to_user(buf, krbuf, rxlen);
printk("end to copy_to_user...\n");
printk("out read...\n");
return rxlen;
}
static ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("into write...\n");
copy_from_user(ktbuf, buf, count);
cs8900_send(count);
return 0;
}
static int scull_open(struct inode *inode, struct file *file)
{
int result;
//clearIrq();
//disable_irq(net_irq);
printk("into open...\n");
set_irq_type(net_irq, IRQT_RISING);
/* enable the ethernet controller */
cs8900_set (cs8900_base_addr,PP_RxCFG,RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE);
cs8900_set (cs8900_base_addr,PP_RxCTL,RxOKA | IndividualA | BroadcastA | PromiscuousA);
cs8900_set (cs8900_base_addr,PP_TxCFG,TxOKiE | Out_of_windowiE | JabberiE);
cs8900_set (cs8900_base_addr,PP_BufCFG,Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE);
cs8900_set (cs8900_base_addr,PP_LineCTL,SerRxON | SerTxON);
cs8900_set (cs8900_base_addr,PP_BusCTL,EnableRQ);
cs8900_set (cs8900_base_addr,PP_RxCTL,MulticastA);
//FULL_DUPLEX
//cs8900_set (cs8900_base_addr,PP_TestCTL,FDX);
sema_init(&net_sem, 0);
udelay(1000);
/* install interrupt handler */
if ((result = request_irq (net_irq, &cs8900_irq_isr, 0, DEVICENAME, NULL)) < 0) {
printk (KERN_ERR "%s: could not register interrupt %d\n", DEVICENAME, net_irq);
return (result);
}
//enable_irq(net_irq);
return 0;
}
static int scull_release(struct inode *inode, struct file *filp)
{
//disable_irq(net_irq);
/* disable ethernet controller */
cs8900_write (cs8900_base_addr,PP_BusCTL,0);
cs8900_write (cs8900_base_addr,PP_TestCTL,0);
cs8900_write (cs8900_base_addr,PP_SelfCTL,0);
cs8900_write (cs8900_base_addr,PP_LineCTL,0);
cs8900_write (cs8900_base_addr,PP_BufCFG,0);
cs8900_write (cs8900_base_addr,PP_TxCFG,0);
cs8900_write (cs8900_base_addr,PP_RxCTL,0);
cs8900_write (cs8900_base_addr,PP_RxCFG,0);
/* uninstall interrupt handler */
free_irq (net_irq,NULL);
return 0;
}
static int scull_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
struct file_operations scull_fops = {
.owner= THIS_MODULE,
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release,
.ioctl = scull_ioctl,
};
int __init scull_init(void)
{
int rc;
cs8900_probe();
/*Register myirq as character device*/
if ((rc = register_chrdev(netirq_MAJOR, DEVICENAME , &scull_fops)) < 0){
printk("net driver: can't get major %d\n", netirq_MAJOR);
return 1;
}
return 0;
}
void __exit scull_exit(void)
{
/* release_region (cs8900_base_addr,16); */
release_mem_region (cs8900_base_addr,16);
unregister_chrdev(netirq_MAJOR, DEVICENAME);
}
MODULE_AUTHOR ("username");
MODULE_DESCRIPTION ("a scull&net device driver");
MODULE_LICENSE ("GPL");
module_init(scull_init);
module_exit(scull_exit);
阅读(2427) | 评论(0) | 转发(1) |