Source: http://blog.csdn.net/walkingman321/article/details/7218705
This article analyzes the implementation of the Bluetooth-to-serial port (SPP) Part in the Bluetooth protocol stack.
1. Basic Concepts
Bluez provides the Bluetooth-to-serial port function, and applications can use the serial interface to control the serial port function of bluez.
1.1 start the SPP service and wait for remote device connection:
Org. bluez. serialproxymanager-> createproxy // get a serial proxy
Org. bluez. serialproxy-> setserialparameters // you can specify serial parameters.
Org. bluez. serialproxy-> enable // start the serial port
Then, bluez waits for the remote device to connect to the local serial port service.
1.2 actively connecting to the SPP service of a remote device:
When a remote device establishes a connection with a local device and the local device finds that the remote device contains the SPP function, bluez registers an instance of the org. bluez. serial interface. Applications can call the Connect interface to establish a serial connection with a remote device.
2. Code Analysis
SPP-related code is in the serial directory of bluez, and its initialization function is serial_init.
Static int serial_init (void)
...
Serial_manager_init (connection );
Int serial_manager_init (dbusconnection * conn)
...
Btd_register_adapter_driver (& serial_proxy_driver );
Btd_register_device_driver (& serial_port_driver );
The serial_manager_init function contains two operations: one is to register serial_proxy_driver, and the other is to register serial_port_driver.
2.1 serial_proxy_driver
Static struct btd_adapter_driver serial_proxy_driver = {
. Name = "Serial-proxy ",
. Probe = proxy_probe,
. Remove = proxy_remove,
};
After bluez is started, the proxy_probe function is called for each Bluetooth adapter device.
Static int proxy_probe (struct btd_adapter * adapter)
...
Proxy_register (connection, adapter );
Int proxy_register (dbusconnection * Conn, struct btd_adapter * btd_adapter)
...
Struct serial_adapter * adapter = g_new0 (struct serial_adapter, 1 );
Adapter-> conn = dbus_connection_ref (conn );
Adapter-> btd_adapter = btd_adapter_ref (btd_adapter );
Path = adapter_get_path (btd_adapter );
// Register the serialproxymanager Interface
G_dbus_register_interface (Conn, path, serial_manager_interface,
Manager_methods, manager_signals, null,
Adapter, manager_path_unregister );
Adapters = g_slist_append (adapters, adapter );
// Register the default serialproxymanager Interface Based on the configuration file. You can skip this step.
Serial_proxy_init (adapter );
The method of serialproxymanager interface is as follows:
Static gdbusmethodtable manager_methods [] = {
{"Createproxy", "SS", "S", create_proxy },
{"Listproxies", "", "as", list_proxies },
{"Removeproxy", "S", "", remove_proxy },
{},
};
When an application needs to create a serial proxy instance, it can call the createproxy method. This method is mapped to the create_proxy function.
Static dbusmessage * create_proxy (dbusconnection * Conn,
Dbusmessage * MSG, void * Data)
// Obtain the parameters passed by the application.
Dbus_message_get_args (MSG, null,
Dbus_type_string, & pattern,
Dbus_type_string, & address,
Dbus_type_invalid );
// Obtain the UUID
Uuid_str = bt_name2string (pattern );
Register_proxy (adapter, uuid_str, address, & proxy );
...
Static int register_proxy (struct serial_adapter * adapter,
Const char * uuid_str, const char * address,
Struct serial_proxy ** proxy)
// Determine the address type based on the address name passed by the application. The type can be UNIX socket, serial port, or TCP socket. This socket is used for serial data communication between bluez and applications. If the application sends data, this socket is written; if the application receives data, bluez writes the socket. For details about the conversion from address to type, refer to the specific implementation of the function.
Type = addr2type (Address );
...
// Enter the corresponding initialization function based on the address type.
Switch (type ){
Case unix_socket_proxy:
Err = proxy_socket_register (adapter, uuid_str, address, proxy );
Break;
Case tty_proxy:
Err = proxy_tty_register (adapter, uuid_str, address, null,
Proxy );
Break;
Case tcp_socket_proxy:
Err = proxy_tcp_register (adapter, uuid_str, address, proxy );
Break;
Default:
Err =-einval;
}
...
Since the initialization of the three geological types is similar, only the UNIX socket types are studied here:
When Upper-layer applications use UNIX socket, the application establishes a socket of this type, and then transmits the socket address to bluez. bluez also creates a socket based on this address. Then, bluez and the application can use this socket to transmit data to each other like an Operation pipeline. In the implementation of bluez, this socket is used to transmit Bluetooth serial port data.
Static int proxy_socket_register (struct serial_adapter * adapter,
Const char * uuid128, const char * address,
Struct serial_proxy ** proxy)
Struct serial_proxy * PRx;
PRx-> address = g_strdup (Address );
PRx-> uuid128 = g_strdup (uuid128 );
PRx-> type = unix_socket_proxy;
Adapter_get_address (adapter-> btd_adapter, & PRx-> SRC );
PRx-> adapter = adapter;
Register_proxy_object (PRx );
Static int register_proxy_object (struct serial_proxy * PRx)
...
G_dbus_register_interface (adapter-> Conn, path,
Serial_proxy_interface,
Proxy_methods, null, null,
PRx, proxy_path_unregister );
...
The above are all the operations of create_proxy. Only a serial port proxy is established here, but this proxy is not enabled yet. The enable operation is completed by the application calling the Enable method in the newly registered interface serial_proxy_interface.
Static gdbusmethodtable proxy_methods [] = {
{"Enable", "", "", proxy_enable },
{"Disable", "", "", proxy_disable },
{"Getinfo", "", "A {SV}", proxy_get_info },
{"Setserialparameters", "syys", "", proxy_set_serial_params },
{},
};
Static dbusmessage * proxy_enable (dbusconnection * Conn,
Dbusmessage * MSG, void * Data)
...
Enable_proxy (PRx );
...
Static int enable_proxy (struct serial_proxy * PRx)
...
// Listen to the RFCOMM Channel
PRx-> IO = bt_io_listen (bt_io_rfcomm, null, confirm_event_cb, PRx,
Null, & Gerr,
Bt_io_opt_source_bdaddr, & PRx-> SRC,
Bt_io_opt_invalid );
// When listen is called, bluez (local Bluetooth adapter) allocates a channel and waits for the other party to connect to the channel. This channel is very important because various applications on RFCOMM are differentiated based on channel numbers in the Bluetooth protocol.
Bt_io_get (PRx-> Io, bt_io_rfcomm, & Gerr,
Bt_io_opt_channel, & PRx-> channel,
Bt_io_opt_invalid );
...
Sdp_record_t * record;
// Assign an SDP record. The channel number is set to SDP record.
Record = proxy_record_new (PRx-> uuid128, PRx-> channel );
// Add the record to the SDP Service. The remote device can see the Bluetooth service of the local device. This service is in the PRx-> channel of RFCOMM.
Add_record_to_server (& PRx-> SRC, record );
PRx-> record_id = record-> handle;
...
At this point, the local device is ready to accept the serial port connection from the remote device. when the other party connects, The confirm_event_cb callback function in bt_io_listen called above will be called.
In confirm_event_cb, bt_io_accept is called to establish a connection for the authentication operation. After successful accept, the callback function connect_event_cb will be called.
Static void connect_event_cb (giochannel * Chan, gerror * conn_err, gpointer data)
...
Switch (PRx-> type)
Case unix_socket_proxy:
SK = unix_socket_connect (PRx-> address );
Break;
Case tty_proxy:
...
Case tcp_socket_proxy:
...
G_io_add_watch (PRx-> RFCOMM,
G_io_in | g_io_hup | g_io_err | g_io_nval,
Forward_data, PRx );
G_io_add_watch (PRx-> local,
G_io_in | g_io_hup | g_io_err | g_io_nval,
Forward_data, PRx );
...
In the unix_socket_connect function, a UNIX socket is created based on the address and connect is executed. Note that the upper-layer application has also established such a socket. If the upper-layer application calls select to wait for this socket, the upper-layer application will be notified after connect is called, the connection to the remote device has been established.
Next let's take a look at the g_io_add_watch call. This is called twice. One is for the socket of the remote device, and the other is for the socket between the upper-layer application, that is, the newly established UNIX socket. There is a pairing relationship between the two sockets. One is used for serial data transmission with the remote device, and the other is used for data transmission with upper-layer applications. After the two channels are associated, the connection between the remote device and the upper-layer application is established. This association is implemented by the callback function forward_data called by g_io_add_watch twice.
When forward_data is called, it indicates that the corresponding channel has data. Its pseudocode is as follows:
1. Obtain the data on the channel.
2. send data to another channel.
In this way, the two channels are associated.
2.2 serial_port_driver
Static struct btd_device_driver serial_port_driver = {
. Name = "Serial-port ",
. Uuids = btd_uuids (rfcomm_uuid_str ),
. Probe = port_probe,
. Remove = port_remove,
};
When a remote device with UUID rfcomm_uuid_str is connected to a local device, bluez calls the port_probe function of serial_port_driver.
Static int port_probe (struct btd_device * device, gslist * uuids)
While (uuids ){
Serial_probe (device, uuids-> data );
Uuids = uuids-> next;
}
Static int serial_probe (struct btd_device * device, const char * UUID)
Const sdp_record_t * rec = btd_device_get_record (device, UUID );
// Obtain the channel number from SDP record. This channel is used for serial communication.
Int CH = sdp_get_proto_port (PROTOS, rfcomm_uuid );
...
Port_register (connection, path, & SRC, & DST, UUID, CH );
Int port_register (dbusconnection * Conn, const char * path, bdaddr_t * SRC,
Bdaddr_t * DST, const char * UUID, uint8_t channel)
...
Create_serial_device (Conn, path, SRC, DST );
...
Static struct serial_device * create_serial_device (dbusconnection * Conn,
Const char * path, bdaddr_t * SRC,
Bdaddr_t * DST)
// Create a new serial_device
Struct serial_device * Device = g_new0 (struct serial_device, 1 );
...
// Register a serial_port_interface Interface
G_dbus_register_interface (Conn, path, serial_port_interface,
Port_methods, null, null, device, path_unregister );
The above operations are performed after the local device finds a remote device that supports spp. Finally, an instance of the serial_port_interface interface is created for this device. This interface is defined:
Static gdbusmethodtable port_methods [] = {
{"Connect", "S", "S", port_connect, g_dbus_method_flag_async },
{"Disconnect", "S", "", port_disconnect },
{}
};
When Upper-layer applications need to actively connect to a remote device, rather than passively waiting for the remote device to connect to a local device, they can call the connect method here. This method is finally mapped to the port_connect function.
The port_connect function eventually calls the bt_io_connect function.
Bt_io_connect (bt_io_rfcomm, rfcomm_connect_cb, port,
Null, null,
Bt_io_opt_source_bdaddr, & device-> SRC,
Bt_io_opt_dest_bdaddr, & device-> DST,
Bt_io_opt_channel, Port-> channel,
Bt_io_opt_invalid );
The callback function after the specified connection is successful is rfcomm_connect_cb.
In rfcomm_connect_cb, IOCTL rfcommcreatedev is called.
This IOCTL creates a device node RFCOMM % d in the/dev directory. The device node name is passed to the upper-layer application. In this way, upper-layer applications can send and receive data through this node, and the data is transmitted from the remote device through the Bluetooth serial port.
Bluez Spp for code analysis (transfer)