Yongbing's Blog

A personal technical note.

Dissect Bluedroid From A2DP: Part IV: Connect and Communication

| Comments

Item A. Connect a remote A2DP device:

1 Android system will try to reconnect paired A2DP device automatically after BT enable.

1
2
3
4
5
6
7
8
9
04-25 01:56:31.080 D/BluetoothAdapterService( 2093): Auto Connecting A2DP Profile with device 50:C9:71:0D:D2:D9
  packages/apps/Bluetooth/jni/com_android_bluetooth_a2dp.cpp 
  static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address)
      const bt_interface_t* btInf= getBluetoothInterface();
      const btav_interface_t *sBluetoothA2dpInterface = (btav_interface_t *)btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID);
      status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)
          external/bluetooth/bluedroid/btif/src/btif_av.c
          btif_queue_connect(UUID_SERVCLASS_AUDIO_SOURCE, bd_addr, connect_int);//This will trigger an event in btu_task, now the caller thread returned.
              GKI_send_msg(BTIF_TASK, BTU_BTIF_MBOX, p_msg);

2 This will later trigger a A2DP server event API_CONNECT_REQ_EVT in state CCB_IDLE_ST:

1
05-02 01:54:33.342 I/bt-avp  ( 2093): CCB ccb=0 event=API_CONNECT_REQ_EVT state=CCB_IDLE_ST

3 A2DP server handle this event in bellow two actions:

1
2
3
4
5
6
7
8
9
10
11
12
external/bluetooth/bluedroid/stack/avdt/avdt_ccb.c
129 const UINT8 avdt_ccb_st_idle[][AVDT_CCB_NUM_COLS] = {
130 /* Event                      Action 1                    Action 2                    Next state */
139 /* API_CONNECT_REQ_EVT */    {AVDT_CCB_SET_CONN,          AVDT_CCB_CHAN_OPEN,         AVDT_CCB_OPENING_ST},
//3.1 Set CCB variables associated with AVDT_ConnectReq().
996 void avdt_ccb_set_conn(tAVDT_CCB *p_ccb, tAVDT_CCB_EVT *p_data)
  BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_AVDTP, p_data->connect.sec_mask,AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_SIG);

//3.2 initiate a signaling channel connection.
87 void avdt_ccb_chan_open(tAVDT_CCB *p_ccb, tAVDT_CCB_EVT *p_data)
                  BTM_SetOutService(p_ccb->peer_addr, BTM_SEC_SERVICE_AVDTP, AVDT_CHAN_SIG);
                  avdt_ad_open_req(AVDT_CHAN_SIG, p_ccb, NULL, AVDT_INT);

4 How L2CAP handle this channel connection:

1
2
3
4
5
6
7
8
9
10
11
12
13
external/bluetooth/bluedroid/stack/l2cap/l2c_api.c                     
229 UINT16 L2CA_ErtmConnectReq (UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_ERTM_INFO *p_ertm_info)                      
  p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE);
  l2cu_create_conn(p_lcb);//This function initiates an acl connection via HCI
      2180 BOOLEAN l2cu_create_conn_after_switch (tL2C_LCB *p_lcb)
          //external/bluetooth/bluedroid/stack/hcic/hcicmds.c
          btsnd_hcic_create_conn (p_lcb->remote_bd_addr,HCI_PKT_TYPES_MASK_DM1 + HCI_PKT_TYPES_MASK_DH1,page_scan_rep_mode,page_scan_mode,clock_offset,allow_switch));
              HCI_CMD_TO_LOWER(p_buf);
                  //external/bluetooth/bluedroid/main/bte_main.c
                  bt_hc_if->transmit_buf((TRANSAC)p_msg, \ (char *) (p_msg + 1), \p_msg->len);
                      utils_enqueue(&tx_q, (void *) transac);
                       bthc_signal_event(HC_EVENT_TX);
          btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK,L2CAP_LINK_CONNECT_TOUT);

5 This command will trigger connection establish process between local and remote device, accomplished by a event-driven state machine in BT stack.

