篇文章《kernel中bluetooth的初始化》一文中晓东和大家分享了HCI层,L2CAP层以及SCO层的初始化流程,今天晓东继续和大家一起来看rfcomm层的初始化流程。
在正式开始之前,我们先来看一下rfcomm层是什么,百度百科是这样介绍rfcomm的:“一个基于欧洲电信标准协会ETSI07.10规程的串行线性仿真协议。此协议提供RS232控制和状态信号,如基带上的损坏,CTS以及数据信号等,为上层业务(如传统的串行线缆应用)提供了传送能力。
RFCOMM是一个简单传输协议,其目的为了解决如何在两个不同设备上的应用程序之间保证一条完整的通信路径,并在它们之间保持一通信段的问题。”
对这段内容,大家简单地读一下就可以了,若想详细了解rfcomm层的作用和内容,推荐大家去看TS07.10和rfcomm两个spec,他们上面讲解得还是蛮全面的。
晓东这里针对rfcomm的理论内容就只给大家看一张图,这张图来源于rfcomm的spec。
从这张图,我们可以清晰地看到rfcomm是基于L2CAP层的,在他们之下就是Baseband了,当然和Baseband的交互必然就少不了HCI层的支援了。Ok,大体的架构就是这样的,让我们开始代码的阅读吧。
同样的,先找到代码的位置kernel/net/bluetooth/rfcomm文件夹,先看里面的core.c,初始化的代码就是这句了module_init(rfcomm_init);,同样不多介绍module_init的作用,请自己google或者百度。
static int __init rfcomm_init(void)
{
int err;
//把rfcomm_cb加入到hci_cb_list链表中,不详细解释
hci_register_cb(&rfcomm_cb);
//创建并启动rfcomm_run线程,rfcomm_run的操作,详见1
rfcomm_thread = kthread_run(rfcomm_run, NULL, "krfcommd");
……
//bt_debugfs就是sys/kernel/debug/bluetooth,在前一篇中介绍过
//这里就是在这个文件夹下再建一个rfcomm_dlc的文件。不解释
if (bt_debugfs) {
rfcomm_dlc_debugfs = debugfs_create_file("rfcomm_dlc", 0444,
bt_debugfs, NULL, &rfcomm_dlc_debugfs_fops);
if (!rfcomm_dlc_debugfs)
BT_ERR("Failed to create RFCOMM debug file");
}
//rfcomm tty的初始化,见2
err = rfcomm_init_ttys();
if (err < 0)
goto stop;
//rfcomm socket的init,和前一篇的hci是一样的
//主要工作就是向proto_list中加入了rfcomm
//初始化了bt_proto中BTPROTO_RFCOMM的内容
//新建了sys/kernel/debug/bluetooth/rfcomm文件
err = rfcomm_init_sockets();
if (err < 0)
goto cleanup;
……
}
1、 rfcomm_run
这个内核线程在没有意外的情况下将一直在后台运行着,下面我们就来详细看一下它的运转究竟干了些什么。初略一看,是不是感觉代码不多,很简单啊,呵呵~~,慢慢来看吧,其实真的好多内容。
static int rfcomm_run(void *unused)
{
BT_DBG("");
//通常而言,kthread_run创建并运行的线程的优先级是SCHED_NORMAL,也就是优先级值为0的线程
//设置当前的进程优先级为-10
set_user_nice(current, -10);
//增加监听,详细分析见1.1
rfcomm_add_listener(BDADDR_ANY);
while (1) {
//设置当前的线程为可中断的睡眠态,也就是睡眠了
set_current_state(TASK_INTERRUPTIBLE);
//看一下当前线程是不是stop了,若是stop了就结束线程
//一般一些出错啊,exit之类的会调用stop
if (kthread_should_stop())
break;
//检测rfcomm的session list中各个seesion的状态的变换,并做相应的操作,这个在以后的rfcomm连接中再详细介绍
/* Process stuff */
rfcomm_process_sessions();
//调度别的线程去占用cpu
schedule();
}
__set_current_state(TASK_RUNNING);
rfcomm_kill_listener();
return 0;
}
1.1、rfcomm_add_listener
该函数主要工作是新建一个L2CAP的socket,然后在该socket上进行监听。具体看代码分析
static int rfcomm_add_listener(bdaddr_t *ba)
{
……
/* Create socket */
//新建L2CAP层的socket,详细见1.1.1
err = rfcomm_l2sock_create(&sock);
……
//bind addr to socket,详细见1.1.2
/* Bind socket */
bacpy(&addr.l2_bdaddr, ba);
addr.l2_family = AF_BLUETOOTH;
addr.l2_psm = cpu_to_le16(RFCOMM_PSM); //psm是3,表示rfcomm,有蓝牙sig规定
addr.l2_cid = 0;
err = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr));
……
/* Set L2CAP options */
sk = sock->sk;
lock_sock(sk);
//设置l2cap channel的mtu(max transfer unit)
l2cap_pi(sk)->chan->imtu = l2cap_mtu;
release_sock(sk);
//开始在该socket上listen,最多有10个连接
//主要就是设置了一下socket和chan的state,然后设了一下最大的连接
/* Start listening on the socket */
err = kernel_listen(sock, 10);
……
//新建一个rfcomm的session并把它加入到session_list中
//同时把该session和socket进行了关联
/* Add listening session */
s = rfcomm_session_add(sock, BT_LISTEN);
……
rfcomm_session_hold(s);
return 0;
failed:
sock_release(sock);
return err;
}
1.1.1 rfcomm_l2sock_create
该函数最终会调用l2cap的creat函数去创建socket
static int rfcomm_l2sock_create(struct socket **sock)
{
//创建L2CAP层
//l2cap_sock.c -----create
//family—PF_BLUETOOTH
//type—SOCK_SEQPACKET
//proto—BTPROTO_L2CAP
err = sock_create_kern(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP, sock);
if (!err) {
struct sock *sk = (*sock)->sk;
//data ready和state change都设为rfcomm这边的回调函数
sk->sk_data_ready = rfcomm_l2data_ready;
sk->sk_state_change = rfcomm_l2state_change;
}
return err;
}
我们继续来看一下l2cap_sock.c中的create函数
static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
struct sock *sk;
BT_DBG("sock %p", sock);
//socket的state先设为SS_UNCONNECTED
sock->state = SS_UNCONNECTED;
//检查socket的type
if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_STREAM &&
sock->type != SOCK_DGRAM && sock->type != SOCK_RAW)
return -ESOCKTNOSUPPORT;
//SOCK_RAW必须要NET_RAW权限才能建
if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW))
return -EPERM;
//ops的初始化
sock->ops = &l2cap_sock_ops;
//l2cap sock的申请,创建了一个L2cap的socket和一个l2cap的channel
//可以详细见1.1.1.1
sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC);
if (!sk)
return -ENOMEM;
//l2cap channel的初始化,就不详细解释了
l2cap_sock_init(sk, NULL);
return 0;
}
1.1.1.1 l2cap_sock_alloc
这个函数还是比较简单的,就是创建一个sock,初始化了他的一些内容。同时还创建了一个l2cap的channel,并且把它加入到了chan_list链表中去了
static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int proto, gfp_t prio)
{
struct sock *sk;
struct l2cap_chan *chan;
//申请sock
sk = sk_alloc(net, PF_BLUETOOTH, prio, &l2cap_proto);
if (!sk)
return NULL;
//sock init
sock_init_data(sock, sk);
//初始化accept_q
INIT_LIST_HEAD(&bt_sk(sk)->accept_q);
//destruct的初始化
sk->sk_destruct = l2cap_sock_destruct;
//发送的timeout,40s
sk->sk_sndtimeo = L2CAP_CONN_TIMEOUT;
//把flag位reset
sock_reset_flag(sk, SOCK_ZAPPED);
sk->sk_protocol = proto;
//sk_state初始化为BT_OPEN
sk->sk_state = BT_OPEN;
//create channel,并把它加入到chan_list链表中
chan = l2cap_chan_create(sk);
if (!chan) {
//没有能够create,则kill
l2cap_sock_kill(sk);
return NULL;
}
//设置chan
l2cap_pi(sk)->chan = chan;
return sk;
}
1.1.2 kernel_bind
该函数其实就是把addr相关的内容绑定到刚刚新建的socket,说白了就是通过传入一些参数初始化一些socket的内容。这些内容也就是chan的psm和cid值。
static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen)
{
……
if (!addr || addr->sa_family != AF_BLUETOOTH)
return -EINVAL;
memset(&la, 0, sizeof(la));
len = min_t(unsigned int, sizeof(la), alen);
//两者的小的拷贝到sockaddr_l2中
memcpy(&la, addr, len);
//若是有了cid和psm就出错返回了
//这里是传入了psm,但是cid还没有传入
if (la.l2_cid && la.l2_psm)
return -EINVAL;
lock_sock(sk);
//检查一下sk_state是否为BT_OPEN
if (sk->sk_state != BT_OPEN) {
err = -EBADFD;
goto done;
}
//若是已经有了psm
if (la.l2_psm) {
__u16 psm = __le16_to_cpu(la.l2_psm);
/* PSM must be odd and lsb of upper byte must be 0 */
//检查psm的值
//psm的值必须在0x1001以上,且是奇数
//同时最高有效byte的最低位必须为0
if ((psm & 0x0101) != 0x0001) {
err = -EINVAL;
goto done;
}
//确认psm的有效性
/* Restrict usage of well-known PSMs */
if (psm < 0x1001 && !capable(CAP_NET_BIND_SERVICE)) {
err = -EACCES;
goto done;
}
}
//若是有l2_cid
if (la.l2_cid)
//把cid保存到chan->cid中
err = l2cap_add_scid(chan, la.l2_cid);
else
//add psm
//主要是赋值chan->psm和chan->sport
//la.l2psm为0时会按照规定申请
//所以,这个函数主要就是初始化psm的值
err = l2cap_add_psm(chan, &la.l2_bdaddr, la.l2_psm);
if (err < 0)
goto done;
//psm若是1和3,则sec level设为sdp
if (__le16_to_cpu(la.l2_psm) == 0x0001 ||
__le16_to_cpu(la.l2_psm) == 0x0003)
chan->sec_level = BT_SECURITY_SDP;
//socket的src
bacpy(&bt_sk(sk)->src, &la.l2_bdaddr);
//chan的state和sk的sk_state都设为BT_BOUND
chan->state = BT_BOUND;
sk->sk_state = BT_BOUND;
done:
release_sock(sk);
return err;
}
至此,我们可以总结一下,rfcomm和L2CAP或者HCI的初始化相差的地方在于,除了那些debug文件和proto相关的内容外,它启动了一个rfcomm_run的thread在后台不停地运行。该thread在一开始就是创建了一个BDADDR_ANY的L2CAP socket和socket channel,并在该socket上进行了listen。同时该thread还会不停地去监听rfcomm_session list的各个状态的变化,同时根据状态的变化去做相应的处理。