天天看点

linux GSM0710

1. 前言:

     关于MUX的功能实现可以参考这篇博客​

      在linux嵌入式平台GPRS联网中,我们通常除了pppd拨号之外、还需要AT指令收发、短信功能...而在实际中只有一个真实的物理串口与GPRS模块通讯,为了同时支持pppd+AT+短信的多种功能引入了MUX多路复用协议,它的目的是虚拟出多个逻辑串口来满足以上同时通讯的要求!

2. gsm驱动源码

2.1 线路规程操作函数结构体

struct tty_ldisc_ops {
  int  magic; //幻数,每种tty对应不同的幻数
  char  *name; //线路规程名称
  int  num; //线路规程支持的个数
  int  flags;

  /*
   * The following routines are called from above.
   */
  int  (*open)(struct tty_struct *);
  void  (*close)(struct tty_struct *);
  void  (*flush_buffer)(struct tty_struct *tty);
  ssize_t  (*chars_in_buffer)(struct tty_struct *tty);
  ssize_t  (*read)(struct tty_struct *tty, struct file *file,
      unsigned char __user *buf, size_t nr);
  ssize_t  (*write)(struct tty_struct *tty, struct file *file,
       const unsigned char *buf, size_t nr);
  int  (*ioctl)(struct tty_struct *tty, struct file *file,
       unsigned int cmd, unsigned long arg);
  long  (*compat_ioctl)(struct tty_struct *tty, struct file *file,
        unsigned int cmd, unsigned long arg);
  void  (*set_termios)(struct tty_struct *tty, struct ktermios *old);
  unsigned int (*poll)(struct tty_struct *, struct file *,
           struct poll_table_struct *);
  int  (*hangup)(struct tty_struct *tty);

  /*
   * The following routines are called from below.
   */
  void  (*receive_buf)(struct tty_struct *, const unsigned char *cp,
             char *fp, int count);
  void  (*write_wakeup)(struct tty_struct *);
  void  (*dcd_change)(struct tty_struct *, unsigned int);

  struct  module *owner;

  int refcount;
};      
struct tty_ldisc_ops tty_ldisc_packet = {
  .owner     = THIS_MODULE,
  .magic           = TTY_LDISC_MAGIC,
  .name            = "n_gsm",
  .open            = gsmld_open,
  .close           = gsmld_close,
  .flush_buffer    = gsmld_flush_buffer,
  .chars_in_buffer = gsmld_chars_in_buffer,
  .read            = gsmld_read,
  .write           = gsmld_write,
  .ioctl           = gsmld_ioctl,
  .poll            = gsmld_poll,
  .receive_buf     = gsmld_receive_buf,
  .write_wakeup    = gsmld_write_wakeup
};      

2.2 gsm虚拟tty操作结构体

这个结构体表示的是虚拟串口,应用程序调用open(...) read(...)...首先就是使用gsmtty,然后gsmtty调用线路规程gsmld