Item B. a2dp_write data path:

1 A2DP client writes to A2DP data socket will trigger API_WRITE_REQ_EVT in SCB_STREAM_ST state:

1
2
3
4
5
05-02 01:14:03.134 I/bt-avp  ( 2139): SCB hdl=1 event=1/API_WRITE_REQ_EVT state=SCB_STREAM_ST
394 /* state table for streaming state */
395 const UINT8 avdt_scb_st_stream[][AVDT_SCB_NUM_COLS] = {
396 /* Event                     Action 1                       Action 2                    Next state */
398 /* API_WRITE_REQ_EVT */     {AVDT_SCB_HDL_WRITE_REQ,        AVDT_SCB_CHK_SND_PKT,       AVDT_SCB_STREAM_ST},

2 A2DP server handle this with bellow two actions:

1
2
3
4
5
6
7
8
//2.1 build a new media packet and stores it in the SCB.
external/bluetooth/bluedroid/stack/avdt/avdt_scb_act.c
1320 void avdt_scb_hdl_write_req(tAVDT_SCB *p_scb, tAVDT_SCB_EVT *p_data)

//2.2 send this stored media packet to L2CAP layer.
1921 void avdt_scb_chk_snd_pkt(tAVDT_SCB *p_scb, tAVDT_SCB_EVT *p_data)
  avdt_ad_write_req(AVDT_CHAN_MEDIA, p_scb->p_ccb, p_scb, p_pkt);
      L2CA_DataWrite(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid, p_buf);

3 L2CAP to HCI layer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bluedroid/stack/l2cap/l2c_api.c
1633 UINT8 L2CA_DataWrite (UINT16 cid, BT_HDR *p_data)
1636     return l2c_data_write (cid, p_data, L2CAP_FLUSHABLE_CH_BASED);
                  p_ccb = l2cu_find_ccb_by_cid (NULL, cid);
                  l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data);
                      935 static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data)//Just consider channel connected state
                      1050     case L2CEVT_L2CA_DATA_WRITE:                    /* Upper layer data to send */
                      1051         l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_data);
                      1052         l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL);
                      1053         break;
                              l2c_link_check_send_pkts (p_lcb, NULL, NULL);
                                  l2c_link_send_to_lower (p_lcb, p_buf);
                                  1341 #if BLE_INCLUDED == TRUE
                                  1342         if (p_lcb->is_ble_link)
                                  1344             L2C_LINK_SEND_BLE_ACL_DATA(p_buf);
                                  1346         else
                                  1349             L2C_LINK_SEND_ACL_DATA (p_buf);
                                                      bte_main_hci_send((BT_HDR *)(p), BT_EVT_TO_LM_HCI_ACL);
                                                          bt_hc_if->transmit_buf((TRANSAC)p_msg, \(char *) (p_msg + 1),p_msg->len);
                                                              bluedroid/hci/src/bt_hci_bdroid.c:249 static int transmit_buf(TRANSAC transac, char *p_buf, int len)
                                                                  utils_enqueue(&tx_q, (void *) transac);
                                                                  bthc_signal_event(HC_EVENT_TX);

4 HCI content write to hardware device driver

1
2
3
4
5
6
339 static void *bt_hc_worker_thread(void *arg)
      if (events & HC_EVENT_TX)
          p_hci_if->send(sending_msg_que[i]);
          593 void hci_h4_send_msg(HC_BT_HDR *p_msg)
              bytes_sent = userial_write(event,(uint8_t *) p,bytes_to_send);
                   ret = write(userial_cb.fd, p_data+total, len);

Item C. Incoming data/event path:

0 Init vendor (BT chip vendor, like MRVL/TI) implement of bt_vendor_interface_t interface.

