2019独角兽企业重金招聘Python工程师标准>>>

1、首先要做Bluez协议栈的移植,这样在开发板上才可以用hciconfig, hcitool等命令。关于bluez协议栈的移植步骤网上很多。

2、该驱动是USB蓝牙设备驱动,分析根据蓝牙驱动的写的顺序进行。因为只是要做数据的传输,所以讲用于语音的等时传输部分去掉了。

首先,定义一个结构体

struct bcm_data ={
        struct usb_endpoint_descriptor *intr_ep;
        struct usb_endpoint_descriptor *bulk_tx_ep;     //批量传输的收端点
        struct usb_endpoint_descriptor *bulk_rx_ep;    //批量传输的收端点
     
        struct usb_anchor tx_anchor;             //用于阻塞操作
        struct usb_anchor intr_anchor;
        struct usb_anchor bulk_anchor;
     
        struct usb_device *udev;
        struct usb_interface *intf;
     
        unsigned long flags;
     
        __u8 cmdreq_type;
    }

接下来是入口函数和出口函数

static int __init bcm_driver_init(void)
    {
        usb_register(&bcm_driver);
        return 0;
    }
     
    static void __exit bcm_driver_exit(void)
    {
        usb_deregister(&bcm_driver);
    }
    module_init(bcm_driver_init);
    module_exit(bcm_driver_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("WillwWu")

入口函数和出口函数是对该USB设备进行注册和注销的操作。

然后是定义struct usb_driver,并对其成员进行填充。

static struct usb_driver bcm_driver={
        .name           = "BCMT",
        .probe        = bcm_probe,       //探测函数
        .disconnect    = bcm_disconnect,
        .id_table        = bcm_table,        //所支持的USB设备表
        .supports_autosuspend = 1,        //支持自动挂起,若是设置为0则不支持
        .disable_hub_initiated_lpm = 1,    //允许低功率态的传输
    };

支持的USB设备表

static usb_device_id bcm_table[]={
        {    USB_DEVICE(0x0a5c, 0x2148)},
            {},
    }
    MODULE_DEVICE_TABLE(usb, bcm_table);

MODULE_DEVICE_TABLE用于输出到用户空间,以便于知道支持什么设备,第一个参数是所支持的类型,此处为USB。

下面来看看探测函数

static int bcm_probe (struct usb_interface *intf ,const struct usb_device_id * id)
    {
        struct usb_endpoint_descriptor *ep_desc;
        struct hci_dev  *hdev;
        struct bcm_data *data;
        int  i,err;
     
        if(intf->cur_altsetting->desc.bInterfaceNumber !=0)   //该接口的编号,端点0保留
            return -ENODEV;
        data=kzalloc( sizeof(*data) ,  GFP_KERNEL)
            if(!data)
                return -ENOMEM;
        for(i=0;i<intf->cur_altsetting->desc.bNumEndpoints;i++){   //对端点描述符进行分配
                ep_desc = &intf->cur_altsetting->endpoint[i].desc;
                if(!data->intr_ep && usb_endpoint_is_int_in(ep_desc)){
                    data->intr_ep=ep_desc;
                    }
                if(!data->bulk_tx_ep && usb_endpoint_is_bulk_out(ep_desc)){
     
                    data->bulk_tx_ep=ep_desc;
                    }
                if(!data->bulk_rx_ep && usb_endpoint_is_bulk_in(ep_desc)){
                    data->bulk_rx_ep=ep_desc;
                    }
                if(!data->intr_ep||!data->bulk_tx_ep||!data->bulk_rx_ep){
                    kfree(data);
                    return -ENODEV;
            }    
            }
        data->cmdreq_type=USB_TYPE_CLASS;
        data->udev=interface_to_usbdev(intf); //从接口描述符获取usb_device结构体信息并赋值
        data->intf=intf;
     
        init_usb_anchor(&data->tx_anchor);    //初始化阻塞
        init_usb_anchor(&data->intr_anchor);
        init_usb_anchor(&data->bulk_anchor);
     
        hdev=hci_alloc_dev();        //申请一个hci_dev
        if(!hdev){
            kfree(data);
            return -ENOMEM;
            }
        hdev->bus = HCI_USB;
        hci_set_drvdata(hdev, data);    //将data中的数据保存到hdev中
        data->hdev=hdev;
        SET_HCIDEV_DEV(hdev, intf->dev);
        /*设置hdev的各成员的函数指针*/
        hdev->open = bcm_open;  
        hdev->close = bcm_close;
        hdev->flush  = bcm_flush
        hdev->send  =bcm_send;
        
        if (!reset)
            set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks);
        err=hci_register_dev(hdev) //注册hci_dev
        if (err < 0) {
            hci_free_dev(hdev);
            kfree(data);
            return err;
                }
        usb_set_intfdata(intf, data);  //将data中的数据保存到intf中
        
        return 0;
    }