struct tty_operations {
  struct tty_struct * (*lookup)(struct tty_driver *driver,
      struct inode *inode, int idx);
  int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
  void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
  int  (*open)(struct tty_struct * tty, struct file * filp);
  void (*close)(struct tty_struct * tty, struct file * filp);
  void (*shutdown)(struct tty_struct *tty);
  void (*cleanup)(struct tty_struct *tty);
  int  (*write)(struct tty_struct * tty,
          const unsigned char *buf, int count);
  int  (*put_char)(struct tty_struct *tty, unsigned char ch);
  void (*flush_chars)(struct tty_struct *tty);
  int  (*write_room)(struct tty_struct *tty);
  int  (*chars_in_buffer)(struct tty_struct *tty);
  int  (*ioctl)(struct tty_struct *tty,
        unsigned int cmd, unsigned long arg);
  long (*compat_ioctl)(struct tty_struct *tty,
           unsigned int cmd, unsigned long arg);
  void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
  void (*throttle)(struct tty_struct * tty);
  void (*unthrottle)(struct tty_struct * tty);
  void (*stop)(struct tty_struct *tty);
  void (*start)(struct tty_struct *tty);
  void (*hangup)(struct tty_struct *tty);
  int (*break_ctl)(struct tty_struct *tty, int state);
  void (*flush_buffer)(struct tty_struct *tty);
  void (*set_ldisc)(struct tty_struct *tty);
  void (*wait_until_sent)(struct tty_struct *tty, int timeout);
  void (*send_xchar)(struct tty_struct *tty, char ch);
  int (*tiocmget)(struct tty_struct *tty);
  int (*tiocmset)(struct tty_struct *tty,
      unsigned int set, unsigned int clear);
  int (*resize)(struct tty_struct *tty, struct winsize *ws);
  int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
  int (*get_icount)(struct tty_struct *tty,
        struct serial_icounter_struct *icount);
#ifdef CONFIG_CONSOLE_POLL
  int (*poll_init)(struct tty_driver *driver, int line, char *options);
  int (*poll_get_char)(struct tty_driver *driver, int line);
  void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
  const struct file_operations *proc_fops;
};      
/* Virtual ttys for the demux */
static const struct tty_operations gsmtty_ops = {
  .install    = gsmtty_install,
  .open      = gsmtty_open,
  .close      = gsmtty_close,
  .write      = gsmtty_write,
  .write_room    = gsmtty_write_room,
  .chars_in_buffer  = gsmtty_chars_in_buffer,
  .flush_buffer    = gsmtty_flush_buffer,
  .ioctl      = gsmtty_ioctl,
  .throttle    = gsmtty_throttle,
  .unthrottle    = gsmtty_unthrottle,
  .set_termios    = gsmtty_set_termios,
  .hangup      = gsmtty_hangup,
  .wait_until_sent  = gsmtty_wait_until_sent,
  .tiocmget    = gsmtty_tiocmget,
  .tiocmset    = gsmtty_tiocmset,
  .break_ctl    = gsmtty_break_ctl,
};      

2.3 gsm_init(...)驱动初始化

static int __init gsm_init(void)
{
  /* Fill in our line protocol discipline, and register it */
  int status = tty_register_ldisc(N_GSM0710, &tty_ldisc_packet); //注册线路规程,见下面分析
  if (status != 0) {
    pr_err("n_gsm: can't register line discipline (err = %d)\n",
                status);
    return status;
  }
    
  gsm_tty_driver = alloc_tty_driver(256); //动态分配256个tty驱动,见下面分析
  if (!gsm_tty_driver) {
    tty_unregister_ldisc(N_GSM0710);
    pr_err("gsm_init: tty allocation failed.\n");
    return -EINVAL;
  }
  gsm_tty_driver->driver_name  = "gsmtty"; //绑定驱动的名称为“gsmtty”
  gsm_tty_driver->name    = "gsmtty";
  gsm_tty_driver->major    = 0;  /* Dynamic */
  gsm_tty_driver->minor_start  = 0;
  gsm_tty_driver->type    = TTY_DRIVER_TYPE_SERIAL;
  gsm_tty_driver->subtype  = SERIAL_TYPE_NORMAL;
  gsm_tty_driver->flags  = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV
            | TTY_DRIVER_HARDWARE_BREAK;
  gsm_tty_driver->init_termios  = tty_std_termios; //初始化tty的标准输入输入口配置
  /* Fixme */
  gsm_tty_driver->init_termios.c_lflag &= ~ECHO;
    
  tty_set_operations(gsm_tty_driver, &gsmtty_ops); //绑定gsm_tty_driver驱动的ops为gsmtty_ops

  spin_lock_init(&gsm_mux_lock);

  if (tty_register_driver(gsm_tty_driver)) { //注册gsm_tty_driver驱动,见下面分析!
    put_tty_driver(gsm_tty_driver);
    tty_unregister_ldisc(N_GSM0710);
    pr_err("gsm_init: tty registration failed.\n");
    return -EBUSY;
  }
  pr_debug("gsm_init: loaded as %d,%d.\n",
      gsm_tty_driver->major, gsm_tty_driver->minor_start);
  return 0;
}      

