星闪夜空 发表于 2012-12-16 10:57

编写Linux TTY设备驱动遇到问题

首先贴出自己的仿照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_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={'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,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;

   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=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);
   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&PARENB)//是否设置奇偶校验值
      if(cflag&PARODD)
         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_LOOP0x04
#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<PAGE_SIZE;++i)//length不能超过一页
   {
      tiny=tiny_table;
      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;          //得到tty设备
      if(tiny)
      {
         //关闭端口
         while(tiny->open_count)
            do_close(tiny);
   
         kfree(tiny);            //释放设备内存空间

         tiny_table=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/0x18)
r7:00000000 r6:c3e21e68 r5:c3e21e00 r4:c3e21e00
[<c0178b74>] (kobject_add+0x0/0x18) 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/0xd8)
r8:00000000 r7:c3dade58 r6:bf001360 r5:00000000 r4:c06ee600
[<c019a70c>] (tty_register_device+0x0/0xd8) from [<bf003118>] (tiny_init+0x118/0x15c )
[<bf003000>] (tiny_init+0x0/0x15c ) 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中编译加载模块并没有问题,所以我自己的想法是因为内核版本的问题才导致加载出现问题,但是我不知道应该怎么去修改,还望大神指教!!!

done_and_todo 发表于 2012-12-19 10:37

你在本地编译好后,加载有问题吗?
还有/dev/ 下没mytty0文件是正确的,你得cat /proc/devices 看下有没有mytty0 再用mknod 命令创建 mytty0 设备文件。

星闪夜空 发表于 2012-12-20 14:53

回复 2# done_and_todo


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

cosmoslhf 发表于 2013-12-12 18:49

楼主 这个问题还记得 是怎么解决的吗 ? 谢谢
页: [1]
查看完整版本: 编写Linux TTY设备驱动遇到问题