This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Correct way to implement a user defined hci command.

I'm trying to create a user defined HCI command for nrf52840 over the Zephyr APIs to be used against Bluez stack on Linux.

I've enabled the CONFIG_BT_HCI_VS_EVT_USER=y.

But I'm unable to use, bt_hci_register_vnd_evt_cb, as the the compiler complains it's undefined.

Found the bt_hci_raw_cmd_ext_register to register a callback, which seems to work. ie. I was able to register a OPCODE under BT_OGF_VS, and the callback works correctly except it's not clear how I can return a complex result back to the host.

Below is the callback I used for testing purpose.

uint8_t hci_cb(struct net_buf_simple *buf){

LOG_DBG("USER_CALLED");
return 0;

}


TIA for any pointers.

Thanks.
Parents
  • I took a look at the Zephyr BLE controller and how data was returned back to the host, this is what I figured out:

    (Be aware that I'm not very familiar with the BLE stack, so I may have misunderstood/overlooked something)

    • bt_hci_cmd_send_sync(uint16_t opcode, struct net_buf *buf, struct net_buf **rsp) is called from the host
      • The first argument specifies the opcode
      • The last argument (rsp) is where the response from the controller will be located
      • The function can be found in zephyr\subsys\bluetooth\host\hci_core.c
    • This triggers cmd_handle() in the BLE controller | zephyr\subsys\bluetooth\controller\hci\hci_driver.c
    • Then hci_cmd_handle() will get called| zephyr\subsys\bluetooth\controller\hci\hci.c
    • Then it goes into a switch case in hci_cmd_handle() of the OGF of the opcode

    switch (BT_OGF(_opcode)) {
        .....
        case BT_OGF_STATUS:
            printk("[contr]Handle cmd packet 7\n");
            err = status_cmd_handle(ocf, cmd, &evt);
            break;
        case BT_OGF_LE:
            printk("[contr]Handle cmd packet 8\n");
            err = controller_cmd_handle(ocf, cmd, &evt, node_rx);
            break;

    • E.g. if it's BT_OGF_LE it will run controller_cmd_handle() |  zephyr\subsys\bluetooth\controller\hci\hci.c
    • Then it will run a switch case on the OCF of the opcode

    switch (ocf) {
        .....
        case BT_OCF(BT_HCI_OP_LE_READ_LOCAL_FEATURES):
            le_read_local_features(cmd, evt);
            break;
    
        case BT_OCF(BT_HCI_OP_LE_SET_RANDOM_ADDRESS):
            le_set_random_address(cmd, evt);
            break;

    • E.g. if it is BT_HCI_OP_LE_SET_RANDOM_ADDRESS, the function le_set_random_address() will run (zephyr\subsys\bluetooth\controller\hci\hci.c), which looks like this:

            static void le_set_random_address(struct net_buf *buf, struct net_buf **evt)
            {
                struct bt_hci_cp_le_set_random_address *cmd = (void *)buf->data;
                uint8_t status;
    
                status = ll_addr_set(1, &cmd->bdaddr.val[0]);
    
                *evt = cmd_complete_status(status);
            }

    • As you can see it runs the appropriate low level function ll_addr_set() and the event to get returned (*evt) will be generated by cmd_complete_status() (zephyr\subsys\bluetooth\controller\hci\hci.c)
    • When the above functions are done executing it will continue the exectution in hci_cmd_handle(), and at the end of this function it will return the event set by le_set_random_address() 
    • The function cmd_handle() (zephyr\subsys\bluetooth\controller\hci\hci_driver.c) will then receive this event and call bt_recv_prio(evt):
      • evt = hci_cmd_handle(buf, (void **) &node_rx);
        ....
        bt_recv_prio(evt);
      • Now we go over to the host side, since bt_recv_prio is defined in zephyr\subsys\bluetooth\host\hci_core.c
    • hci_event_prio(buf) (zephyr\subsys\bluetooth\host\hci_core.c) will get called (buf is the evt seen earlier. The name has just changed)
    • Then handle_event(..,,prio_events..) is called, where prio_events contains an array of handlers:

    static const struct event_handler prio_events[] = {
    	EVENT_HANDLER(BT_HCI_EVT_CMD_COMPLETE, hci_cmd_complete,
    		      sizeof(struct bt_hci_evt_cmd_complete)),
    	EVENT_HANDLER(BT_HCI_EVT_CMD_STATUS, hci_cmd_status,
    		      sizeof(struct bt_hci_evt_cmd_status)),
    		      .....

    • handle_event() will then get triggered, go through the handlers, and find the one where handler->event != event
      • Don't mix this event with the buf/evt seen earlier
    • E.g. if cmd complete was a match, the function hci_cmd_complete() will get called
    • (Remember that bt_hci_cmd_send_sync() was called in the initially by the host, and it will wait at k_sem_take() until it get woken up)
    • The function hci_cmd_complete() will call hci_cmd_done(), which will trigger k_sem_give(), which will wake up bt_hci_cmd_send_sync() 
    • bt_hci_cmd_send_sync() will then set *rsp = buf
    • If bt_hci_cmd_send_sync() was called through e.g. if common_init()-->bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_FEATURES, NULL, &rsp); it can use rps in the following manner:
      • bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_FEATURES, NULL, &rsp);
      • read_local_features_complete(rsp);
      • net_buf_unref(rsp);

    Hopefully this makes you understand better how it works

    Best regards,

    Simon

  • Hi Simon,

    Thanks a lot for this comprehensive answer.
    I'll follow it and see if I can implement what I wanted.

Reply Children
No Data
Related