线路规程注册tty_register_ldisc(...):

int status = tty_register_ldisc(N_GSM0710, &tty_ldisc_packet); //注册线路规程      
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
  unsigned long flags;
  int ret = 0;

  if (disc < N_TTY || disc >= NR_LDISCS)
    return -EINVAL;

  raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
  tty_ldiscs[disc] = new_ldisc; //绑定线路规程
  new_ldisc->num = disc; //绑定该线路规程的编号
  new_ldisc->refcount = 0;
  raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);

  return ret;
}      

tty_ldiscs[*]为全局变量,根据disc变量为

tty_ldiscs[*]数组的索引用来绑定新的线路规程操作接口。

动态分配tty驱动alloc_tty_driver(...):

gsm_tty_driver = alloc_tty_driver(256);      
static inline struct tty_driver *alloc_tty_driver(unsigned int lines)
{
  struct tty_driver *ret = tty_alloc_driver(lines, 0);
  if (IS_ERR(ret))
    return NULL;
  return ret;
}      
#define tty_alloc_driver(lines, flags) \
    __tty_alloc_driver(lines, THIS_MODULE, flags)      
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
    unsigned long flags)
{
  struct tty_driver *driver;
  unsigned int cdevs = 1;
  int err;

  if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))
    return ERR_PTR(-EINVAL);

  driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
  if (!driver)
    return ERR_PTR(-ENOMEM);

  kref_init(&driver->kref);
  driver->magic = TTY_DRIVER_MAGIC; //初始化该驱动的幻数
  driver->num = lines; //驱动个数
  driver->owner = owner;
  driver->flags = flags;

  if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {
    driver->ttys = kcalloc(lines, sizeof(*driver->ttys), //分配lines个数ttys
        GFP_KERNEL);
    driver->termios = kcalloc(lines, sizeof(*driver->termios), //分配lines个数termios
        GFP_KERNEL);
    if (!driver->ttys || !driver->termios) {
      err = -ENOMEM;
      goto err_free_all;
    }
  }

  if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
    driver->ports = kcalloc(lines, sizeof(*driver->ports), //分配lines个数ports
        GFP_KERNEL);
    if (!driver->ports) {
      err = -ENOMEM;
      goto err_free_all;
    }
    cdevs = lines;
  }

  driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
  if (!driver->cdevs) {
    err = -ENOMEM;
    goto err_free_all;
  }

  return driver; //返回该驱动给gsm_tty_driver
err_free_all:
  kfree(driver->ports);
  kfree(driver->ttys);
  kfree(driver->termios);
  kfree(driver);
  return ERR_PTR(err);
}      

tty驱动注册tty_register_driver(...):

if (tty_register_driver(gsm_tty_driver)) {      
int tty_register_driver(struct tty_driver *driver)
{
  int error;
  int i;
  dev_t dev;
  struct device *d;

  if (!driver->major) { //major为0,这里动态分配字符设备号
    error = alloc_chrdev_region(&dev, driver->minor_start,
            driver->num, driver->name); //分配num个字符设备,num=256
    if (!error) {
      driver->major = MAJOR(dev); //主设备号
      driver->minor_start = MINOR(dev); //次设备号的起始值
    }
  } else {
    dev = MKDEV(driver->major, driver->minor_start);
    error = register_chrdev_region(dev, driver->num, driver->name);
  }
  if (error < 0)
    goto err;

  if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
    error = tty_cdev_add(driver, dev, 0, driver->num);
    if (error)
      goto err_unreg_char;
  }

  mutex_lock(&tty_mutex);
  list_add(&driver->tty_drivers, &tty_drivers); //将当前tty驱动增加到全局链表tty_drivers中
  mutex_unlock(&tty_mutex);

  if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
    for (i = 0; i < driver->num; i++) { //num=256
      d = tty_register_device(driver, i, NULL); //tty设备注册
      if (IS_ERR(d)) {
        error = PTR_ERR(d);
        goto err_unreg_devs;
      }
    }
  }
  proc_tty_register_driver(driver); //proc文件系统下的tty驱动注册,重要!!!在创建ttyGSM1...时就是检索proc文件系统而创建的!!!见下面分析
  driver->flags |= TTY_DRIVER_INSTALLED;
  return 0;