1
2
3
4
5
6
7
8
9
10
  187 void init_vnd_if(unsigned char *local_bdaddr)
          dlhandle = dlopen("libbt-vendor.so", RTLD_NOW);               
          GLOBAL bt_vendor_interface_t *bt_vnd_if = (bt_vendor_interface_t *) dlsym(dlhandle, "BLUETOOTH_VENDOR_LIB_INTERFACE");
          bt_vnd_if->init(&vnd_callbacks, local_bdaddr);
          
  306 uint8_t userial_open(uint8_t port)
          result = bt_vendor_interface_t * bt_vnd_if->op(BT_VND_OP_USERIAL_OPEN, &fd_array);
              int bt_vnd_mrvl_if_op(bt_vendor_opcode_t opcode, void *param)
                  mchar_fd = open("/dev/mbtchar0", O_RDWR);
  363     pthread_create(&(userial_cb.read_thread), &thread_attr, userial_read_thread, NULL) != 0 );

1 Got a packet from hardware device driver, in HCI layer.

1
2
3
4
5
6
7
bluedroid/hci/src/userial.c:210
static void *userial_read_thread(void *arg)
  rx_length = select_read(userial_cb.fd, p, READ_LIMIT);
      ret = read(fd, pbuf, (size_t)len);
  utils_enqueue(&(userial_cb.rx_q), p_buf);
  //bluedroid/hci/src/bt_hci_bdroid.c
  bthc_signal_event(HC_EVENT_RX);

2 Transfer this to L2CAP layer.

1
2
3
4
5
6
7
8
9
10
11
bluedroid/hci/src/bt_hci_bdroid.c  
339 static void *bt_hc_worker_thread(void *arg)
  if (events & HC_EVENT_RX)
       p_hci_if->rcv();
          uint16_t hci_h4_receive_msg(void)//Construct HCI EVENT/ACL packets and send them to stack
 957             if (p_cb->p_rcv_msg->event != MSG_HC_TO_STACK_HCI_ACL)
 958                 btsnoop_capture(p_cb->p_rcv_msg, TRUE);//dump to HCI trace file/socket.
 960             if (p_cb->p_rcv_msg->event == MSG_HC_TO_STACK_HCI_EVT)
 965                 bt_hc_cbacks->data_ind((TRANSAC) p_cb->p_rcv_msg, (char *) (p_cb->p_rcv_msg + 1), p_cb->p_rcv_msg->len + BT_HC_HDR_SIZE);
                          bluedroid/main/bte_main.c:504 static int data_ind(TRANSAC transac, char *p_buf, int len)
                              GKI_send_msg (BTU_TASK, BTU_HCI_RCV_MBOX, transac);//handle in btu_task.

Reference:

Dissect Bluedroid From A2DP: Part III: Init A2DP Service

| Comments

1 Get Android defined A2DP interface btav_interface_t from bt_interface_t get_bluetooth_interface().