要区分一下的是:

bNumInterfaces : 配置所支持的接口数.指该配置配备的接口数量,也表示该配置下接口描述符数量.

bInterfaceNumber: 该接口的编号.

bNumEndpoint : 使用的端点数目.端点0除外.

static void bcm_disconnect(struct usb_interface *intf)
    {
        struct bcm_data *data;
        struct hci_dev *hdev;
     
        if(!data)
            return ;
        hdev = data->hdev;
        intf = data->intf;
        usb_set_intfdata(intf, NULL);
        hci_unregister_dev( hdev);
        hci_free_dev( hdev);
        kfree(data);
    }

该函数所做的就是对probe函数中的注册等一系列操作的反操作。

static int bcm_open(struct hci_dev *hdev)
    {
        ……
        if(test_and_set_bit(HCI_RUNNING, &hdev->flags))
            return 0;
        if(test_and_set_bit(BCM_INTR_RUNNING,&data->flags))//BCM_INTR_RUNNING=0
            return 0;
        err=bcm_submit_intr_urb(hdev,GFP_KERNEL);
        if(err<0)
            goto error;
        set_bit(BCM_BULK_RUNNING,&data->flags);    //BCM_BULK_RUNNING=1                
        err=bcm_submit_bulk_urb(hdev,GFP_KERNEL);
    ……
    error:
        clear_bit(HCI_RUNNING, &hdev->flags);
        clear_bit(BCM_INTR_RUNNING,&data->flags);
        clear_bit(BCM_BULK_RUNNING,&data->flags);
        return err;
    }

这个函数是probe中对hdev结构体成员的填充的。主要做就是设置data中的flags参数。其中要说的是set_bit函数,例如set(0,&a)指的是对a中的第0位设置为1.

这个函数的作用其实也是在做接收函数的初始化的操作,首先我们先看看err=bcm_submit_intr_urb(hdev,GFP_KERNEL);

static int bcm_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags)
    {
        struct bcm_data *data=hci_get_drvdata(hdev) //获取data数据
        struct urb *urb;
        unsigned char *buf;
        unsigned int pipe;
        int err,size;
     
        if (!data->intr_ep)
            return -ENODEV;
        urb=usb_alloc_urb(0, mem_flags);    分配一个urb
        if(!urb)
            return -ENOMEM;
        size=le16_to_cpu(data->intr_ep->wMaxPacketSize);   //设置最大包的长度大小
        buf=kzalloc(size, mem_flags);                 //分配一个缓冲区
        pipe=usb_rcvintpipe(data->udev, data->intr_ep->bEndpointAddress); //设置USB的接收端点
        usb_fill_int_urb(urb, data->udev, pipe, buf, size, bcm_intr_complete, hdev ,data->intr_ep->bInterval);     //这个时候就要对urb进行填充了,使用了中断urb
        urb->transfer_flags |=URB_FREE_BUFFER;//Free transfer buffer with the URB
        usb_anchor_urb(urb, &data->intr_anchor);
        err = usb_submit_urb(urb, mem_flags); //将填充的urb提交给usb core处理。
        if(err<0)
            usb_unanchor_urb(urb);
        usb_free_urb(urb);   //防止重复提交,先进行释放。
        return err;
    }

在usb_fill_int_urb中有个回调函数,当提交了urb后,将调用该回调函数bcm_intr_complete。

