2018-12-28 18:46:55 u010659887 阅读数 2967

前言

bluez编译完后会生成很多命令行工具,比如gatttool、hcitool、bluetoothctl等,bluetoothctl的生成需要在configure的时候把--disable-test去掉。这些工具可以用来在linux环境下与ble设备进行调试,但是本人需要的是可用的c语言api,如果你只是开发经典蓝牙,那么恭喜你,交叉编译完后的的api足够用了;但是低功耗蓝牙用的C接口是没有的,下面就是我的趟坑过程,希望对大家有所帮助。

虽然bluez并没有给c提供直接可用的ble接口,但是通过分析源码我们可以找到更下层实现方式。

思路:

1.先熟悉各个工具,用工具连接ble设备实现服务、关键字的读写,经测试可正常使用的工具是hcitool(ble扫描),gatttool、bluetoothctl、btgatt-client;

2.分析以上工具的源码,提取可用的api,将bluez源码重新编译成为我所用的api

这件事已经有老外实现了,但是我在交叉编译过程中遇到很多问题未解决,所以并未再继续,有兴趣的可以看看:

https://github.com/labapart/gattlib

扫描

扫描参考hcitool工具源码:/tool/hcitool.c

扫描的api再hci_lib.h中已经定义了,也包含在libbluetooth.so中,可以直接使用。

static void cmd_lescan(int dev_id, int argc, char **argv)
{
	int err, opt, dd;
	uint8_t own_type = LE_PUBLIC_ADDRESS;
	uint8_t scan_type = 0x01;
	uint8_t filter_type = 0;
	uint8_t filter_policy = 0x00;
	uint16_t interval = htobs(0x0010);
	uint16_t window = htobs(0x0010);
	uint8_t filter_dup = 0x01;

	for_each_opt(opt, lescan_options, NULL) {
		switch (opt) {
		case 's':
			own_type = LE_RANDOM_ADDRESS;
			break;
		case 'p':
			own_type = LE_RANDOM_ADDRESS;
			break;
		case 'P':
			scan_type = 0x00; /* Passive */
			break;
		case 'w':
			filter_policy = 0x01; /* Whitelist */
			break;
		case 'd':
			filter_type = optarg[0];
			if (filter_type != 'g' && filter_type != 'l') {
				fprintf(stderr, "Unknown discovery procedure\n");
				exit(1);
			}

			interval = htobs(0x0012);
			window = htobs(0x0012);
			break;
		case 'D':
			filter_dup = 0x00;
			break;
		default:
			printf("%s", lescan_help);
			return;
		}
	}
	helper_arg(0, 1, &argc, &argv, lescan_help);

	if (dev_id < 0)
		dev_id = hci_get_route(NULL);

	dd = hci_open_dev(dev_id);
	if (dd < 0) {
		perror("Could not open device");
		exit(1);
	}

	err = hci_le_set_scan_parameters(dd, scan_type, interval, window,
						own_type, filter_policy, 10000);
	if (err < 0) {
		perror("Set scan parameters failed");
		exit(1);
	}

	err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000);
	if (err < 0) {
		perror("Enable scan failed");
		exit(1);
	}

	printf("LE Scan ...\n");

	err = print_advertising_devices(dd, filter_type);
	if (err < 0) {
		perror("Could not receive advertising events");
		exit(1);
	}

	err = hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000);
	if (err < 0) {
		perror("Disable scan failed");
		exit(1);
	}

	hci_close_dev(dd);
}

hci_get_route取得当前device

hci_open_dev打开本机的蓝牙设备

hci_le_set_scan_parameters设置扫描参数

hci_le_set_scan_enable开始扫描

print_advertising_devices打印扫描结果

连接

参考btgatt-client工具源码:/tool/btgatt-client.c

连接使用的l2cap层,本质还是socket,网上给的例子我连接提示host is down,跟下面的源码比对后发现是安全参数设置的问题:

setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,sizeof(btsec));

这个参数设置完毕后就可以正常连接了,注意每次连接完毕后接的关闭,不然ble从设备端会拒绝下次连接。

static int l2cap_le_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type, int sec)
{
	int sock;
	struct sockaddr_l2 srcaddr, dstaddr;
	struct bt_security btsec;
#if 1
	char srcaddr_str[18];
	char dstaddr_str[18];
	

		ba2str(src, srcaddr_str);
		ba2str(dst, dstaddr_str);

		printf("btgatt-client: Opening L2CAP LE connection on ATT "
					"channel:\n\t src: %s\n\tdest: %s\n",
					srcaddr_str, dstaddr_str);

#endif
	sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
	if (sock < 0) {
		perror("Failed to create L2CAP socket");
		return -1;
	}

	/* Set up source address */
	memset(&srcaddr, 0, sizeof(srcaddr));
	srcaddr.l2_family = AF_BLUETOOTH;
	srcaddr.l2_cid = htobs(ATT_CID);
	srcaddr.l2_bdaddr_type = 0;
	bacpy(&srcaddr.l2_bdaddr, src);

	if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0) {
		perror("Failed to bind L2CAP socket");
		close(sock);
		return -1;
	}

	/* Set the security level */
	memset(&btsec, 0, sizeof(btsec));
	btsec.level = sec;
	if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,
							sizeof(btsec)) != 0) {
		printf(stderr, "Failed to set L2CAP security level\n");
		close(sock);
		return -1;
	}

	/* Set up destination address */
	memset(&dstaddr, 0, sizeof(dstaddr));
	dstaddr.l2_family = AF_BLUETOOTH;
	dstaddr.l2_cid = htobs(ATT_CID);
	dstaddr.l2_bdaddr_type = dst_type;
	bacpy(&dstaddr.l2_bdaddr, dst);

	printf("Connecting to device...");
	fflush(stdout);
	//printf("-%02x-%02x-%02x-%02x-%02x-%02x-\n",dstaddr.l2_bdaddr.b[0],dstaddr.l2_bdaddr.b[1],dstaddr.l2_bdaddr.b[2],dstaddr.l2_bdaddr.b[3],dstaddr.l2_bdaddr.b[4],dstaddr.l2_bdaddr.b[5]);
	if (connect(sock, (struct sockaddr *) &dstaddr, sizeof(dstaddr)) < 0) {
		perror(" Failed to connect");
		close(sock);
		return -1;
	}

	printf(" Done\n");

	return sock;
}

服务、特征值读写

参考btgatt-client工具源码:/tool/btgatt-client.c

源码先注册一个loop循环,然后注册一系列回调,通过回调接收发现的服务和特征值,写在源码中也有,这里就不赘述了。

static struct client *client_create(int fd, uint16_t mtu)
{
	struct client *cli;

	cli = new0(struct client, 1);
	if (!cli) {
		printf("Failed to allocate memory for client\n");
		return NULL;
	}