err_unreg_devs:
  for (i--; i >= 0; i--)
    tty_unregister_device(driver, i);

  mutex_lock(&tty_mutex);
  list_del(&driver->tty_drivers);
  mutex_unlock(&tty_mutex);

err_unreg_char:
  unregister_chrdev_region(dev, driver->num);
err:
  return error;
}      

tty设备注册tty_register_device(...):

struct device *tty_register_device(struct tty_driver *driver, unsigned index,
           struct device *device)
{
  return tty_register_device_attr(driver, index, device, NULL, NULL);
}      
struct device *tty_register_device_attr(struct tty_driver *driver,
           unsigned index, struct device *device,
           void *drvdata,
           const struct attribute_group **attr_grp)
{
  char name[64];
  dev_t devt = MKDEV(driver->major, driver->minor_start) + index; //组合主次设备号
  struct device *dev = NULL;
  int retval = -ENODEV;
  bool cdev = false;

  if (index >= driver->num) {
    printk(KERN_ERR "Attempt to register invalid tty line number "
           " (%d).\n", index);
    return ERR_PTR(-EINVAL);
  }

  if (driver->type == TTY_DRIVER_TYPE_PTY) //type==TTY_DRIVER_TYPE_SERIAL,条件不成立
    pty_line_name(driver, index, name);
  else
    tty_line_name(driver, index, name);  //创建name="gsmtty0","gsmtty1"...

  if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
    retval = tty_cdev_add(driver, devt, index, 1); //tty字符设备增加
    if (retval)
      goto error;
    cdev = true;
  }

  dev = kzalloc(sizeof(*dev), GFP_KERNEL);
  if (!dev) {
    retval = -ENOMEM;
    goto error;
  }

  dev->devt = devt;
  dev->class = tty_class;
  dev->parent = device;
  dev->release = tty_device_create_release;
  dev_set_name(dev, "%s", name);
  dev->groups = attr_grp;
  dev_set_drvdata(dev, drvdata);

  retval = device_register(dev);
  if (retval)
    goto error;

  return dev;

error:
  put_device(dev);
  if (cdev)
    cdev_del(&driver->cdevs[index]);
  return ERR_PTR(retval);
}      
static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
    unsigned int index, unsigned int count)
{
  /* init here, since reused cdevs cause crashes */
  cdev_init(&driver->cdevs[index], &tty_fops); //注意这里的tty_fops,用来绑定应用程序在打开相应的串口设备时调用的操作函数!!!!!!
  driver->cdevs[index].owner = driver->owner;
  return cdev_add(&driver->cdevs[index], dev, count); //注册字符设备
}      
static const struct file_operations tty_fops = {
  .llseek    = no_llseek,
  .read    = tty_read,
  .write    = tty_write,
  .poll    = tty_poll,
  .unlocked_ioctl  = tty_ioctl,
  .compat_ioctl  = tty_compat_ioctl,
  .open    = tty_open,
  .release  = tty_release,
  .fasync    = tty_fasync,
};      

3. 应用程序操作底层驱动串口时流程

先贴出应用程序操作gsm-mux多路复用协议时的操作流程:

#include <linux/gsmmux.h> 
  #define N_GSM0710 21  /* GSM 0710 Mux */ 
  #define DEFAULT_SPEED B115200 
  #define SERIAL_PORT /dev/ttyS0
  
  int ldisc = N_GSM0710;
  struct gsm_config c;
  struct termios configuration;
  
  /* open the serial port connected to the modem */
  fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY); //打开一个真实串口/dev/ttyS0
  /* configure the serial port : speed, flow control ... */
  /* send the AT commands to switch the modem to CMUX mode and check that it's successful (should return OK) */ 
  write(fd, "AT+CMUX=0\r", 10); //发送AT+CMUX多路复用协议指令到MODEM,告诉MODEM需要切换到MUX复用协议
  /* experience showed that some modems need some time before being able to answer to the first MUX packet so a delay may be needed here in some case */ 
  sleep(3);
  /* use n_gsm line discipline */
  ioctl(fd, TIOCSETD, &ldisc); //设置fd文件描述符,即/dev/ttyS0的线路规程为N_GSM0710,下面会重点分析tty_io.c层是如何处理的!!!
  /* get n_gsm configuration */
  ioctl(fd, GSMIOC_GETCONF, &c);  //获取GSMIOC配置
  /* we are initiator and need encoding 0 (basic) */
  c.initiator = 1; //1:初始化为高级模式
  c.encapsulation = 0;
  /* our modem defaults to a maximum size of 127 bytes */
  c.mru = 127; //最大接收单元
  c.mtu = 127; //最大传输单元
  /* set the new configuration */
  ioctl(fd, GSMIOC_SETCONF, &c); //设置GSMIOC配置
  /* and wait for ever to keep the line discipline enabled */
  daemon(0,0);
  pause();
4- create the devices corresponding to the "virtual" serial ports (take care, each modem has its configuration and some DLC have dedicated functions, for example GPS), starting with minor 1 (DLC0 is reserved for the management of the mux)
MAJOR=`cat /proc/devices |grep gsmtty | awk '{print $1}` for i in `seq 1 4`; do mknod /dev/ttygsm$i c $MAJOR $i done
5- use these devices as plain serial ports. for example, it's possible :
- and to use gnokii to send / receive SMS on ttygsm1
- to use ppp to establish a datalink on ttygsm2
6- first close all virtual ports before closing the physical port.      

下面具体分析应用程序的执行流程:

3.1 open(...)

open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);      

对应内核tty_io.c文件:

static int tty_open(struct inode *inode, struct file *filp)
{
  struct tty_struct *tty;
  int noctty, retval;
  struct tty_driver *driver = NULL;
  int index;
  dev_t device = inode->i_rdev;   //打开任何一个设备,如/dev/ttyGSM时,该设备的所有信息都在inode节点中,其中包括主设备、从设备...
  unsigned saved_flags = filp->f_flags;

  nonseekable_open(inode, filp);

retry_open:
  retval = tty_alloc_file(filp);  
  if (retval)
    return -ENOMEM;

  noctty = filp->f_flags & O_NOCTTY;
  index  = -1;
  retval = 0;

  mutex_lock(&tty_mutex);
  /* This is protected by the tty_mutex */
  tty = tty_open_current_tty(device, filp);
  if (IS_ERR(tty)) {
    retval = PTR_ERR(tty);
    goto err_unlock;
  } else if (!tty) {
    driver = tty_lookup_driver(device, filp, &noctty, &index); //通过device主次设备号集合寻找在全局变量tty_drivers是否有匹配的驱动,成功就返回在tty_drivers数组中index索引,如果该函数内部的处理不记得了,可以再回到上面注册gsmtty驱动加入到tty_drivers中顺序在看一遍
    if (IS_ERR(driver)) {
      retval = PTR_ERR(driver);
      goto err_unlock;
    }

    /* check whether we're reopening an existing tty */
    tty = tty_driver_lookup_tty(driver, inode, index); //根据index索引在driver中返回对应的tty[index]
    if (IS_ERR(tty)) { 
      retval = PTR_ERR(tty);
      goto err_unlock;
    }
  }

  if (tty) { //这里为真
    tty_lock(tty);
    retval = tty_reopen(tty); //打开tty设备,该函数内部会对该tty设备打开的次数进行统计!!!原来测试K32的bug就是因为应用层打开之后没有关闭串口,导致内部统计次数一直在增加!!!
    if (retval < 0) {
      tty_unlock(tty);
      tty = ERR_PTR(retval);
    }
  } else  /* Returns with the tty_lock held for now */
    tty = tty_init_dev(driver, index);   //上面tty条件不成立时,需初始化一个tty设备,该函数内部的操作流程为,1.初始化一个tty结构体,设置该tty的线路规程默认为N_TTY, 2.绑定该tty->ops为driver->ops, 3.安装该tty设备driver->ops->install
  mutex_unlock(&tty_mutex);
  if (driver)
    tty_driver_kref_put(driver);
  if (IS_ERR(tty)) {
    retval = PTR_ERR(tty);
    goto err_file;
  }

  tty_add_file(tty, filp);

  check_tty_count(tty, __func__);
  if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
      tty->driver->subtype == PTY_TYPE_MASTER)
    noctty = 1;