static void bcm_intr_complete(struct urb *)
    {
        struct hci_dev *hdev = urb->context;
        struct bcm_data *data = hci_get_drvdata(hdev);
        int err;
     
        if(test_bit(HCI_RUNNING, &hdev->flags))
            return
    /*判断urb是否发送成功,若status为0,则表示数据被发送或者接受成功*/
        if(urb->status==0){
            hdev->stat.byte_rx+=urb->actual_length;
            if(hci_recv_fragment( hdev,HCI_EVENT_PKT, urb->transfer_buffer, urb->actual_length)<0)
                hdev->stat.err_rx++;
            }
        if(!test_bit(BCM_INTR_RUNNING, &data->flags));
            return;
        usb_anchor_urb(urb, &data->intr_anchor);
        err=usb_submit_urb(urb, GFP_KERNEL);
        if(err<0){
            usb_unanchor_urb(urb);
        }
    }

帧的类型:

1) HCI_EVENT_PKT:     hci_event_packet() 处理来自Controller的事件

2) HCI_ACLDATA_PKT: hci_acldata_packet() 处理ACL类型的数据包

3) HCI_SCODATA_PKT: hci_scodata_packet() 处理SCO类型的数据包

hci_recv_fragment是bt协议栈数据接收函数。 hci_recv_fragmen 将数据帧放到hci_dev->rx_q链表尾部

int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count)
    {
        int rem = 0;
     
        if (type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT)
            return -EILSEQ;
     
        while (count) {
            rem = hci_reassembly(hdev, type, data, count, type - 1);
            if (rem < 0)
                return rem;
     
            data += (count - rem);
            count = rem;
        }
     
        return rem;
    }

下面是批量传输的bulk_urb的初始化操作

static int bcm_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags)
    {
        struct bcm_data *data=hci_get_drvdata(hdev);
        struct urb *urb;
        unsigned *buf;
        unsigned int pipe;
        int err,size = HCI_MAX_FRAME_SIZE;
     
        if(!data->bulk_rx_ep)
            return -ENODEV;
        urb=usb_alloc_urb(0, mem_flags);
        if(!urb)
            return -ENOMEM;
        buf=kzalloc(size, mem_flags);
        pipe=usb_rcvbulkpipe(data->udev, data->bulk_rx_ep->bEndpointAddress);
        usb_fill_bulk_urb(urb, data->udev, pipe, buf, size, bcm_bulk_complete, hdev);
        usb_anchor_urb(urb, &data->bulk_anchor);
        err=usb_submit_urb(urb, mem_flags);
        if(err<0)
            usb_unanchor_urb( urb)
        usb_free_urb(urb);
        return err;
     
    }

该函数的操作与上面那个中断的几乎相同,就是在usb_fill_bulk_urb时使用了批量urb。

static void bcm_bulk_complete(struct urb *)
    {
        struct hci_dev *hdev = urb->context;
        struct bcm_data *data = hci_get_drvdata(hdev);
        int err;
     
        if(test_bit(HCI_RUNNING, &hdev->flags))
            return
        if(urb->status==0){
            hdev->stat.byte_rx+=urb->actual_length;
            if(hci_recv_fragment( hdev,HCI_ACLDATA_PKT, urb->transfer_buffer, urb->actual_length)<0)
                hdev->stat.err_rx++;
            }
        if(!test_bit(BCM_BULK_RUNNING, &data->flags));
            return;
        usb_anchor_urb(urb,& data->bulk_anchor);
        err=usb_submit_urb(urb, GFP_KERNEL);
        if(err<0){
            usb_unanchor_urb(urb);
        }
    }

此处也与中断的一样。

下面来看看对于发送函数时如何进行操作的。在Linux中,定义了五种HCI数据包类型

COMMAND/ACLDATA/SCODATA/EVENT/VENDOR,我们此处只对其中的COMMAND和ACLDATA进行发送。bcm_send用于提供给HCI去发送数据包。

