免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3677 | 回复: 3
打印 上一主题 下一主题

编写Linux TTY设备驱动遇到问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-12-16 10:57 |只看该作者 |倒序浏览
首先贴出自己的仿照LDD3上写的TTY设备驱动的源码:
   

/*所有的模块代码都包含下面两行代码*/
#include <linux/init.h>
#include <linux/module.h>

#include <linux/kernel.h>      //printk
#include <linux/errno.h>       //err
#include <linux/slab.h>        //kmalloc()
#include <linux/tty.h>         //tty_struct
#include <linux/tty_driver.h>  //tty_driver
#include <linux/tty_flip.h>    //tty_flip_buffer_push()
#include <asm/uaccess.h>       //copy_to_user()
#include <linux/wait.h>        //wait_queue_head_t
#include <asm/semaphore.h>     //semaphore
#include <linux/timer.h>       //timer
#include <linux/serial.h>      //serial_struct
   

#define DRIVER_VERSION "v2.0"

#define TINY_TTY_MAJOR 240     //主设备号
#define TINY_TTY_MINORS 4      //设备数

#define DELAY_TIME HZ*2


struct tiny_serial{
   struct tty_struct *tty;    /*指向该设备的tty指针*/
   int open_count;            /*该端口被打开的次数*/
   struct semaphore sem;      /*锁住该结构*/
   struct timer_list *timer;  /*申明一个定时器结构体*/
   
   int msr;  //MODEM状态寄存器
   int mcr;  //MODEM控制寄存器

   struct serial_struct serial;  //定义串口寄存器结构体

   wait_queue_head_t wait;     //定义等待队列头     
};
static struct tiny_serial *tiny_table[TINY_TTY_MINORS];  /*初始化所有的tiny_serial为0*/

static struct tty_driver *tiny_tty_driver; //定义一个tty_driver指针


//定时器处理函数
static void tiny_timer(unsigned long timer_data)
{
   struct tiny_serial *tiny=(struct tiny_serial *)timer_data;
   struct tty_struct *tty;
   int i;
   char data[1]={'t'};
   int data_size=1;

   if(!tiny) //设备为NULL
     return;
   
   tty=tiny->tty; //得到tty_struct
   
   for(i=0;i<data_size;++i)
   {
      if((*tty).count>=TTY_FLIPBUF_SIZE)
         tty_flip_buffer_push(tty);   //将交替缓冲区中数据发送给用户
      tty_insert_flip_char(tty,data[i],TTY_NORMAL); /*将准备发给用户的字符添加到交替缓冲区中*/
   }
   tty_flip_buffer_push(tty);

   //重新设置定时器
   tiny->timer->expires=jiffies+DELAY_TIME;
   add_timer(tiny->timer);
}

static int tiny_open(struct tty_struct *tty,struct file *file)
{
   struct tiny_serial *tiny;  
   int index;

   tty->driver_data=NULL;     //初始化保存私有数据的空间
   
   index=tty->index;          /*获得与tty指针相关的串口对象*/
   tiny=tiny_table[index];

   if(tiny==NULL)
   {
      /*第一次访问该设备,创建它*/
      tiny=kmalloc(sizeof(*tiny),GFP_KERNEL);
      if(!tiny)
         return -ENOMEM;

      init_MUTEX(&tiny->sem); /*把信号量sem的值设为1*/
      tiny->open_count=0;     /*初始化引用计数器*/

      tiny_table[index]=tiny;
   }
  
   down(&tiny->sem);    //获取信号量
  
   tty->driver_data=tiny;  /*在tty结构中保存上述结构*/
   tiny->tty=tty;

   /*保存设备被打开的次数*/
   ++tiny->open_count;
   if(tiny->open_count==1)
   {
      //动态初始化定时器
      tiny->timer->data=(unsigned long)tiny;  /*初始化function和data*/
      tiny->timer->function=tiny_timer;
      init_timer(tiny->timer);
      //指定触发的时间
      tiny->timer->expires=jiffies+DELAY_TIME;//延时2秒
      //加入系统定时器链表
      add_timer(tiny->timer);      
   }

   up(&tiny->sem);

   return 0;
}

static void do_close(struct tiny_serial *tiny)
{
   down(&tiny->sem);    /*获取信号量*/
   
   if(!tiny->open_count)
      //端口从未被打开
      goto exit;

    --tiny->open_count;
    if(tiny->open_count<=0)
    {
       /*最后一个用户已经将端口关闭*/
       del_timer(tiny->timer); //在定时器没有到期前关闭
    }
exit:
    up(&tiny->sem);     /*释放信号量*/
}