	cli->att = bt_att_new(fd, false);
	if (!cli->att) {
		printf(stderr, "Failed to initialze ATT transport layer\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	if (!bt_att_set_close_on_unref(cli->att, true)) {
		printf("Failed to set up ATT transport layer\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	if (!bt_att_register_disconnect(cli->att, att_disconnect_cb, NULL,
								NULL)) {
		printf(stderr, "Failed to set ATT disconnect handler\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	cli->fd = fd;
	cli->db = gatt_db_new();
	if (!cli->db) {
		printf("Failed to create GATT database\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu);
	if (!cli->gatt) {
		printf("Failed to create GATT client\n");
		gatt_db_unref(cli->db);
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	gatt_db_register(cli->db, service_added_cb, service_removed_cb,NULL, NULL);

	bt_att_set_debug(cli->att, att_debug_cb, "att: ", NULL);
		bt_gatt_client_set_debug(cli->gatt, gatt_debug_cb, "gatt: ",
									NULL);
	bt_gatt_client_ready_register(cli->gatt, ready_cb, cli, NULL);
	bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli,NULL);

	/* bt_gatt_client already holds a reference */
	gatt_db_unref(cli->db);

	return cli;
}

实现的方法有了,接下来就是如何把这些api(btgatt-client.c中用到api)为我所用了,下面是我目录结构:

文件目录介绍
btio--------头文件
include-----bluez交叉编译时生成的头文件
lib---------静态依赖库,可自行修改Makefile编译
libb--------bluez交叉编译时生成的lib库
monitor-----头文件
profiles----头文件
src---------头文件
src/shared--静态依赖库,可自行修改Makefile编译
conn.c------测试demon

lib、src/shared中的代码编译成库文件,配合交叉编译bluez生成的so库一起使用即可直接调用上面提到的api了。

ps:以上基于bluez5.50源码修改,上面用到的库根据平台不同需要自行重新编译哦

 

我已经编译上传https://download.csdn.net/download/u010659887/10884710

 

2013-07-19 11:51:20 lushengchu2003 阅读数 4297

       最近闲来无事情做,想到以前项目中遇到串口硬件流控制的问题,蓝牙串口控制返回错误,上层读写串口buffer溢出的问题等,也折腾了一阵子,虽然最终证明与串口驱动无关,但是排查问题时候毫无疑问会查看串口驱动的相关代码,所以把串口驱动的流程过了一遍,方便以后再用到时拿来用。分析的是全志代码A20。直接开始代码分析吧。

串口驱动代码在linux-3.3/drivers/tty/serial目录下,全志把自己平台相关的代码集中到了一个文件中,叫sw_uart.c,那就从它的__init开始了:

static int __init sw_uart_init(void)
{
    int ret; 
    u32 i;
    struct sw_uart_pdata *pdata;

    SERIAL_MSG("driver initializied\n");
    ret = sw_uart_get_devinfo();
    if (unlikely(ret))
        return ret; 

    ret = uart_register_driver(&sw_uart_driver);
    if (unlikely(ret)) {
        SERIAL_MSG("driver initializied\n");
        return ret; 
    }    

    for (i=0; i<SW_UART_NR; i++) {
        pdata = &sw_uport_pdata[i];
        if (pdata->used)
            platform_device_register(&sw_uport_device[i]);
    }    

    return platform_driver_register(&sw_uport_platform_driver);
}
sw_uart_get_devinfo是解析全志的sys配置脚本中的串口配置,一共有八个串口,要用到那个直接在sys脚本中设置1就行了,这个用过全志平台的都知道

接着sw_uart_driver结构体定义如下:

static struct uart_driver sw_uart_driver = {                                                                                                                   
    .owner = THIS_MODULE,
    .driver_name = "sw_serial",
    .dev_name = "ttyS",
    .nr = SW_UART_NR,
    .cons = SW_CONSOLE,
};
这里SW_UART_NR为8,ttyS就是将要显示在/dev/目录下的名字了,从0~7

接着看uart注册函数,顾名思义是把全志自己平台的串口注册到串口核心serial_core中去:

int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal;
    int i, retval;

    BUG_ON(drv->state);

    /*
     * Maybe we should be using a slab cache for this, especially if
     * we have a large number of ports to handle.
     */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    if (!drv->state)
        goto out;

    normal = alloc_tty_driver(drv->nr);
    if (!normal)
        goto out_kfree;

    drv->tty_driver = normal;

    normal->owner       = drv->owner;
    normal->driver_name = drv->driver_name;
    normal->name        = drv->dev_name;//名字为ttyS
    normal->major       = drv->major;
    normal->minor_start = drv->minor;
    normal->type        = TTY_DRIVER_TYPE_SERIAL;
    normal->subtype     = SERIAL_TYPE_NORMAL;
    normal->init_termios    = tty_std_termios;
    normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;                                                                                       
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
    normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;
    tty_set_operations(normal, &uart_ops);
    /*
     * Initialise the UART state(s).
     */
    for (i = 0; i < drv->nr; i++) {
        struct uart_state *state = drv->state + i;
        struct tty_port *port = &state->port;

        tty_port_init(port);
        port->ops = &uart_port_ops;
        port->close_delay     = HZ / 2; /* .5 seconds */
        port->closing_wait    = 30 * HZ;/* 30 seconds */
    }

    retval = tty_register_driver(normal);
    if (retval >= 0)
        return retval;

    put_tty_driver(normal);
out_kfree:
    kfree(drv->state);
out:
    return -ENOMEM;
}

先列出来吧,以用到时候再回来看,这里先创建了NR个state, 并为每个state做一些初始化,但是这些state还没有和端口(uart_port)对应起来;初始化完port口后,调用tty_register_driver:

int tty_register_driver(struct tty_driver *driver)
{
    int error;
    int i;
    dev_t dev;
    void **p = NULL;
    struct device *d;

    if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
        p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
        if (!p)
            return -ENOMEM;
    }

    if (!driver->major) {
        error = alloc_chrdev_region(&dev, driver->minor_start,
                        driver->num, driver->name);
        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) {
        kfree(p);                                                                                                                                              
        return error;
    }

    if (p) {
        driver->ttys = (struct tty_struct **)p;
        driver->termios = (struct ktermios **)(p + driver->num);
    } else {
        driver->ttys = NULL;
        driver->termios = NULL;
    }

    cdev_init(&driver->cdev, &tty_fops);
    driver->cdev.owner = driver->owner;
    error = cdev_add(&driver->cdev, dev, driver->num);
    if (error) {
        unregister_chrdev_region(dev, driver->num);
        driver->ttys = NULL;
        driver->termios = NULL;
        kfree(p);
        return error;
    }

    mutex_lock(&tty_mutex);
    list_add(&driver->tty_drivers, &tty_drivers);
    mutex_unlock(&tty_mutex);

    if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
        for (i = 0; i < driver->num; i++) {
            d = tty_register_device(driver, i, NULL);
            if (IS_ERR(d)) {
                error = PTR_ERR(d);
                goto err;
            }
        }
    }
    proc_tty_register_driver(driver);
    driver->flags |= TTY_DRIVER_INSTALLED;
    return 0;

这里alloc_chrdev_region是动态分配了主从设备号,接着cdev_init,它file_operations结构提和它关联了起来,以后我们open /dev/ttyS节点时候会调用他的open函数,先看看这个结构体:

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,
};

接着cdev_add就把file_operations和设备号关联起来了,我们现在还没有创建设备节点,不过看到有为driver->major,driver->minor_start赋值的,后面创建节点就是用这两个主从设备号。接着list_add把这个tty_driver添加到链表中来,方便后续查找。


接着if语句判断TTY_DRIVER_DYNAMIC_DEV标志,我们前面有赋值:

     normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;

所以这里的if条件不成立的,最后创建proc下的节点,就返回了。按我的理解,tty_register_driver是注册了一个tty的驱动,这个驱动有了逻辑能力,但是这个时候这个驱动还没有对应任何设备,所以后续还要添加对应的端口(也就是芯片的物理串口),并创建/dev/下的设备节点,上层用tty_driver驱动的逻辑来操作对应的端口。


回到sw_uart.c中,继续__init函数,platform_device_register函数,如果sys配置文件中那个串口配置了1,才会注册相应的平台设备;

接着platform_driver_register,看它的probe函数了:

static int __devinit sw_uart_probe(struct platform_device *pdev)
{
    u32 id = pdev->id;
    struct uart_port *port;
    struct sw_uart_port *sw_uport;
    struct clk *apbclk;
    int ret = -1;

    if (unlikely(pdev->id < 0 || pdev->id >= SW_UART_NR))
        return -ENXIO;
    port = &sw_uart_port[id].port;
    port->dev = &pdev->dev;
    sw_uport = UART_TO_SPORT(port);
    sw_uport->id = id;
    sw_uport->ier = 0;
    sw_uport->lcr = 0;
    sw_uport->mcr = 0;
    sw_uport->fcr = 0;
    sw_uport->dll = 0;
    sw_uport->dlh = 0;

    /* request system resource and init them */
    ret = sw_uart_request_resource(sw_uport);
    if (unlikely(ret)) {                                                                                                                                       
        SERIAL_MSG("uart%d error to get resource\n", id);
        return -ENXIO;
    }

    apbclk = clk_get(&pdev->dev, CLK_SYS_APB1);
    if (IS_ERR(apbclk)) {
        SERIAL_MSG("uart%d error to get source clock\n", id);
        return -ENXIO;
    }
    ret = clk_set_parent(sw_uport->mclk, apbclk);
    if (ret) {
        SERIAL_MSG("uart%d set mclk parent error\n", id);
        clk_put(apbclk);
        return -ENXIO;
    }
    port->uartclk = clk_get_rate(apbclk);
    clk_put(apbclk);

    port->type = PORT_SW;
    port->flags = UPF_BOOT_AUTOCONF;
    port->mapbase = sw_uport->pdata->base;
    port->irq = sw_uport->pdata->irq;
    platform_set_drvdata(pdev, port);
#ifdef CONFIG_PROC_FS
    sw_uart_procfs_attach(sw_uport);
#endif
    SERIAL_DBG("add uart%d port, port_type %d, uartclk %d\n",
            id, port->type, port->uartclk);
    return uart_add_one_port(&sw_uart_driver, port);
}
sw_uart_request_resource是申请配置GPIO,接着uart_add_one_port:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
    struct uart_state *state;
    struct tty_port *port;
    int ret = 0; 
    struct device *tty_dev;

    BUG_ON(in_interrupt());

    if (uport->line >= drv->nr)
        return -EINVAL;

    state = drv->state + uport->line;//state是在函数 uart_register_driver中kmalloc初始化
    port = &state->port;

    mutex_lock(&port_mutex);
    mutex_lock(&port->mutex);
    if (state->uart_port) {
        ret = -EINVAL;
        goto out; 
    }    

    state->uart_port = uport;
    state->pm_state = -1;
                                                                                                                                                               
    uport->cons = drv->cons;
    uport->state = state;

    /*   
     * If this port is a console, then the spinlock is already
     * initialised.
     */
    if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
        spin_lock_init(&uport->lock);
        lockdep_set_class(&uport->lock, &port_lock_key);
    }    