static int bcm_send (struct sk_buff *skb)
    {
        struct hci_dev *hdev = (struct hci_dev *) skb->dev;
        struct bcm_data *data=hci_get_drvdata( hdev);
        struct urb *urb;
        struct usb_ctrlrequest *cr;
        unsigned int pipe;
     
        if(!test_bit(HCI_RUNNING,&hdev->flags))     //每一步都要首先检测是否正在运行
            return -EBUSY;
        switch(bt_cb(skb)->pkt_type){           //从skb中的控制buffer中取出包的类型
            case HCI_COMMAND_PKT:
                urb=usb_alloc_urb(0, GFP_ATOMIC);
                if(!urb)
                    return -ENOMEM;
                cr=kmalloc(sizeof(*cr), GFP_ATOMIC);
                if(!cr){
                    usb_free_urb(urb);
                    return -ENOMEM;
                    }
                cr->bRequestType = data->cmdreq_type;
                cr->bRequest     = 0;
                cr->wIndex       = 0;
                cr->wValue       = 0;
                cr->wLength      = __cpu_to_le16(skb->len);
     
                pipe = usb_sndctrlpipe(data->udev, 0x00);
     /*填充控制URB,这里我们需要注意的是,此处的数据缓冲区和数据的长度,都是由skb中的结构体成员进行设置的*/
                usb_fill_control_urb(urb, data->udev, pipe, (void *) cr,skb->data, skb->len, bcm_tx_complete, skb);
                hdev->stat.cmd_tx++;
                break;
            case HCI_ACLDATA_PKT
                urb=usb_alloc_urb(0, GFP_ATOMIC);
                if(!urb)
                    return -ENOMEM;
                pipe=usb_sndbulkpipe(data->udev, data->bulk_rx_ep->bEndpointAddress);
                usb_fill_bulk_urb( urb, data->udev, pipe, skb->data, skb->len, bcm_tx_complete, skb);   //填充批量URB
                hdev->stat.acl_tx++;
                        break;
            default:
                return -EILSEQ;
            }
            usb_anchor_urb(urb, &data->tx_anchor);
            err=usb_submit_urb(urb,GFP_ATOMIC);
            if(err<0){
                kfree(urb->setup_packet);
                usb_unanchor_urb(urb);
                }
            return err;
    }

首先我们要来看看struct sk_buff 这个结构体。

sk_buff是Linux网络代码中最重要的结构体之一。它是Linux在其协议栈里传送的结构体,也就是所谓的“包”,在他里面包含了各层协议的头部,比如ethernet, ip ,tcp ,udp等等。并且他是一个复杂的双向链表,在他结构中有next和prev指针,分别指向链表的下一个节点和前一个节点.

此处的回调函数是bcm_tx_complete

static void bcm_tx_complete(struct urb *)
    {    
        struct sk_buff *skb=urb->context;
        struct hci_dev *hdev = (struct hci_dev *)skb->dev;
        struct bcm_data *data= hci_get_drvdata(hdev);
     
        if(!test_bit(HCI_RUNNING,&hdev->flags));
            goto done ;
        if(!urb->status)
            hdev->stat.byte_tx+=urb->transfer_buffer_length;
        else
            hdev->stat.err_tx++;
    done:
        kfree(urb->setup_packet);
        kfree_skb(skb);
    }

最后是close函数

static int bcm_close(struct hci_dev *hdev)
    {
        struct bcm_data *data = hci_get_drvdata(hdev);
        if(!test_and_clear_bit(HCI_RUNNING,&hdev->flags))
            return 0;
        clear_bit(BCM_INTR_RUNNING, &data->flags);
        clear_bit(BCM_BULK_RUNNING, &data->flags);
        data->intf->needs_remote_wakeup=0;
        return 0;
    }

就是针对data的flags进行位清零设置。

最后

static int bcm_flush (struct hci_dev *hdev)
    {
        struct bcm_data *data=hci_get_drvdata( hdev)
        usb_kill_anchored_urbs(&data->tx_anchor);  //取消传输请求
        return 0;
    }

转载于:https://my.oschina.net/u/920274/blog/2994672

