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(...):
 		for (i = 0; i < driver->num; i++) {
			d = tty_register_device(driver, i, NULL);
			if (IS_ERR(d)) {
				error = PTR_ERR(d);
				goto err_unreg_devs;
			}
		}
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;

 //...
	case TIOCSETD:
		return tiocsetd(tty, p);
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(...)-->...-->如何到底层驱动的???

见改篇博客:点击打开链接的“6. 发送AT”小节。
























评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值