#ifdef TTY_DEBUG_HANGUP
  printk(KERN_DEBUG "%s: opening %s...\n", __func__, tty->name);
#endif
  if (tty->ops->open)
    retval = tty->ops->open(tty, filp);
  else
    retval = -ENODEV;
  filp->f_flags = saved_flags;

  if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
            !capable(CAP_SYS_ADMIN))
    retval = -EBUSY;

  if (retval) {
#ifdef TTY_DEBUG_HANGUP
    printk(KERN_DEBUG "%s: error %d in opening %s...\n", __func__,
        retval, tty->name);
#endif
    tty_unlock(tty); /* need to call tty_release without BTM */
    tty_release(inode, filp);
    if (retval != -ERESTARTSYS)
      return retval;

    if (signal_pending(current))
      return retval;

    schedule();
    /*
     * Need to reset f_op in case a hangup happened.
     */
    if (filp->f_op == &hung_up_tty_fops)
      filp->f_op = &tty_fops;
    goto retry_open;
  }
  tty_unlock(tty);


  mutex_lock(&tty_mutex);
  tty_lock(tty);
  spin_lock_irq(¤t->sighand->siglock);
  if (!noctty &&
      current->signal->leader &&
      !current->signal->tty &&
      tty->session == NULL)
    __proc_set_tty(current, tty);
  spin_unlock_irq(¤t->sighand->siglock);
  tty_unlock(tty);
  mutex_unlock(&tty_mutex);
  return 0;
err_unlock:
  mutex_unlock(&tty_mutex);
  /* after locks to avoid deadlock */
  if (!IS_ERR_OR_NULL(driver))
    tty_driver_kref_put(driver);
err_file:
  tty_free_file(filp);
  return retval;
}      

由于这里是打开/dev/ttyS0,所以绑定的线路规程是N_TTY。

3.2 write(...)

write(fd, "AT+CMUX=0\r", 10);      

对应内核tty_io.c文件:

static ssize_t tty_write(struct file *file, const char __user *buf,
            size_t count, loff_t *ppos)
{
  struct tty_struct *tty = file_tty(file);
  struct tty_ldisc *ld;
  ssize_t ret;

  if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
    return -EIO;
  if (!tty || !tty->ops->write ||
    (test_bit(TTY_IO_ERROR, &tty->flags)))
      return -EIO;
  /* Short term debug to catch buggy drivers */
  if (tty->ops->write_room == NULL)
    printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
      tty->driver->name);
  ld = tty_ldisc_ref_wait(tty); //注意这里的ld绑定的线路规程是N_TTY,还不是N_GSM0710
  if (!ld->ops->write)    
    ret = -EIO;
  else
    ret = do_tty_write(ld->ops->write, tty, file, buf, count); //调用线路规程ld->ops->write发送buf中的数据
  tty_ldisc_deref(ld);
  return ret;
}      