    uart_configure_port(drv, state, uport);

    /*   
     * Register the port whether it's detected or not.  This allows
     * setserial to be used to alter this ports parameters.
     */
    tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
    if (likely(!IS_ERR(tty_dev))) {
        device_set_wakeup_capable(tty_dev, 1);
    } else {
        printk(KERN_ERR "Cannot register tty device on line %d\n",
               uport->line);
    }

    /*
     * Ensure UPF_DEAD is not set.
     */
    uport->flags &= ~UPF_DEAD;

 out:
    mutex_unlock(&port->mutex);
    mutex_unlock(&port_mutex);

    return ret;
}

这个函数看名字就猜到是为uart_driver添加端口,前面说过state的状态还没有和uart_port对应起来,那么这里state->uart_port = uport就对应了,port的配置就不去关心它了,这样,uart_driver就可以通过port对ops结构体的操作函数控制底层了,最后调用tty_register_device:

struct device *tty_register_device(struct tty_driver *driver, unsigned index,
                   struct device *device)
{   
    char name[64];
    dev_t dev = MKDEV(driver->major, driver->minor_start) + index;

    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)
        pty_line_name(driver, index, name);
    else
        tty_line_name(driver, index, name);

    return device_create(tty_class, device, dev, NULL, name);
}

这里的tty_line_name函数定义如下:

static void tty_line_name(struct tty_driver *driver, int index, char *p)                                                                                       
{
    sprintf(p, "%s%d", driver->name, index + driver->name_base);//名字在uart_register_driver中赋值
}
可以看到就是前面说的名字ttyS0~ttyS7。

这样,如果上层open节点,会调用到前面的file_operations结构体函数tty_open,这是tty核心层的调用:

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;
    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);
    tty_lock();

    tty = tty_open_current_tty(device, filp);

     ...................................

    if (tty->ops->open)
        retval = tty->ops->open(tty, filp);
    else

    ........................................

            return retval;

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

可以看到,首先查找tty_driver链表,找到前面添加的tty_driver,然后调用他的ops->open函数,这个ops赋值是在前面的uart_register_driver函数中:

tty_set_operations(normal, &uart_ops);

所以进入uart_ops结构体的open函数,这里就是从tty核心转到serial核心,往下走了一层:

static int uart_open(struct tty_struct *tty, struct file *filp)
{
    struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
    int retval, line = tty->index;
    struct uart_state *state = drv->state + line;
    struct tty_port *port = &state->port;
     
      ................................

    /*
     * Start up the serial port.
     */
    retval = uart_startup(tty, state, 0);

    .....................................
 

uart_startup:

static int uart_startup(struct tty_struct *tty, struct uart_state *state,                                                                                      
        int init_hw)
{
    struct tty_port *port = &state->port;
    int retval;

    if (port->flags & ASYNC_INITIALIZED)
        return 0;

    /*
     * Set the TTY IO error marker - we will only clear this
     * once we have successfully opened the port.
     */
    set_bit(TTY_IO_ERROR, &tty->flags);

    retval = uart_port_startup(tty, state, init_hw);
    if (!retval) {
        set_bit(ASYNCB_INITIALIZED, &port->flags);
        clear_bit(TTY_IO_ERROR, &tty->flags);
    } else if (retval > 0)
        retval = 0;

    return retval;
}

uart_port_startup:

static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,                                                                                 
        int init_hw)
{
    struct uart_port *uport = state->uart_port;
    struct tty_port *port = &state->port;
    unsigned long page;
    int retval = 0;
    retval = uport->ops->startup(uport);
    if (retval == 0) {
        if (uart_console(uport) && uport->cons->cflag) {
            tty->termios->c_cflag = uport->cons->cflag;
            uport->cons->cflag = 0;
        }
        /*
         * Initialise the hardware port settings.
         */
        uart_change_speed(tty, state, NULL);

        if (init_hw) {
            /*
             * Setup the RTS and DTR signals once the
             * port is open and ready to respond.
             */
            if (tty->termios->c_cflag & CBAUD)
                uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);
        }

        if (port->flags & ASYNC_CTS_FLOW) {
            spin_lock_irq(&uport->lock);
            if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS))
                tty->hw_stopped = 1;
            spin_unlock_irq(&uport->lock);
        }
    }

 
可以看到,最终调用了 uport->ops->startup,这样就从serical核心层往下走到了平台的串口驱动层,也就是最底层的驱动了,这个函数定义在sw_uart.c的sw_uart_port结构体中:

static struct sw_uart_port sw_uart_port[] = {                                                                                                                  
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 0, },
      .pdata = &sw_uport_pdata[0], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 1, },
      .pdata = &sw_uport_pdata[1], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 2, },
      .pdata = &sw_uport_pdata[2], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 3, },
      .pdata = &sw_uport_pdata[3], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 4, },
      .pdata = &sw_uport_pdata[4], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 5, },
      .pdata = &sw_uport_pdata[5], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 6, },
      .pdata = &sw_uport_pdata[6], },
    { .port = { .iotype = UPIO_MEM, .ops = &sw_uart_ops, .fifosize = 64, .line = 7, },
      .pdata = &sw_uport_pdata[7], },
};

看他的.startup函数:

static int sw_uart_startup(struct uart_port *port)                                                                                                             
{
    struct sw_uart_port *sw_uport = UART_TO_SPORT(port);
    int ret;

    SERIAL_DBG("start up ...\n");
    snprintf(sw_uport->name, sizeof(sw_uport->name),
         "sw_serial%d", port->line);
    ret = request_irq(port->irq, sw_uart_irq, 0, sw_uport->name, port);
    if (unlikely(ret)) {
        SERIAL_MSG("uart%d cannot get irq %d\n", sw_uport->id, port->irq);
        return ret;
    }

    sw_uport->msr_saved_flags = 0;
    /*
     * PTIME mode to select the THRE trigger condition:
     * if PTIME=1(IER[7]), the THRE interrupt will be generated when the
     * the water level of the TX FIFO is lower than the threshold of the
     * TX FIFO. and if PTIME=0, the THRE interrupt will be generated when
     * the TX FIFO is empty.
     * In addition, when PTIME=1, the THRE bit of the LSR register will not
     * be set when the THRE interrupt is generated. You must check the
     * interrupt id of the IIR register to decide whether some data need to
     * send.
     */
    sw_uport->ier = SW_UART_IER_RLSI | SW_UART_IER_RDI;
    #ifdef CONFIG_SW_UART_PTIME_MODE
    sw_uport->ier |= SW_UART_IER_PTIME;
    #endif

    return 0;
}

这里最终就是初始化ARM芯片的寄存器操作了,可以看到申请了中断函数,后续的读写操作就和中断服务函数密切相关了。

接着open之后,看看上层的write函数调用过程,首先调用了tty核心层的write:

static ssize_t tty_write(struct file *file, const char __user *buf,                                                                                            
                        size_t count, loff_t *ppos)
{
    struct inode *inode = file->f_path.dentry->d_inode;
    struct tty_struct *tty = file_tty(file);
    struct tty_ldisc *ld;
    ssize_t ret;

    if (tty_paranoia_check(tty, inode, "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);
    if (!ld->ops->write)
        ret = -EIO;
    else
        ret = do_tty_write(ld->ops->write, tty, file, buf, count);
    tty_ldisc_deref(ld);
    return ret;
}

看这里do_tty_write函数:

static inline ssize_t do_tty_write(
    ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
    struct tty_struct *tty,
    struct file *file,
    const char __user *buf,
    size_t count)
{
    
    ............................

    for (;;) {
        size_t size = count;
        if (size > chunk)
            size = chunk;
        ret = -EFAULT;
        if (copy_from_user(tty->write_buf, buf, size))
            break;
        ret = write(tty, file, tty->write_buf, size);
        if (ret <= 0)
            break;

    ...............................


copy_from_user就把要写的数据拷贝到了内核空间的write_buf中来,接着write是函数指针,指向ld->ops->write。

这里的ld->ops指向的是n_tty.c 中结构体:

struct tty_ldisc_ops tty_ldisc_N_TTY = {
    .magic           = TTY_LDISC_MAGIC,
    .name            = "n_tty",
    .open            = n_tty_open,
    .close           = n_tty_close,
    .flush_buffer    = n_tty_flush_buffer,
    .chars_in_buffer = n_tty_chars_in_buffer,
    .read            = n_tty_read,
    .write           = n_tty_write,                                                                                                                            
    .ioctl           = n_tty_ioctl,
    .set_termios     = n_tty_set_termios,
    .poll            = n_tty_poll,
    .receive_buf     = n_tty_receive_buf,
    .write_wakeup    = n_tty_write_wakeup
};
所以调用了调用的是线路规程的write:

static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
               const unsigned char *buf, size_t nr)
{ 
    ..........................
                b++; nr--;
            }
            if (tty->ops->flush_chars)
                tty->ops->flush_chars(tty);
        } else {
            while (nr > 0) {
                c = tty->ops->write(tty, b, nr);
                if (c < 0) {
                    retval = c;
                    goto break_out;
                }
                if (!c)
                    break;
                b += c;
                nr -= c;
            }
        }
    ............................

从线路规程转到tty驱动层的write:

static int uart_write(struct tty_struct *tty,
                    const unsigned char *buf, int count)
{

    ......................

    if (!circ->buf)
        return 0;

    spin_lock_irqsave(&port->lock, flags);
    while (1) {
        c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
        if (count < c) 
            c = count;
        if (c <= 0)
            break;
        memcpy(circ->buf + circ->head, buf, c);
        circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
        buf += c;
        count -= c;
        ret += c;
    }
    spin_unlock_irqrestore(&port->lock, flags);

    uart_start(tty);
    return ret;
}

可以看到,把数据memcpy到环形队列中来,这样数据就保存到了该端口对应的state的xmit的buf中,这是一个环形队列。接着调用uart_start:

static void uart_start(struct tty_struct *tty)
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port = state->uart_port;
    unsigned long flags;

    spin_lock_irqsave(&port->lock, flags);
    __uart_start(tty);
    spin_unlock_irqrestore(&port->lock, flags);
}
看来要开始传输数据了,所以对这个操作加锁了:

static void __uart_start(struct tty_struct *tty)                                                                                                               
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port = state->uart_port;

    if (port->ops->wake_peer)
        port->ops->wake_peer(port);

    if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
        !tty->stopped && !tty->hw_stopped)
        port->ops->start_tx(port);
}