1
2
3
4
5
6
7
8
9
10
04-25 01:56:30.530 I/BluetoothA2dpServiceJni( 2093): classInitNative: succeeds
packages/apps/Bluetooth/jni/com_android_bluetooth_a2dp.cpp
137 static void initNative(JNIEnv *env, jobject object)
  GLOBAL const btav_interface_t *sBluetoothA2dpInterface = (btav_interface_t *)btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID);
  sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks);
      external/bluetooth/bluedroid/btif/src/btif_av.c
      725 static bt_status_t init(btav_callbacks_t* callbacks )
          686 int btif_a2dp_start_media_task(void)
              GKI_create_task((TASKPTR)btif_media_task, A2DP_MEDIA_TASK,
          btif_enable_service(BTA_A2DP_SERVICE_ID);//Upon BT enable, BTIF core shall invoke the BTA APIs to enable the profiles

2 Init A2DP service by btav_interface_t->init().

* Start a btif_media_task as main loop for A2DP service.
* Open a socket to listen on client's connect request from control channel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
external/bluetooth/bluedroid/btif/src/btif_media_task.c
//Task for SBC encoder.  This task receives an event when the waveIn interface has a pcm data buffer ready.  On receiving the event, handle all ready pcm data buffers.  If stream is started, run the SBC encoder on each chunk of pcm samples and build an output packet consisting of one or more encoded SBC frames.
1066 int btif_media_task(void *p)
1073     btif_media_task_init();
1044     UIPC_Init(NULL);
          606 UDRV_API void UIPC_Init(void *p_data)
              569 int uipc_start_main_server_thread(void)
                  pthread_create(&uipc_main.tid, (const pthread_attr_t *) NULL, (void*)uipc_read_task, NULL)
1047     UIPC_Open(UIPC_CH_ID_AV_CTRL , btif_a2dp_ctrl_cb);
          uipc_setup_server_locked(ch_id, A2DP_CTRL_PATH, p_cback);//This is the control socket that listen on A2DP client.
1079     while (1)//A2DP event loop 
1080     {
1085         if (event & BTIF_MEDIA_TASK_CMD)
1090                 btif_media_task_handle_cmd(p_msg);
1093
1094         if (event & BTIF_MEDIA_TASK_DATA)
1099                 btif_media_task_handle_media(p_msg);
1102
1103         if (event & BTIF_MEDIA_AA_TASK_TIMER)
1105             /* advance audio timer expiration */
1106             btif_media_task_aa_handle_timer();

Reference:

Dissect Bluedroid From A2DP Part I: Use Case

| Comments

Android framework defined two hardware interfaces for operating audio output devices, they are audio_hw_device_t and audio_stream_out_t, AudioFlinger is the only user of these interfaces. Bluedroid implemented these two interface in external/bluetooth/bluedroid/audio_a2dp_hw/audio_a2dp_hw.c, AudioFlinger can output audio sample to a connected A2DP sink device if this implementation has been registered to Android.

In Bluedroid’s A2DP hardware implementation, it will use two sockets to communicate with A2DP server.

Reference:

Skeleton of a Bluetooth SDIO Driver

| Comments

A Bluetooth SDIO card driver talks with hardware through SDIO interface, providing R/W method for Bluetooth Adapter layer, here’s the class diagram for this relationship:

The outbound Bluetooth data path:

  1. Upper layer use HCI interface send() to send data/command packet, implemented as btmrvl_send_frame() in this driver.
  2. Put this packet in adapter’s tx queue, wakeup the main data processing thread (like NAPI in a network driver, thread function is btmrvl_service_main_thread()).
  3. In main data procssing thread, re-organize skb data payload for DMA transfer (in btmrvl_tx_pkt()).
  4. Call sdio_writesb() to write data to hardware (in btmrvl_sdio_host_to_card()).

The incoming Bluetooth data path:

  1. SDIO card received a data packet, triggered a interrupt to host.
  2. The SDIO ISR triggred the main data processing thread.
  3. In this thread, allocate a skb with DAM aligned, call sdio_readsb() to read the data from SDIO interface (in btmrvl_sdio_card_to_host()).
  4. Call hci_recv_frame(skb) to send this data packet to upper layer Bluetooth stack.

Appendix: How to register a driver specific ISR to SDIO’s ISR:

In driver module init, hook up a device ISR to SDIO’s ISR.
1
2
3
4
5
6
7
8
9
10
    int __init btmrvl_sdio_init_module(void)
    |
    |
    -->sdio_register_driver(&bt_mrvl_sdio) != 0)
      |
      |
      -->btmrvl_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
          |
          |
          -->sdio_claim_irq(func, btmrvl_sdio_interrupt);
In this ISR, wake up main data pocessing thread to read data from card.
1
2
3
4
5
6
7
    void btmrvl_sdio_interrupt(struct sdio_func *func)
    |
    |
    -->btmrvl_interrupt(priv);
      |
      |
      -->wake_up_interruptible(&priv->main_thread.wait_q);

Source Code:

Note: How to load firmware for a SDIO device

1. Disable interrupt from this SDIO device.
1
2
3
4
5
6
sdio_claim_host(card->func);
btmrvl_sdio_disable_host_int(card);
  host_int_mask = sdio_readb(card->func, card->reg->host_int_mask, &ret);
  host_int_mask &= ~HIM_DISABLE;
  sdio_writeb(card->func, host_int_mask, card->reg->host_int_mask, &ret);
sdio_release_host(card->func);
2. Get and Write firmware.
1
2
3
4
5
6
7
8
9
10
11
12
13
sdio_claim_host(card->func);
  //Get firmware from user space.
  request_firmware(&fw_firmware, card->firmware,//name of firmware file, = "mrvl/sd8787_uapsta.bin",
          &card->func->dev);
  //Write firmware into SDIO device, check firmware status.
  tmpfwbufsz = ALIGN_SZ(BTM_UPLD_SIZE, BTSDIO_DMA_ALIGN);
  fwbuf = (u8 *) ALIGN_ADDR(tmpfwbuf, BTSDIO_DMA_ALIGN);
  memcpy(fwbuf, &firmware[offset], txlen);
  sdio_writesb(card->func, card->ioport, fwbuf,tx_blocks * blksz_dl);
  
  //Release firmware related resource in kernel.
  release_firmware(fw_firmware);
sdio_release_host(card->func);

Alternatively, you can use request_firmware_nowait() if current thread is not allowed to sleep for a long time.

3. Enable SDIO device interrupt.
1
btmrvl_sdio_enable_host_int(card);

Reference:

$Jellybean/system/core/init/devices.c
1
2
3
4
5
#define SYSFS_PREFIX    "/sys"
#define FIRMWARE_DIR1   "/etc/firmware"
asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
asprintf(&file1, FIRMWARE_DIR1"/%s", uevent->firmware);
fw_fd = open(file1, O_RDONLY);

Footnote: sdio_claim_host(card->func)

  1. card->func means an independent function residues in same card (there maybe different functions implemented in same card simultaneously, like BT and Wifi in MRVL 8787 module. The device field in struct sdio_device_id is used as function id to distinguish these functions, in this driver, the device field in driver is 0x911B for MRVL_BT_SD8787, it reflected as: 0x911b in /sys/class/mmc_host/mmc1/mmc1\:0001/mmc1\:0001\:3/device.
  2. sdio_claim_host() is acting like a lock, I guess this will serialize access to same SD device between different functions, and also between different threads inside same function.

Refresh Memo: How a Process Accesses Physical Memory

| Comments

Scenario: a process acquired a new block of memory, then try to access part of this block:

1
2
3
        unsigned char * buf = (unsigned char *)malloc(1024);
        if (buf != NULL)
                *(buf + 3) = 0xfe;

What happens in malloc():

  1. stdlib will try to handle it internally, if can’t satisfy this request, then,
  2. Call sbrk() to let kernel enlarge current process’s heap space (by adjust process’s VMAs).

What happens next when try to access the memory:

  1. CPU’s MMU use this virtual address to look up in current CPU’s TLB, not found.
  2. Then MMU switch to look up this address in process’s Page Table, try to do the virtual-to-physical address translation.
  3. Step 2 will fail, a Page Fault exception happens.
    • In Page Fault exception handler, check if current process has write permission to this address, that’s done by check process’s VMA list.
    • Allocate a physical page, update process’s Page Table for this page.
  4. After the exception handler returned, and the process get scheduled to execute again, it will retry the instruction that caused the Page Fault, this time will get correct physical address pointed to the new page. Update TLB entry for this new map.
  5. Select proper line in Cache for this physical address, write the new value to Cache, then hardware will write back this new value from Cache to real memory at proper time.

Read e-EDID With Clock Stretching

| Comments

Read e-EDID with master clock stretching

In E-DDC, a special I²C addressing scheme was introduced, in which multiple 256-byte segments could be selected. To do this, a single 8-bit segment index is passed to the display via the I²C address 30h. (Because this access is always a write, the first I²C octet will always be 60h.).

In my MHL project, this first write will always get an I2C NO_ACK error(E-DDC need tolerate with NO_ACK error for this write, to be back-compatible with legacy DDC, so we are using GPIO to simulate I2C, instead of using standard I2C controller).

From below signal, we found the ACK actually came, but master did not acquire it at that time, that’s exactly what clock stretching targeted at.

Add clock stretching logic in our GPIO simulation function, then can successfully read all e-EDID segments.

Sending a Signal From Linux Kernel

| Comments

Send SIGUSR1 from kernel

I want to use a signal to inform an asynchronous event from one kernel module to a user space application.

To achieve this, add below code in kernel driver:

patch on kernel module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
+#include <linux/sched.h>
+#include <asm/siginfo.h>
+#include <linux/pid_namespace.h>
+#include <linux/pid.h>

typedef enum
 {
+       THIS_MODULE_IOCTL_SET_OWNER = 0x111,
 }MODULE_IOCTL_CMD;


+static int owner = 0;
+static struct task_struct * current_task;

+static void send_signal(int sig_num)
+{
+       if (owner == 0)
+               return;
+       printk("%s,%d.sending to owner %d\n",__func__, __LINE__, owner);
+       struct siginfo info;
+       memset(&info, 0, sizeof(struct siginfo));
+       info.si_signo = sig_num;
+       info.si_code = 0;
+       info.si_int = 1234;
+       if (current_task == NULL){
+               rcu_read_lock();
+               current_task = pid_task(find_vpid(owner), PIDTYPE_PID);
+               rcu_read_unlock();
+       }
+       int ret = send_sig_info(sig_num, &info, current_task);
+       if (ret < 0) {
+               printk("error sending signal\n");
+       }
+}
+
 static int device_event_handler(struct snd_pcm_substream *substream, int cmd)
 {
      if (cmd == INTRESTED_EVENT) {
+                       send_signal(SIGUSR1);



@@ -325,6 +352,13 @@ static long device_ioctl(struct file *file,
+       case THIS_MODULE_IOCTL_SET_OWNER:
+               printk("%s, owner pid %d\n",__func__, owner);
+               if(copy_from_user(&owner, (int *)arg, sizeof(int))){
+                       ret = -EFAULT;
+               }
+               current_task = NULL;
+               break;

+MODULE_LICENSE("GPL");

In user space application, set its pid to kernel module, then listen on the signal.

patch on user application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+static sem_t event_sem;
+static volatile sig_atomic_t intrested_event = 0;
+
+void sig_handler_event1(int sig)
+{
+       interested_event = 1;
+       sem_post(&event_sem);
+}

+static void * event_handler_thread_func()
+{
+        while(1){
+                sem_wait(&event_sem);
+                if (intrested_event){
+                        LOGD("%s,%d, received intrested_event signal.\n",__func__, __LINE__);
+                        intrested_event = 0;
+                }
+        }
+        pthread_exit(NULL);
+}

int main(int argc, char *argv[])
+               pthread_t event_thread;
+               if (pthread_create(&event_thread, NULL, event_handler_thread_func, NULL) != 0){
                        printf("Thread create failed%s.\n", strerror(errno));
+                  exit(1);
+               }
+               sem_init(&event_sem, 0, 0);

+               struct sigaction usr_action;
+               sigset_t block_mask;
+               sigfillset (&block_mask);
+               usr_action.sa_handler = sig_handler_event1;
+               usr_action.sa_mask = block_mask;//block all signal inside signal handler.
+               usr_action.sa_flags = SA_NODEFER;//do not block SIGUSR1 within sig_handler_int.
+               sigaction (SIGUSR1, &usr_action, NULL);
+               int fd = open("/dev/target_device_name", O_RDWR);
+               int my_pid = getpid();
+               ioctl(fd, 0x111, &my_pid);
+               close(fd);

A signal could interrupt below primitive calls: close, fcntl (operation F_SETLK), open, read, recv, recvfrom, select, send, sendto, tcdrain, waitpid, wait, and write, POXIS and BSD will handle this situation differently. POXIS will fail these primitive call with EINTR, caller need to use macro TEMP_FAILURE_RETRY (expression) to retry. Programmer need to take care of this issue.

Reference:

Sending Signal Across Processes

In receiver process, create a share memory, write receiver’s pid to it, then wait for the signal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
+#include  <signal.h>
+#include  <sys/ipc.h>
+#include  <sys/shm.h>
+#include <pthread.h>
+#include <semaphore.h>

+#define MY_IPC_KEY_PATH "/data/misc/bluedroid"

+static sem_t event_sem;
+static volatile sig_atomic_t intrested_event = 0;
+
+static void sig_handler_int(int sig)
+{
+       interested_event = 1;
+       sem_post(&event_sem);
+}
+
+static void wait_for_signal(void)
+{
+       pid_t pid = getpid();
+       key_t my_ipc_key   = ftok(MY_IPC_KEY_PATH, 's');
+       int share_mem_id   = shmget(my_ipc_key, sizeof(pid_t), IPC_CREAT | 0666);
+       pid_t *share_mem  = (pid_t *) shmat(share_mem_id, NULL, 0);
+       *share_mem = pid;
+
+       printf("%s,%d, set my pid %d\n", __func__, __LINE__, pid);
+
+       struct sigaction usr_action;
+       sigset_t block_mask;
+       sigfillset (&block_mask);
+       usr_action.sa_handler = sig_handler_int;
+       usr_action.sa_mask = block_mask;//block all signal inside signal handler.
+       usr_action.sa_flags = SA_NODEFER;//do not block SIGUSR1 within sig_handler_int.
+       sigaction (SIGINT, &usr_action, NULL);
+
+       sem_init(&event_sem, 0, 0);
+       while(intrested_event == 0){
+               sem_wait(&event_sem);
+               if (intrested_event){
+                       printf("%s,%d, received intrested_event signal.\n",__func__, __LINE__);
+               }
+       }
+       intrested_event = 0;
+       shmdt(share_mem);
+       shmctl(share_mem_id, IPC_RMID, NULL);
+}

int receiver_process(int argc, char *argv[])
{

+    wait_for_signal();

In sender process, acquire the receiver process’spid through the share memory object, then send the signal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
+#include  <signal.h>
+#include  <sys/ipc.h>
+#include  <sys/shm.h>

+#define MY_IPC_KEY_PATH "/data/misc/bluedroid"

+static void wakeup_receiver(void)
+{
+       key_t my_ipc_key;
+       int share_mem_id;
+       pid_t *share_mem;
+       pid_t pid;
+
+       my_ipc_key   = ftok(MY_IPC_KEY_PATH, 's');
+       share_mem_id   = shmget(my_ipc_key, sizeof(pid_t), 0666);
+       share_mem  = (pid_t *) shmat(share_mem_id, NULL, 0);
+       pid     = *share_mem;
+       shmdt(share_mem);
+
+       if (pid > 0){
+               int ret = sigquue(pid, SIGINT, 0);
                if (ret != 0 ){
                        printf("send signal failed %s.\n", strerror(errno));
                        return;
                }
+               printf("%s,%d, send signale to pid %d\n", __func__, __LINE__, pid);
+       }
+       else{
+               printf("%s,%d, pid %d not ready\n", __func__, __LINE__, pid);
+       }
+
+}
void sender(void)
{
+    wakeup_receiver();
}

A note: shmget() is not available in Android, so this is not a valid IPC for Android.

Reference:

http://www.csl.mtu.edu/cs4411.ck/www/NOTES/signal/kill.html

Footnote: signal lost

1 unreliable signal: earlier version of UNIX signal is unreliable

2 real-time signal:

POSIX added signals, and

3 signal lost:

. when signal handler is running, blocked signals is “lost” (?!) . when signal handler is running, the same signal is blocked by default, add SA_NODEFER in sigaction.sa_flags unblock it.

Resource Leakage in Android Java Apk

| Comments

Bug: Keep on resetting BT adapter cause Android crash

Bug description: In GTV/Honeycomb’s System Setting UI, keep on turning on/off Bluetooth for about 270 times, Android will crash, and UI restart from blackscreen.

My analyze: First thing is to located where Android system crashed from logcat, there must be something related to zygote, then I found this:

1
03-08 20:15:00.026 I/Zygote  ( 3749): Exit zygote because system server (3780) has terminated

Above that I found a fatal error message:

1
03-08 20:14:59.226 F/Looper  ( 3780): Could not create wake pipe.  errno=24

then all system service died like below:

1
03-08 20:14:59.316 I/ServiceManager(  690): service 'usagestats' died

Tracing down the fatal error message in Android XRef, the error occurs when creating a new Looper instance, in Honeycomb/frameworks/base/libs/utils/Looper.cpp, where 3 new pips need to be created for one new Looper object. So I guess the isssue is kind of file/socket/pipe handle leakage issue, to verify this, compare “lsof” result after turn on/off Bluetooth once, I can see there’s 3 newly opened pipes in system_service everytime I performed a turn on/off Bluetooth. Then I found from website that someone reported similar issue on Android Bug List

1
2
3
4
5
6
7
8
/ # lsof |grep system_se >/data/debug/after_turn_off_bt.txt
/ # hciconfig hci0 up; sleep 5; hciconfig hci0 down
/ # lsof |grep system_se >/data/debug/after_turn_off_bt_again.txt
/ # busybox diff  /data/debug/after_turn_off_bt.txt /data/debug/after_turn_off_bt_again.txt
 system_se   887        ???  224       ???                ???       ???        ??? /dev/null
+system_se   887        ???  225       ???                ???       ???        ??? pipe:[7641]
+system_se   887        ???  227       ???                ???       ???        ??? pipe:[7641]
+system_se   887        ???  228       ???                ???       ???        ??? anon_inode:[eventpoll]

The symptom is quite clear now, but how to locate leakage position in Android?

The first clue is that Looper object is used for Android Handler object, it’s hard to dump call stack in Android native C++ service, but it’s easy to do that in Java. So I added a dump stack in Handler’s construction method, caught below message when I turn on Bluetooth:

1
2
3
4
5
6
7
8
9
10
03-11 05:14:07.806 D/Handler (  898):android.os.Handler
03-11 05:14:07.806 D/Handler (  898):com.android.internal.util.HierarchicalStateMachine$HsmHandler
03-11 05:14:07.806 D/Handler (  898):com.android.internal.util.HierarchicalStateMachine$HsmHandler
03-11 05:14:07.806 D/Handler (  898):com.android.internal.util.HierarchicalStateMachine
03-11 05:14:07.806 D/Handler (  898):com.android.internal.util.HierarchicalStateMachine
03-11 05:14:07.806 D/Handler (  898):android.bluetooth.BluetoothDeviceProfileState
03-11 05:14:07.806 D/Handler (  898):android.server.BluetoothService
03-11 05:14:07.806 D/Handler (  898):android.server.BluetoothService
03-11 05:14:07.806 D/Handler (  898):android.server.BluetoothService
03-11 05:14:07.806 D/Handler (  898):android.server.BluetoothService$EnableThread

After doing some source code search, I reached the place in BluetoothService.java when problem occurs: When turn off BT, BluetoothService will do:

1
2
3
2146     void removeProfileState(String address) {
2147         mDeviceProfileState.remove(address); //Comment out this line so BluetoothService will not create a new BluetoothDeviceProfileState every time BT reset.
2148     }

Then when turn on BT again, it will do:

1
2
3
4
5
2136     BluetoothDeviceProfileState addProfileState(String address) {
2137         BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
2138         if (state != null) return state;
2140         state = new BluetoothDeviceProfileState(mContext, address, this, mA2dpService);
2144     }

Problem here is, we only need do a reset when turn off BT, then we can reuse the BluetoothDeviceProfileState object after we turn it on again, but Android removed it instead, then need to create a new BluetoothDeviceProfileState object next time. If we did not remove the object every time we turn off BT, then the issue disappeared.

My comments: For Java class who claims system resource in its construction method, we must declare corresponding interface to release them, GC can not do this job for us.