注意这里的ld->ops->write是对应N_TTY的驱动流程操作,这里还是个疑问,先记录下???

3.3 设置/dev/ttyS0线路规程 

ioctl(fd, TIOCSETD, &ldisc); //ldisc=N_GMS0710      
long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
  struct tty_struct *tty = file_tty(file);
  struct tty_struct *real_tty;
  void __user *p = (void __user *)arg;
  int retval;
  struct tty_ldisc *ld;

 //...      
static int tiocsetd(struct tty_struct *tty, int __user *p)
{
  int ldisc;
  int ret;

  if (get_user(ldisc, p))
    return -EFAULT;

  ret = tty_set_ldisc(tty, ldisc); //设置新的线路规程,即N_GSM0710

  return ret;
}      
int tty_set_ldisc(struct tty_struct *tty, int ldisc)
{
  int retval;
  struct tty_ldisc *o_ldisc, *new_ldisc;
  struct tty_struct *o_tty;

  new_ldisc = tty_ldisc_get(ldisc); //获取线路规程
  if (IS_ERR(new_ldisc))
    return PTR_ERR(new_ldisc);

  tty_lock(tty);
  /*
   * We need to look at the tty locking here for pty/tty pairs
   * when both sides try to change in parallel.
   */

  o_tty = tty->link;  /* o_tty is the pty side or NULL */


  /*
   * Check the no-op case
   */

  if (tty->ldisc->ops->num == ldisc) { //确定线路规程索引是否相等,我们在前面分析过num=N_GSM0710的
    tty_unlock(tty);
    tty_ldisc_put(new_ldisc);
    return 0;
  }

  mutex_lock(&tty->ldisc_mutex);

  /*
   * We could be midstream of another ldisc change which has
   * dropped the lock during processing. If so we need to wait.
   */

  while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
    mutex_unlock(&tty->ldisc_mutex);
    tty_unlock(tty);
    wait_event(tty_ldisc_wait,
      test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0);
    tty_lock(tty);
    mutex_lock(&tty->ldisc_mutex);
  }

  set_bit(TTY_LDISC_CHANGING, &tty->flags);

  /*
   * No more input please, we are switching. The new ldisc
   * will update this value in the ldisc open function
   */

  tty->receive_room = 0;

  o_ldisc = tty->ldisc;

  tty_unlock(tty);
  /*
   * Make sure we don't change while someone holds a
   * reference to the line discipline. The TTY_LDISC bit
   * prevents anyone taking a reference once it is clear.
   * We need the lock to avoid racing reference takers.
   *
   * We must clear the TTY_LDISC bit here to avoid a livelock
   * with a userspace app continually trying to use the tty in
   * parallel to the change and re-referencing the tty.
   */

  retval = tty_ldisc_halt(tty, o_tty, 5 * HZ);

  /*
   * Wait for hangup to complete, if pending.
   * We must drop the mutex here in case a hangup is also in process.
   */

  mutex_unlock(&tty->ldisc_mutex);

  flush_work(&tty->hangup_work);

  tty_lock(tty);
  mutex_lock(&tty->ldisc_mutex);

  /* handle wait idle failure locked */
  if (retval) {
    tty_ldisc_put(new_ldisc);
    goto enable;
  }

  if (test_bit(TTY_HUPPING, &tty->flags)) {
    /* We were raced by the hangup method. It will have stomped
       the ldisc data and closed the ldisc down */
    clear_bit(TTY_LDISC_CHANGING, &tty->flags);
    mutex_unlock(&tty->ldisc_mutex);
    tty_ldisc_put(new_ldisc);
    tty_unlock(tty);
    return -EIO;
  }

  /* Shutdown the current discipline. */
  tty_ldisc_close(tty, o_ldisc); //关闭老的线路规程接口,即关闭原来打开的/dev/ttyS0串口,所以我们在应用程序里发送完AT+MUX后未看到关闭串口,就是这里操作的,注意!!!

  /* Now set up the new line discipline. */
  tty->ldisc = new_ldisc;
  tty_set_termios_ldisc(tty, ldisc); //绑定新的线路规程

  retval = tty_ldisc_open(tty, new_ldisc); //打开线路规程,注意这里会采用高级MUX模式发送SAM/PF,试探模块是否会响应!!  我的M590E没成功,是否是这里导致的????
  if (retval < 0) {
    /* Back to the old one or N_TTY if we can't */
    tty_ldisc_put(new_ldisc);
    tty_ldisc_restore(tty, o_ldisc);
  }

  /* At this point we hold a reference to the new ldisc and a
     a reference to the old ldisc. If we ended up flipping back
     to the existing ldisc we have two references to it */

  if (tty->ldisc->ops->num != o_ldisc->ops->num && tty->ops->set_ldisc)
    tty->ops->set_ldisc(tty);

  tty_ldisc_put(o_ldisc);

enable:
  /*
   * Allow ldisc referencing to occur again
   */

  tty_ldisc_enable(tty);
  if (o_tty)
    tty_ldisc_enable(o_tty);

  /* Restart the work queue in case no characters kick it off. Safe if
     already running */
  schedule_work(&tty->port->buf.work);
  if (o_tty)
    schedule_work(&o_tty->port->buf.work);

  mutex_unlock(&tty->ldisc_mutex);
  tty_unlock(tty);
  return retval;
}      

3.4  设置MUX工作模式

ioctl(fd, GSMIOC_GETCONF, &c); //先获取GSM IO配置,可以通过幻数GSMIOC_GETCONFIG在n_gsm.c查看
/* we are initiator and need encoding 0 (basic) */
c.initiator = 1; //设置MUX为高级模式
c.encapsulation = 0; //不绑定,具体查看n_gsm.c内部的操作
/* our modem defaults to a maximum size of 127 bytes */
c.mru = 127;
c.mtu = 127;
/* set the new configuration */
ioctl(fd, GSMIOC_SETCONF, &c);  //设置GSM IO配置      

3.4 创建多路复用设备

4- create the devices corresponding to the "virtual" serial ports (take care, each modem has its configuration and some DLC have dedicated functions, for example GPS), starting with minor 1 (DLC0 is reserved for the management of the mux)
MAJOR=`cat /proc/devices |grep gsmtty | awk '{print $1}` for i in `seq 1 4`; do mknod /dev/ttygsm$i c $MAJOR $i done      

按照这里的脚本操作,在文件系统目录/dev/下将分别创建/dev/ttygsm1, /dev/ttygsm2, /dev/ttygsm3, /dev/ttygsm4

3.5 操作gsm多路复用串口

这里将/dev/ttygsm1指定为pppd串口拨号用,/dev/ttygsm2绑定为AT指令通讯用。/dev/ttygsm2为短信通讯:

打开/dev/ttygsm1多路复用串口:

int fd_pppd = open("/dev/ttygsm1", xx);      
int fd_at = open("/dev/ttygsm2", xx);      
int fd_sms = open("/dev/ttygsm3", xxx);      

打开多路复用串口的驱动内部执行的流程与上面打开/dev/ttys0的操作一样,只是这里绑定的线路规程为N_GSM0710,而/dev/ttyS0为N_TTY规程。接下来我们就可以分时复用的执行pppd联网、at指令、短信功能的收发了。

这里还有一个没有解决的问题,就是在应用层执行write(...)操作时,线路规程具体的执行流程???

tty_write(...)-->gsmld_write(...)-->gsmtty_write(...)-->...-->如何到底层驱动的???