linux下的蓝牙驱动程序详解相关推荐

  1. Linux下安装网卡驱动程序详解

    一.查看网卡型号和机器位数 1.查看网卡型号 linux系统下通过以下命令,可以查看当前的网卡驱动信息: [box color="white" icon="none&qu ...

  2. linux下sort命令使用详解---linux将文本文件内容加以排序命令

    转载自:http://www.cnblogs.com/hitwtx/archive/2011/12/03/2274592.html linux下sort命令使用详解---linux将文本文件内容加以排 ...

  3. Linux下的awk用法详解

    Linux下的awk用法详解 一.awk介绍 二.awk的语法 三.awk常见用法 四.awk其他用法 五.awk语言特性 一.awk介绍 1.AWK 是一种处理文本文件的语言,是一个强大的文本分析工 ...

  4. linux输入一个用户看是否在工作,linux下的用户管理详解

    linux下的用户管理详解 useradd 命令详解 添加用户 想要对linux下面的帐号了解的话首先必须要了解的4个配置文件[root@localhost /]# cat /etc/passwd 首 ...

  5. Linux下find命令用法详解

    Linux下find命令用法详解 学神VIP烟火 学神IT教育:XueGod-IT 最负责任的线上直播教育平台 本文作者为VIP学员 烟火 第一部分:根据文件名查找 1.在当前目录查找名称为test的 ...

  6. linux中which命令详解,Linux下which命令使用详解(转)

    我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索: which 查看可执行文件的位置. whereis 查看文件的位置. locate 配合数据库查看文件位置. f ...

  7. linux下测试ftp传输,linux下ftp命令使用详解---linux文件传输ftp命令

    linux下ftp命令使用详解---linux文件传输ftp命令 上一篇 / 下一篇  2010-12-18 09:15:35 / 个人分类:Linux ftp(file transfer proto ...

  8. rm命令linux00,linux下rm命令使用详解 | Soo Smart!

    linux下rm命令使用详解---linux删除文件或目录命令 用户可以用rm命令删除不需要的文件.该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除 ...

  9. linux下top命令参数详解

    linux下top命令参数详解 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器.下面详细介绍它的使用方法. 内存信息.内容如下: ...

  10. Linux下文件查找使用详解-羽飞作品

    Linux下文件查找使用详解 (1/3) http://www.boobooke.com/v/bbk1579 Linux下文件查找使用详解 (2/3) http://www.boobooke.com/ ...

最新文章

  1. 子窗体更新父窗体控件内容
  2. 数据中心基础设施故障处理流程
  3. 如何下载 SAP Cloud for Customer Cloud Application Studio
  4. [vue] 在vue事件中传入$event,使用e.target和e.currentTarget有什么区别?
  5. 云存储服务器销售,云存储服务器销售
  6. 使用 sync.ErrGroup 实现并发搜索文件
  7. hashmap.clear会不会失败_每日三省吾身 | 试问:现在的你,还害怕失败吗?
  8. Vue学习笔记之03v-on事件监听
  9. oracle两种用户认证方式
  10. Crossing Road Simulation
  11. 苹果隐私十年史:变与不变(3)产品与常识
  12. php经典实例读后感,读卡耐基《人性的弱点》章节之读后感
  13. 一文2000字搞懂高并发性能指标:QPS、TPS、RT、并发数、吞吐量
  14. 字节跳动开源隐私合规检测工具appshark
  15. Web scraper 爬虫傻瓜教程(不断更新中)
  16. 绝对位置运动指令(MoveAbsJ)
  17. DSP-FTU实现DNP3.0
  18. java xmap_使用XMAP完成JavaBean和XML之间转换
  19. Website Watchman for Mac(mac网站内容监控工具)
  20. 怎么样区分劳动关系和劳务关系

热门文章

  1. Hyrax: Doubly-efficient zkSNARKs without trusted setup学习笔记
  2. 【转】Iphone4/4S验机教程
  3. 魅蓝s6手机sim卡不显示无服务器,科普OPPOA57怎么截图及魅蓝S6怎么插卡
  4. 向大众推荐的字处理器,TEXMACS
  5. a标签的href属性与事件修饰符阻止默认行为
  6. 安卓TV应用 Hello Word - 怎样新建一个Android TV 项目
  7. c语言程序经过编译以后生成的文件名的后缀为,c语言源文件经过编译后生成文件的后缀是什么...
  8. 厚着脸皮求领导写了一篇java小白进阶大牛之路!!!
  9. LUP分解求解线性方程组及求逆矩阵 java
  10. 阿里巴巴国际站全屏代码装修贸店铺装修平台国际站平台全屏代码装修方法教程视频教程