最终调用驱动层的操作函数,也就是该端口对应的传输函数来触发数据发送:

static void sw_uart_start_tx(struct uart_port *port)                                                                                                           
{
    struct sw_uart_port *sw_uport = UART_TO_SPORT(port);

    if (!(sw_uport->ier & SW_UART_IER_THRI)) {
        sw_uport->ier |= SW_UART_IER_THRI;
        SERIAL_DBG("start tx, ier %x\n", sw_uport->ier);
        serial_out(port, sw_uport->ier, SW_UART_IER);
    }    
}

serial_out函数对应的就是最底层的寄存器操作了,这里#define SW_UART_IER (0x04),SW_UART_IER_THRI它对应的是使能中断,具体要参考全志的A20 CPU手册:

static inline void serial_out(struct uart_port *port, unsigned char value, int offs)                                                                           
{
    __raw_writeb(value, port->membase + offs);
}
把配置写到了寄存器中,使能中断后,中断服务函数就自动把buf的数据发送出去了,前面分析看到数据是memcpy到了该端口对应的state的circ_buf结构体中的,所以进入前面open时候分析的中断服务函数中去:

static irqreturn_t sw_uart_irq(int irq, void *dev_id)
{
    struct uart_port *port = dev_id;
    struct sw_uart_port *sw_uport = UART_TO_SPORT(port);
    unsigned int iir = 0, lsr = 0;
    unsigned long flags;

    spin_lock_irqsave(&port->lock, flags);

    iir = serial_in(port, SW_UART_IIR) & SW_UART_IIR_IID_MASK;                                                                                                 
    lsr = serial_in(port, SW_UART_LSR);
    SERIAL_DBG("irq: iir %x lsr %x\n", iir, lsr);

    if (iir == SW_UART_IIR_IID_BUSBSY) {
        /* handle busy */
//      SERIAL_MSG("uart%d busy...\n", sw_uport->id);
        serial_in(port, SW_UART_USR);

        #ifdef CONFIG_SW_UART_FORCE_LCR
        sw_uart_force_lcr(sw_uport, 10);
        #else
        serial_out(port, sw_uport->lcr, SW_UART_LCR);
        #endif
    } else {
        if (lsr & (SW_UART_LSR_DR | SW_UART_LSR_BI))
            lsr = sw_uart_handle_rx(sw_uport, lsr);
        sw_uart_modem_status(sw_uport);
        #ifdef CONFIG_SW_UART_PTIME_MODE
        if (iir == SW_UART_IIR_IID_THREMP)
        #else
        if (lsr & SW_UART_LSR_THRE)
        #endif
            sw_uart_handle_tx(sw_uport);
    }

    spin_unlock_irqrestore(&port->lock, flags);

    return IRQ_HANDLED;
}

我们要做的是发送操作,所以进入sw_uart_handle_tx:

static void sw_uart_handle_tx(struct sw_uart_port *sw_uport)
{
    struct circ_buf *xmit = &sw_uport->port.state->xmit;
    int count;

    if (sw_uport->port.x_char) {                                                                                                                               
        serial_out(&sw_uport->port, sw_uport->port.x_char, SW_UART_THR);
        sw_uport->port.icount.tx++;
        sw_uport->port.x_char = 0;
#ifdef CONFIG_SW_UART_DUMP_DATA
        sw_uport->dump_buff[sw_uport->dump_len++] = sw_uport->port.x_char;
        SERIAL_DUMP(sw_uport, "Tx");
#endif
        return;
    }
    if (uart_circ_empty(xmit) || uart_tx_stopped(&sw_uport->port)) {
        sw_uart_stop_tx(&sw_uport->port);
        return;
    }
    count = sw_uport->port.fifosize / 2;
    do {
#ifdef CONFIG_SW_UART_DUMP_DATA
        sw_uport->dump_buff[sw_uport->dump_len++] = xmit->buf[xmit->tail];
#endif
        serial_out(&sw_uport->port, xmit->buf[xmit->tail], SW_UART_THR);
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        sw_uport->port.icount.tx++;
        if (uart_circ_empty(xmit)) {
            break;
        }
    } while (--count > 0);

    SERIAL_DUMP(sw_uport, "Tx");
    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {
        spin_unlock(&sw_uport->port.lock);
        uart_write_wakeup(&sw_uport->port);
        spin_lock(&sw_uport->port.lock);
    }
    ..........................
 
看到了,serial_out果然是取出circ_buf的buf数据,在do{}while语句中完成发送:

static inline void serial_out(struct uart_port *port, unsigned char value, int offs)                                                                           
{
    __raw_writeb(value, port->membase + offs);
}
这样就把发送的数据写到了相应的寄存器中,硬件会自动完成数据发送操作


而上层read操作时候调用和write差不多,不同之处在于read是读取一个环形buf的数据,因为数据到了会产生中断,是中断服务函数自动接收数据并把它存储在buf中的;而前面写的时候是主动把数据写到buf去,所以先从中断服务函数中看是如何接收输入的:

static unsigned int sw_uart_handle_rx(struct sw_uart_port *sw_uport, unsigned int lsr)
{
    struct tty_struct *tty = sw_uport->port.state->port.tty;
    unsigned char ch = 0;
    int max_count = 256;
    char flag;

    do {
        if (likely(lsr & SW_UART_LSR_DR)) {
            ch = serial_in(&sw_uport->port, SW_UART_RBR);

       ..........................

        if (uart_handle_sysrq_char(&sw_uport->port, ch))
            goto ignore_char;
        uart_insert_char(&sw_uport->port, lsr, SW_UART_LSR_OE, ch, flag);

       ............................

 

从寄存器读取字符后赋值给ch, 接着uart_insert_char来处理该字符,其实就是把这个数据放到uart层去:

void uart_insert_char(struct uart_port *port, unsigned int status,
         unsigned int overrun, unsigned int ch, unsigned int flag)
{
    struct tty_struct *tty = port->state->port.tty;

    if ((status & port->ignore_status_mask & ~overrun) == 0)
        tty_insert_flip_char(tty, ch, flag);

    /*
     * Overrun is special.  Since it's reported immediately,
     * it doesn't affect the current character.
     */
    if (status & ~port->ignore_status_mask & overrun)
        tty_insert_flip_char(tty, 0, TTY_OVERRUN);
}

static inline int tty_insert_flip_char(struct tty_struct *tty,                                                                                                 
                    unsigned char ch, char flag)
{
    struct tty_buffer *tb = tty->buf.tail;
    if (tb && tb->used < tb->size) {
        tb->flag_buf_ptr[tb->used] = flag;
        tb->char_buf_ptr[tb->used++] = ch;
        return 1;
    }
    return tty_insert_flip_string_flags(tty, &ch, &flag, 1);
}
当前的tty_buffer空间不够时调用tty_insert_flip_string_flags,在这个函数里会去查找下一个tty_buffer,并将数据放到下一个tty_buffer的char_buf_ptr里。
这里char_buf_ptr的数据是如何放到线路规程的read_buf中的呢?那是在tty open操作的时候,tty_init_dev -> initialize_tty_struct -> initialize_tty_struct -> tty_buffer_init:

void tty_buffer_init(struct tty_struct *tty)                                                                                                                   
{
    spin_lock_init(&tty->buf.lock);
    tty->buf.head = NULL;
    tty->buf.tail = NULL;
    tty->buf.free = NULL;
    tty->buf.memory_used = 0;
    INIT_WORK(&tty->buf.work, flush_to_ldisc);
}

可以看到初始化了工作队列的,而调用工作队列的时机是在这里操作完成后,继续sw_uart_handle_rx函数的tty_flip_buffer_push时候:

void tty_flip_buffer_push(struct tty_struct *tty)
{
    unsigned long flags;
    spin_lock_irqsave(&tty->buf.lock, flags);
    if (tty->buf.tail != NULL)
        tty->buf.tail->commit = tty->buf.tail->used;
    spin_unlock_irqrestore(&tty->buf.lock, flags);

    if (tty->low_latency)
        flush_to_ldisc(&tty->buf.work);
    else
        schedule_work(&tty->buf.work);
}
EXPORT_SYMBOL(tty_flip_buffer_push); 

这里就有两种方法把数据上报给链路规程层,其实都差不多,这样数据就上报到了链路规程中,看看这个工作队列函数:

static void flush_to_ldisc(struct work_struct *work)
{
    ..........................

                count = tty->receive_room;
            char_buf = head->char_buf_ptr + head->read;
            flag_buf = head->flag_buf_ptr + head->read;
            head->read += count;
            spin_unlock_irqrestore(&tty->buf.lock, flags);
            disc->ops->receive_buf(tty, char_buf,
                            flag_buf, count);

    ............................

链路规程的receive_buf函数:

static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,                                                                                 
                  char *fp, int count)
{
    const unsigned char *p;
    char *f, flags = TTY_NORMAL;
    int i;
    char    buf[64];
    unsigned long cpuflags;

    if (!tty->read_buf)
        return;

    ................................

        memcpy(tty->read_buf + tty->read_head, cp, i);
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;


    ...........................

        }
        if (tty->ops->flush_chars)
            tty->ops->flush_chars(tty);
    }

    n_tty_set_room(tty);



可以看到,很明显,memcpy将数据拷贝到了read_buf中。


现在,再回头从上层看read函数是如何读取数据的,流程也是tty核心 ->链路规程 :

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
             unsigned char __user *buf, size_t nr)
{
    unsigned char __user *b = buf; 

    .......................................

                c = tty->read_buf[tty->read_tail];

    ...............................

            uncopied = copy_from_read_buf(tty, &b, &nr);
            uncopied += copy_from_read_buf(tty, &b, &nr);

    ..............................
 

static int copy_from_read_buf(struct tty_struct *tty,
                      unsigned char __user **b,
                      size_t *nr)

{
    int retval;
    size_t n;
    unsigned long flags;

    retval = 0;
    spin_lock_irqsave(&tty->read_lock, flags);
    n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail);
    n = min(*nr, n);
    spin_unlock_irqrestore(&tty->read_lock, flags);
    if (n) {
        retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);                                                                                          
        n -= retval;
        tty_audit_add_data(tty, &tty->read_buf[tty->read_tail], n);
        spin_lock_irqsave(&tty->read_lock, flags);
        tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt -= n;
        /* Turn single EOF into zero-length read */
        if (L_EXTPROC(tty) && tty->icanon && n == 1) {
            if (!tty->read_cnt && (*b)[n-1] == EOF_CHAR(tty))
                n--;
        }
        spin_unlock_irqrestore(&tty->read_lock, flags);
        *b += n;
        *nr -= n;
    }
    return retval;
} 

看到了copy_to_user函数,就是把read_buf的数据拷贝到了用户空间。

到这里,串口的读写流程就很清楚,一目了然了。
   

















2017-05-15 21:27:05 housezhu 阅读数 975

一、前言
下图是树莓派3的IO映射图,可以看到树莓派3 BCM码的14和15分别对应的是Uart的Tx和Rx。但是由于树莓派3板载了蓝牙,默认的情况下,该路的Uart被蓝牙占用,从而导致了我们正常情况下再Linux系统层无法对该路Uart IO进行读写操作。从而导致我们需要对外部的Uart设备进行通信时,显得力不从心,下面就来看看怎么从系统中解禁该路Uart。
树莓派3 IO映射图

二、启用Uart设备的具体步骤
0、该文档的验证系统环境为CentOS-Userland-7;在该系统中上述的Uart设备在系统中的映射为/dev/ttyAMA0
1、为了重新启用Uart,我们需要配置一下系统根目录下boot文件夹中的两个配置文件config.txt和cmdline.txt文件。
2、首先打开config.txt文件,在尾行追加以下两行:dtoverlay=pi3-miniuart-bt dtoverlay=pi3-disable-bt;
3、再打开cmdline.txt文件,在该文件中有关/dev/ttyAMA0有关的配置去掉;例如原cmdline.txt的文件内容为:dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p3 rootfstype=ext4 elevator=deadline rootwait selinux=1 security=selinux enforcing=0修改后应为:dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p3 rootfstype=ext4 elevator=deadline rootwait selinux=1 security=selinux enforcing=0
保存以上配置,重启后就可以对串口Uart设备进行正常的读写操作了。简单的说明一下以上的操作,在config.txt里面增加的那两行就是让系统启动时关掉蓝牙,将蓝牙占用的Uart解除。由于树莓派的许多设备的驱动挂载是基于设备树的机制,系统启动时会首先到overlay文件夹下找到相关的设备树文件(dtb)以此来启动设备。关于config.txt文件对设备的更多操作可以参考本博客文章《树莓派设备config文件配置README》。

2012-02-21 16:36:24 Tommy_wxie 阅读数 9803

SDIO

       SDIO卡是在SD内存卡接口的基础上发展起来的接口,SDIO接口兼容以前的SD内存卡,并且可以连接SDIO接口的设备,目前根据SDIO协议的SPECSDIO接口支持的设备总类有蓝牙,网卡,电视卡等。

       SDIO协议是由SD卡的协议演化升级而来的,很多地方保留了SD卡的读写协议,同时SDIO协议又在SD卡协议之上添加了CMD52CMD53命令。由于这个,SDIOSD卡规范间的一个重要区别是增加了低速标准,低速卡的目标应用是以最小的硬件开始来支持低速I/O能力。低速卡支持类似调制解调器,条形码扫描仪和GPS接收器等应用。高速卡支持网卡,电视卡还有“组合”卡等,组合卡指的是存储器+SDIO

       SDIOSD卡的SPEC间的又一个重要区别是增加了低速标准。SDIO卡只需要SPI1SD传输模式。低速卡的目标应用是以最小的硬件开支来支持低速I/O能力,低速卡支持类似MODEM,条形扫描仪和GPS接收器等应用。对组合卡来说,全速和4BIT操作对卡内存储器和SDIO部分都是强制要求的。

       在非组合卡的SDIO设备里,其最高速度要只有达到25M,而组合卡的最高速度同SD卡的最高速度一样,要高于25M

 

SDIO总线

       SDIO总线和USB总线类似,SDIO总线也有两端,其中一端是主机(HOST)端,另一端是设备端(DEVICE),采用HOST- DEVICE这样的设计是为了简化DEVICE的设计,所有的通信都是由HOST端发出命令开始的。在DEVICE端只要能解溪HOST的命令,就可以同HOST进行通信了。

       SDIOHOST可以连接多个DEVICE,如下图所示:

 

 

       这个是同SD的总线一样的,其中有如下的几种信号

1.       CLK信号:HOSTDEVICE的时钟信号.

2.       CMD信号:双向的信号,用于传送命令和反应。

3.       DAT0-DAT3 信号:四条用于传送的数据线。

4.       VDD信号:电源信号。

5.       VSS1VSS2:电源地信号。

SDIO总线定义中,DAT1信号线复用为中断线。在SDIO1BIT模式下DAT0用来传输数据,DAT1用作中断线。在SDIO4BIT模式下DAT0-DAT3用来传输数据,其中DAT1复用作中断线。

 

SDIO命令:

       SDIO总线上都是HOST端发起请求,然后DEVICE端回应请求。其中请求和回应中会数据信息。

1.       Command:用于开始传输的命令,是由HOST端发往DEVICE端的。其中命令是通过CMD信号线传送的。

2.       Response:回应是DEVICE返回的HOST的命令,作为Command的回应。也是通过

CMD线传送的。

3.       Data:数据是双向的传送的。可以设置为1线模式,也可以设置为4线模式。数据是通过DAT0-DAT3信号线传输的。

  SDIO的每次操作都是由HOSTCMD线上发起一个CMD,对于有的CMDDEVICE需要返回Response,有的则不需要。

       对于读命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的数据线上,在传送数据的同时会跟随着CRC校验码。当整个读传送完毕后,HOST会再次发送一个命令,通知DEVICE操作完毕,DEVICE同时会返回一个响应。

       对于写命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的数据线上,在传送数据的同时会跟随着CRC校验码。当整个写传送完毕后,HOST会再次发送一个命令,通知DEVICE操作完毕,DEVICE同时会返回一个响应。

 

SDIO的寄存器:

      SDIO卡的设备驱动80%的任务就是操作SDIO卡上的有关寄存器。SDIO卡最多允许有7个功能(function,这个同其功能号是对应的(07,每个功能都对应一个128K字节大小的寄存器,这个见下面的图。功能号之所以取值范围是1~7,而没有包含0,是因为功能0并不代表真正的功能,而代表CIA寄存器,即Common I/O Area,这个纪录着SDIO卡的一些基本信息和特性,并且可以改写这些寄存器。其中地址0x1000~0x17fffSDIO卡的CIS区域,就是基本信息区域,Common Information Structure。初始化的时候读取并配对SDIO设备。

       这些寄存器的详细分区已经其对应的功能,在开发过程中都是需要仔细研读的,这些都在协议的SPEC中都有详细说明,这里就不在罗索了。 

 

CMD52命令:

SDIO设备为了和SD内存卡兼容,SD卡所有CommandResponse完全兼容,同时加入了一些新的CommandResponse。例如,初始化SD内存卡使用ACMD41,而SDIO卡设备则用CMD5通知DEVICE进行初始化。

但二者最重要的区别是,SDIO卡比SD内存卡多了CMD52CMD53命令,这两个命令可以方便的访问某个功能的某个地址寄存器。

CMD52命令是IO_RW_DIRECT命令的简称,其命令格式如下

首先第一位为0,表明是起始位,第二位为传输方向,这里为1,代表方向为HOSTDEVICE设备传送,其后6位为命令号,这里是110100b,用十进制表示为52CMD52的名字也由此而来。紧接着是读写标志位。

      然后是操作的功能号。也就是function number。如果为0则指示为CCCR寄存器组。

       紧接着是寄存器地址,用17指示,由于功能寄存器有128K地址,17位正好能寻址。

       再下来8Write data or Staff Bits的意思是说,如果当前为写操作,则为数据,否则8位为填充位。无意义。

       最后7位为CRC校验码。最后一位为结束位0

       对于CMD52Response48位,命令格式如下:

 

       总结下,CMD52是由HOST发往DEVICE的,它必须有DEVICE返回来的Response CMD52不需要占用DAT线,读写的数据是通过CMD52或者Response来传送。每次CMD52只能读或者写一个byte

 

CMD53命令:

CMD52每次只能读写一个字节,因为有了CMD53对读写进行了扩展,CMD53允许每次读写多个字节或者多个块(BLOCK)CMD53的命令格式如下:

       第一位是1,为开始位,然后是一位方向位,总是1,代表方向为HOSTDEVICE设备传送,其后6位为命令号,这里是110101b,用十进制表示为53CMD53的名字也由此而来。

       然后是1位的读写标志。接着是3位功能号,这个同CMD52都是相同的。Block Mode如果1代表是块传输模式,否则为字节传输模式。

       OP Code为操作位,如果是0,代表数据往固定的位置读写,如果1代表是地质增量读写。例如,对地址0固定读写16个字节,相当于16次读写的地址0,而对地址0增量读写16个字节,相当于读写0~15地址的数据。

       然后是17位的地址寄存器,可以寻址到128K字节的地址,然后是9位的读写的计数,对于字节读取,读写大小就是这个计数,而对于块读写,读写的大小是计数乘以块的大小。

       随后的7位为CRC校验码。最后一位为1

       当读写操作是块操作的时候,块的大小是可以通过设置FBR中的相关寄存器来设置。

       CMD52命令不同的是,CMD53没有返回的命令的,这里判断是否DEVICE设备读写完毕是需要驱动里面自己判断的,一般有2个方法,1.设置相应的读写完毕中断。如果DEVICE设备读写完毕,则对HOST设备发送中断。2.HOST设备主动查询DEVICE设备是否读写完毕,可以通过CMD命令是否有返回来判断是否DEVICE是否读写完毕。


驱动:

以SDIO为例其会采用mmc_attach_sdio来实现驱动和设备的匹配,其本质还是根据sdio_bus的匹配规则来实现匹配。在mmc_attach_sdio中首先是mmc匹配一个bus,即采用何种bus来进行mmc bus来处理host。在这里需要理解一点就是在SDIO中,对于SD卡存储器mmc为实体设备,而对于非SD卡存储器,如SDIO接口的设备,则mmc则表征为bus,这个比较重要。除了mmc bus外还存在SDIO_BUS。

int mmc_attach_sdio(struct mmc_host *host,u32 ocr)

{

         interr;

         inti, funcs;

         structmmc_card *card;

         mmc_attach_bus(host,&mmc_sdio_ops); --host匹配一条mmc bus

         card= mmc_alloc_card(host, NULL); --申请一个card实体,类似于总线设备。

         card->type= MMC_TYPE_SDIO;

         card->sdio_funcs= funcs;

         host->card= card;

         for(i = 0;i < funcs;i++) {

                   sdio_init_func(host->card,i + 1);

         }

         mmc_release_host(host);

         mmc_add_card(host->card);

         for(i = 0;i < funcs;i++) {

                   sdio_add_func(host->card->sdio_func[i]);      

}

         return0;

}

比较难以理解的是func,这个东东其实是一个实体设备的封装,可以认为其是一个设备。

struct sdio_func *sdio_alloc_func(structmmc_card *card)

{

         structsdio_func *func;

         func= kzalloc(sizeof(struct sdio_func), GFP_KERNEL);

         func->card= card;

         device_initialize(&func->dev);

         func->dev.parent= &card->dev;  --很明显card设备为sdio设备的父设备。

         func->dev.bus= &sdio_bus_type;

         func->dev.release= sdio_release_func;

         returnfunc;

}

上面的code一目了然,其就是具体设备实体的封装,其bus类型为sdio_bus. sdio_init_func仅仅是初始化一个设备,而并没有register。在sdio_add_func实现设备的register,同理就是card实体,在mmc_add_card之前并没有注册,在mmc_add_card函数中才实现设备的注册。

到此设备注册也就完成了,其实sdio总线在形式上类似于usb bus,为什么呢?编写过usb驱动的童鞋们应该知道,编写usb驱动仅仅是编写驱动的加载,并没有具体加载设备实体,导致很多童鞋的困惑,为什么没有设备的加载,其实在usb设备插入时,会动态的创建一个usb设备实体,在usb设备实体创建完成后,根据不同设备id调用相匹配的驱动。而SDIO设备设备也是一样的。上面的code比较混乱,总是让人看不出具体的设备的加载。其实在上面的code中,其中包括了mmc host的驱动。

三.驱动加载

我们还是以SDIO驱动为例,注册一个SDIO驱动会调用下面的函数。

int sdio_register_driver(struct sdio_driver*drv)

{

         drv->drv.name= drv->name;

         drv->drv.bus= &sdio_bus_type;

         returndriver_register(&drv->drv);

}

其实很好理解sdio_driver其实是driver的封装,并且该driver的bus为sdio_bus_type。这个设备的驱动很简单。那来看sdio_driver结构

struct sdio_driver{

         char*name; --驱动名称

         conststruct sdio_device_id *id_table;  --驱动设备ID

         int(*probe)(struct sdio_func *, const struct sdio_device_id *);

         void(*remove)(struct sdio_func *);

         structdevice_driver drv;

};

id_table很熟悉吧,嘿嘿在usb的驱动中如何将设备和驱动匹配就是根据这个东西。在SDIO中也是根据该id_table来进行设备和驱动的匹配。

四.驱动和设备匹配

在介绍了设备注册和驱动注册后,那来看这两个是怎样匹配的。记住SDIO驱动之上有两条总线一个mmc bus 一个是SDIO bus。

先来看mmc bus的match

static int mmc_bus_match(struct device *dev,struct device_driver *drv)

{

         return1;

}

这个很省事,直接就是1.

那在看sdio bus 的match

static int sdio_bus_match(struct device *dev,struct device_driver *drv)

{

         structsdio_func *func = dev_to_sdio_func(dev);

         structsdio_driver *sdrv = to_sdio_driver(drv);

         if(sdio_match_device(func, sdrv))

                   return1;

         return0;

}

通过查看上面的具体code的实现你就会发现就是根据id_table来实现设备和驱动的匹配。

五.probe

不管在设备注册还是驱动注册时,如果发现存在对应的设备和驱动则会调用对应的驱动。不过记住一点是均会先调用mmc bus的probe其次是sdio bus的probe。其实现的过程与platfrom类似,不多加赘述。

六.总结

SDIO说白了还是一种总线,其本质还是离不开驱动和设备这两者,如果有usb驱动的经验则会很好的理解SDIO总线的驱动。在linux内核是可以触类旁通的


- sdio_register_driver():向系统注册sdio接口驱动,调用以后,系统会触发sdio设备id检测,如果设备id和接口驱动里.id_table里定义的id一致,则系统调用probe函数。
1. 可以在DibBridgeTargetModuleInit()里调用,这样insmod之后,驱动接口即被注册(设备id被注册),有相应设备插入则probe会被调用(此种做法参考LinuxKernelSdioMx28)
2. 也可以在sdio初始化时调用,这样设备插入时,probe不会被调用,只有在sdio初始化,sdio_register_driver()被调用时,系统才会重新检测设备id,并调用probe。(此种做法好处是,模块初始化不涉及何种设备,具有更好的通用性。参考LinuxKernelSdioMx53)




2016-06-18 09:57:40 lu1005287365 阅读数 3462

Apache POI

中文名           适用于
Apache POI 跨平台的 Java API Microsoft Office格式档案读写 Excel97-2016


简介

Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“可怜的模糊实现”。
Apache POI 是创建和维护操作各种符合Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API。用它可以使用Java读取和创建,修改MS Excel文件.而且,还可以使用Java读取和创建MS Word和MSPowerPoint文件。Apache POI 提供Java操作Excel解决方案(适用于Excel97-2008)。(简介部分来自百度百科)


下载POI

到apache 官方网站下载POI 的jar 包 然后解压如下图所示





快速入门案例(创建一个工作簿并在不同单元格设置不同的值)

创建一个JAVA项目



导入Jar包



Jar包说明:当我们只要使用xls格式时、只要导入poi-3.14-20160307.jar就可以了。

当我们还要使用xlsx格式、还要导入poi-ooxml-3.14-20160307.jar。

至于poi-ooxml-schemas-3.14-20160307.jar这个jar基本不太会用到的。

当我们需要操作word、ppt、viso、outlook等时需要用到poi-scratchpad-3.14-20160307.jar。

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Date;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

/**
 * 作者:[LYW]
 */
public class Demo3 {
	public static void main(String[] args) throws Exception {
		// 定义一个工作簿
		Workbook wb = new HSSFWorkbook();
		//创建Sheet页 默认名为sheet+编号编号从0开始
		Sheet sheet = wb.createSheet();
		//创建行
		Row row = sheet.createRow(0);
		//创建单元格
		Cell cell = row.createCell(0);
		//给单元格 设置值
		cell.setCellValue(1);			//int行
		
		row.createCell(1).setCellValue(1.2);	//float
		row.createCell(2).setCellValue("这是一个字符串");	//String
		row.createCell(3).setCellValue(new Date());		//Date类型
		row.createCell(4).setCellValue(false);		//Boolean类型
		
		//定义输出流
		OutputStream out = new FileOutputStream("D://单元格.xls");
		wb.write(out);
		out.close();
	}
}

通过以上代码实现效果为:




POI的更多操作请看后续更新 谢谢观赏 转载请注意出处

linux i2c驱动分析

阅读数 491

没有更多推荐了,返回首页