static void tiny_close(struct tty_struct *tty,struct file *file)
{
   struct tiny_serial *tiny=tty->driver_data;
   if(tiny)
      do_close(tiny);
}

static int tiny_write(struct tty_struct *tty,const unsigned char *buffer,int count)
{
   struct tiny_serial *tiny=tty->driver_data;
   int i;
   int retval=-EINVAL;

   if(!tiny)
      //驱动程序没有设置open成员
      return -ENODEV;
   
   down(&tiny->sem);
   if(!tiny->open_count)
      //端口未被打开
      goto exit;
  
   /*将数据写入内核调试日志,来伪装将数据发送出硬件端口*/
   printk(KERN_DEBUG "%s - ",__FUNCTION__);
   for(i=0;i<count;++i)
      printk("%02x\n",buffer[i]);
   printk("\n";

exit:
   up(&tiny->sem);
   
   return retval;
}

static int tiny_write_room(struct tty_struct *tty)
{
   struct tiny_serial *tiny=tty->driver_data;
   int room=-EINVAL;

   if(!tiny)
      return -ENODEV;
   
   down(&tiny->sem);

   if(!tiny->open_count)
      goto exit;  //端口没有被打开

   /*计算设备中可用空间的大小*/
   room=255;

exit:
   up(&tiny->sem);

   return room;
}


#define RELEVANT_IFLAG(iflag) ((iflag)&(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
static void tiny_set_termios(struct tty_struct *tty, struct ktermios * old_termios)
{
   unsigned int cflag;
   
   cflag=tty->termios->c_cflag; //得到驱动程序的termios设置
   
   /*检查以保证确实有参数要被改变*/
   if(old_termios)  //先检查是否是个合法的指针
   {
      if((cflag==old_termios->c_cflag)&&
         (RELEVANT_IFLAG(tty->termios->c_iflag)==
          RELEVANT_IFLAG(old_termios->c_iflag)))
      {
         printk(KERN_DEBUG"nothing to change...\n";

         return;
      }
   }

   /*获得字节大小*/
   switch(cflag & CSIZE)
   {
      case CS5:
          printk(KERN_DEBUG "- data bits=5\n";
          break;
      case CS6:
          printk(KERN_DEBUG "- data bits=6\n";
          break;
      case CS7:
          printk(KERN_DEBUG "- data bits=7\n";
          break;
      default:
      case CS8:
          printk(KERN_DEBUG "- data bits=8\n";
          break;
   }

   /*判断奇偶*/
   if(cflag&ARENB)  //是否设置奇偶校验值
      if(cflag&ARODD)
         printk(KERN_DEBUG"- parity=odd\n";
      else
         printk(KERN_DEBUG"- parity=even\n";
    else
       printk(KERN_DEBUG"- parity=none\n";

   /*确定需要的停止位*/
   if(cflag & CSTOPB)
      printk(KERN_DEBUG"- stop bits=2\n";
   else
      printk(KERN_DEBUG"- stop bits=1\n");

   /*确定硬件流控制设置*/
   if(cflag & CRTSCTS)
      printk(KERN_DEBUG"-RTS/CTS is enabled\n");
   else
      printk(KERN_DEBUG"-RTS/CTS is disabled\n");

   /*确定软件流控制*/
   //如果实现了XON/XOFF,设置设备中开始及结束字符
   if(I_IXOFF(tty)||I_IXON(tty))
   {
      unsigned char stop_char = STOP_CHAR(tty);
      unsigned char start_char = START_CHAR(tty);

      //如果实现了XON/XOFF
      if(I_IXOFF(tty))
         printk(KERN_DEBUG"- INBOUND XON/XOFF is enabled,XON=%2x,XOFF=%2x",start_char,stop_char);
      else
         printk(KERN_DEBUG"- INBOUND XON/XOFF is disabled");
      //如果实现了OUTBOUND XON/XOFF
      if(I_IXON(tty))
         printk(KERN_DEBUG"- OUTBOUND XON/XOFF is enabled,XON=%2x,XOFF=%2x",start_char,stop_char);
      else
         printk(KERN_DEBUG"- OUTBOUND XON/XOFF is disabled");
   }

   //获得需要的波特率
   printk(KERN_DEBUG"- baud rate = %d",tty_get_baud_rate(tty));
   
}

#define MCR_DTR   0x01
#define MCR_RTS   0x02
#define MCR_LOOP  0x04
#define MSR_CTS   0x08
#define MSR_CD    0x10
#define MSR_RI    0x20
#define MSR_DSR   0x40

static int tiny_tiocmget(struct tty_struct *tty,struct file *file)
{
   struct tiny_serial *tiny=tty->driver_data;

   unsigned int result=0;
   unsigned int msr=tiny->msr;
   unsigned int mcr=tiny->mcr;

   result=((mcr&MCR_DTR)  ? TIOCM_DTR  : 0) |   /*设置了DTR*/
          ((mcr&MCR_RTS)  ? TIOCM_RTS  : 0) |   /*设置了RTS*/
          ((mcr&MCR_LOOP) ? TIOCM_LOOP : 0) |   /*设置了LOOP*/
          ((msr&MSR_CTS)  ? TIOCM_CTS  : 0) |   /*设置了CTS*/
          ((msr&MSR_CD)   ? TIOCM_CAR  : 0) |   /*设置了Carrier detect*/
          ((msr&MSR_RI)   ? TIOCM_RI   : 0) |   /*设置了Ring Indicator*/
          ((msr&MSR_DSR)  ? TIOCM_DSR  : 0);    /*设置了DSR*/
   
   return result;
}

static int tiny_tiocmset(struct tty_struct *tty,struct file *file,unsigned int set,unsigned int clear)
{
   struct tiny_serial *tiny=tty->driver_data;
   unsigned int mcr=tiny->mcr;                //得到MODEM控制寄存器

   if(set & TIOCM_RTS)
      mcr |=MCR_RTS;
   if(set & TIOCM_DTR)
      mcr |=MCR_RTS;

   if(clear & TIOCM_RTS)
      mcr &=~MCR_RTS;
   if(clear & TIOCM_DTR)
      mcr &=~MCR_RTS;

   /*设置设备中新的MCR值*/
   tiny->mcr=mcr;

   return 0;
}

static int tiny_read_proc(char *page,char **start,off_t off,int count,int *eof,void *data)
{
   struct tiny_serial *tiny;
   off_t begin=0;
   int length=0;
   int i;

   length+=sprintf(page,"tinyserinfo:1.0 driver:%s\n",DRIVER_VERSION);
   for(i=0;i<TINY_TTY_MINORS  && length<AGE_SIZE;++i)  //length不能超过一页
   {
      tiny=tiny_table[i];
      if(tiny==NULL)
         continue;

      length+=sprintf(page+length,"%d\n",i);
      if((length+begin)>(off+count))        //达到了读数据的范围
         goto done;
      if((length+begin)<off)
      {
         begin +=length;
         length=0;
      }
   }
   *eof=1;

done:
   if(off>=(length+begin))  //判断读数据的位置还没有到off
      return 0;
   *start=page+(off-begin); //调整start
   return (count<begin+length-off) ? count :begin+length-off;
}


static struct tty_operations serial_ops={
   .open       =  tiny_open,
   .close      =  tiny_close,
   .write      =  tiny_write,
   .write_room =  tiny_write_room,  /*该函数用于检测缓冲区的剩余空间*/
   .set_termios=  tiny_set_termios, /*当设备的termios设置发生改变时,该函数被调用*/
};

static int __init tiny_init(void)
{
   int retval,i;
   
   /*分配tty驱动程序*/
   tiny_tty_driver=alloc_tty_driver(TINY_TTY_MINORS);
   if(!tiny_tty_driver)
      return -ENOMEM;

   /*初始化tty驱动程序*/
   tiny_tty_driver->owner = THIS_MODULE;
   tiny_tty_driver->driver_name = "tiny_tty";     //在/proc/tty和sysfs中使用
   tiny_tty_driver->name = "mytty";               //驱动程序节点的名字
   tiny_tty_driver->major = TINY_TTY_MAJOR;       //tty的主设备号
   tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;//可以被任何串行类驱动程序使用
   tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL;
   tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW;
   tiny_tty_driver->init_termios = tty_std_termios;
   tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;

   tty_set_operations(tiny_tty_driver,&serial_ops); /*复制到tty_driver对应的函数指针*/
   
   tiny_tty_driver->read_proc=tiny_read_proc;       /*proc*/
   tiny_tty_driver->tiocmget=tiny_tiocmget;         /*获得特定tty设备当前的线路设置*/
   tiny_tty_driver->tiocmset=tiny_tiocmset;         /*为特定的tty设备设置当前线路*/

   /*注册tty驱动程序*/
   retval=tty_register_driver(tiny_tty_driver);
   if(retval)
   {
      printk(KERN_DEBUG"failed to register tiny tty driver:\n");
      put_tty_driver(tiny_tty_driver);  /*注册不成功,该函数负责清空tty_driver结构*/
      return retval;
   }

   /*注册所有的tty设备(虚拟)*/
   for(i=0;i<TINY_TTY_MINORS;++i)
     tty_register_device(tiny_tty_driver,i,NULL);

   printk(KERN_DEBUG"Tiny TTY driver register success:\n");
  
   return retval;
}

static void __exit tiny_exit(void)
{
   struct tiny_serial *tiny;
   int i;

   /*注销驱动程序*/
   for(i=0;i<TINY_TTY_MINORS;++i)
     tty_unregister_device(tiny_tty_driver,i);
   tty_unregister_driver(tiny_tty_driver);

   for(i=0;i<TINY_TTY_MINORS;++i)  //依次遍历每一个tty设备
   {
      tiny=tiny_table[i];          //得到tty设备
      if(tiny)
      {
         //关闭端口
         while(tiny->open_count)
            do_close(tiny);
     
         kfree(tiny);              //释放设备内存空间

         tiny_table[i]=NULL;
      }
   }
}

module_init(tiny_init);
module_exit(tiny_exit);

MODULE_AUTHOR("chenqi");
MODULE_LICENSE("GPL");
  
注:我的VM内核是Linux2.6.18,我的ARM开发板的内核是linux2.6.22,另外在 /dev/ 中并没有mytty0设备文件。

在我将编译好的模块加载进ARM开发板内核时,出现如下的错误:

kobject_add failed for mytty0 with -EEXIST, don't try to register things with the same name in the same directory.
[<c002fde8>] (dump_stack+0x0/0x14) from [<c0178b58>] (kobject_shadow_add+0x190/0x1ac)
[<c01789c8>] (kobject_shadow_add+0x0/0x1ac) from [<c0178b88>] (kobject_add+0x14/0x1
r7:00000000 r6:c3e21e68 r5:c3e21e00 r4:c3e21e00
[<c0178b74>] (kobject_add+0x0/0x1 from [<c01ba628>] (device_add+0x88/0x53c)
[<c01ba5a0>] (device_add+0x0/0x53c) from [<c01baaf8>] (device_register+0x1c/0x20)
[<c01baadc>] (device_register+0x0/0x20) from [<c01baf64>] (device_create+0x7c/0xa0)
r4:c3e21e00
[<c01baeec>] (device_create+0x4/0xa0) from [<c019a7c8>] (tty_register_device+0xbc/0xd
r8:00000000 r7:c3dade58 r6:bf001360 r5:00000000 r4:c06ee600
[<c019a70c>] (tty_register_device+0x0/0xd from [<bf003118>] (tiny_init+0x118/0x15c [mytty])
[<bf003000>] (tiny_init+0x0/0x15c [mytty]) from [<c0063854>] (sys_init_module+0x1424/0x1514)
r5:bf001360 r4:00000000
[<c0062430>] (sys_init_module+0x0/0x1514) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
Tiny TTY driver register success

     但是在我的VM中编译加载模块并没有问题,所以我自己的想法是因为内核版本的问题才导致加载出现问题,但是我不知道应该怎么去修改,还望大神指教!!!

论坛徽章:
0
2 [报告]
发表于 2012-12-19 10:37 |只看该作者
你在本地编译好后,加载有问题吗?
还有/dev/ 下没mytty0文件是正确的,你得cat /proc/devices 看下有没有mytty0 再用mknod 命令创建 mytty0 设备文件。

论坛徽章:
0
3 [报告]
发表于 2012-12-20 14:53 |只看该作者
回复 2# done_and_todo


    可能是我的描述让你误解了:我说的在 /dev/ 下没mytty0文件是在我没有加载驱动的时候,而在我加载驱动后,虽然加载过程中出现错误,但是在/dev/ 下还是有mytty0文件。
   
                             另外在本地虚拟机中加载驱动没有任何的问题。我的虚拟机内核版本为2.6.18,而我的开发板中的系统内核版本为2.6.22,所以我自己认为导致问题出现的原因是内核版本的不同。

论坛徽章:
0
4 [报告]
发表于 2013-12-12 18:49 |只看该作者
楼主 这个问题还记得 是怎么解决的吗 ? 谢谢
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP