diff mbox series

[RFC,BlueZ,02/10] emulator/hciemu: Create ELL based version of hciemu

Message ID 20201107070312.8561-3-inga.stotland@intel.com (mailing list archive)
State New, archived
Headers show
Series Convert tools to use ELL library | expand

Commit Message

Stotland, Inga Nov. 7, 2020, 7:03 a.m. UTC
This adds a separate implementtion of hciemu code, hciemu-ell.c,
that uses ELL library primitives.
---
 emulator/hciemu-ell.c | 564 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 564 insertions(+)
 create mode 100644 emulator/hciemu-ell.c

Comments

Luiz Augusto von Dentz Nov. 9, 2020, 6:04 p.m. UTC | #1
Hi Inga,

On Fri, Nov 6, 2020 at 11:06 PM Inga Stotland <inga.stotland@intel.com> wrote:
>
> This adds a separate implementtion of hciemu code, hciemu-ell.c,
> that uses ELL library primitives.

I wonder if this should really be separated like this or we just make
use of struct io instead of l_io, that way we don't need to keep
duplicating things on the emulator, the other option would be to drop
entirely the glib version but I guess you haven't done that because
there are quite a few dependencies to get rid in order to run the
tester with ell.

> ---
>  emulator/hciemu-ell.c | 564 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 564 insertions(+)
>  create mode 100644 emulator/hciemu-ell.c
>
> diff --git a/emulator/hciemu-ell.c b/emulator/hciemu-ell.c
> new file mode 100644
> index 000000000..40342e99b
> --- /dev/null
> +++ b/emulator/hciemu-ell.c
> @@ -0,0 +1,564 @@
> +// SPDX-License-Identifier: LGPL-2.1-or-later
> +/*
> + *
> + *  BlueZ - Bluetooth protocol stack for Linux
> + *
> + *  Copyright (C) 2012-2014, 2020  Intel Corporation. All rights reserved.
> + *
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdbool.h>
> +#include <errno.h>
> +#include <sys/socket.h>
> +
> +#include <ell/ell.h>
> +
> +#include "lib/bluetooth.h"
> +#include "lib/hci.h"
> +
> +#include "monitor/bt.h"
> +#include "emulator/btdev.h"
> +#include "emulator/bthost.h"
> +#include "src/shared/util.h"
> +#include "src/shared/queue.h"
> +#include "emulator/hciemu.h"
> +
> +struct hciemu {
> +       int ref_count;
> +       enum btdev_type btdev_type;
> +       struct bthost *host_stack;
> +       struct btdev *master_dev;
> +       struct btdev *client_dev;
> +       struct l_io *host_io;
> +       struct l_io *master_io;
> +       struct l_io *client_io;
> +       struct queue *post_command_hooks;
> +       char bdaddr_str[18];
> +
> +       hciemu_debug_func_t debug_callback;
> +       hciemu_destroy_func_t debug_destroy;
> +       void *debug_data;
> +};
> +
> +struct hciemu_command_hook {
> +       hciemu_command_func_t function;
> +       void *user_data;
> +};
> +
> +static void destroy_command_hook(void *data)
> +{
> +       struct hciemu_command_hook *hook = data;
> +
> +       free(hook);
> +}
> +
> +struct run_data {
> +       uint16_t opcode;
> +       const void *data;
> +       uint8_t len;
> +};
> +
> +static void run_command_hook(void *data, void *user_data)
> +{
> +       struct hciemu_command_hook *hook = data;
> +       struct run_data *run_data = user_data;
> +
> +       if (hook->function)
> +               hook->function(run_data->opcode, run_data->data,
> +                                       run_data->len, hook->user_data);
> +}
> +
> +static void master_command_callback(uint16_t opcode,
> +                               const void *data, uint8_t len,
> +                               btdev_callback callback, void *user_data)
> +{
> +       struct hciemu *hciemu = user_data;
> +       struct run_data run_data = { .opcode = opcode,
> +                                               .data = data, .len = len };
> +
> +       btdev_command_default(callback);
> +
> +       queue_foreach(hciemu->post_command_hooks, run_command_hook, &run_data);
> +}
> +
> +static void client_command_callback(uint16_t opcode,
> +                               const void *data, uint8_t len,
> +                               btdev_callback callback, void *user_data)
> +{
> +       btdev_command_default(callback);
> +}
> +
> +static void writev_callback(const struct iovec *iov, int iovlen,
> +                                                               void *user_data)
> +{
> +       struct l_io *io = user_data;
> +       ssize_t written;
> +       int fd;
> +
> +       fd = l_io_get_fd(io);
> +
> +       written = writev(fd, iov, iovlen);
> +       if (written < 0)
> +               return;
> +}
> +
> +static bool receive_bthost(struct l_io *io, void *user_data)
> +{
> +       struct bthost *bthost = user_data;
> +       unsigned char buf[4096];
> +       ssize_t len;
> +       int fd;
> +
> +       fd = l_io_get_fd(io);
> +
> +       len = read(fd, buf, sizeof(buf));
> +       if (len < 0)
> +               return false;
> +
> +       bthost_receive_h4(bthost, buf, len);
> +
> +       return true;
> +}
> +
> +static struct l_io *create_io_bthost(int fd, struct bthost *bthost)
> +{
> +       struct l_io *io;
> +
> +       io = l_io_new(fd);
> +
> +       l_io_set_close_on_destroy(io, true);
> +
> +       bthost_set_send_handler(bthost, writev_callback, io);
> +
> +       l_io_set_read_handler(io, receive_bthost, bthost, NULL);
> +
> +       return io;
> +}
> +
> +static bool receive_btdev(struct l_io *io, void *user_data)
> +
> +{
> +       struct btdev *btdev = user_data;
> +       unsigned char buf[4096];
> +       ssize_t len;
> +       int fd;
> +
> +       fd = l_io_get_fd(io);
> +
> +       len = read(fd, buf, sizeof(buf));
> +       if (len < 0) {
> +               if (errno == EAGAIN || errno == EINTR)
> +                       return true;
> +
> +               return false;
> +       }
> +
> +       if (len < 1)
> +               return false;
> +
> +       switch (buf[0]) {
> +       case BT_H4_CMD_PKT:
> +       case BT_H4_ACL_PKT:
> +       case BT_H4_SCO_PKT:
> +               btdev_receive_h4(btdev, buf, len);
> +               break;
> +       }
> +
> +       return true;
> +}
> +
> +static struct l_io *create_io_btdev(int fd, struct btdev *btdev)
> +{
> +       struct l_io *io;
> +
> +       io = l_io_new(fd);
> +
> +       l_io_set_close_on_destroy(io, true);
> +
> +       btdev_set_send_handler(btdev, writev_callback, io);
> +
> +       l_io_set_read_handler(io, receive_btdev, btdev, NULL);
> +
> +       return io;
> +}
> +
> +static bool create_vhci(struct hciemu *hciemu)
> +{
> +       struct btdev *btdev;
> +       uint8_t create_req[2];
> +       ssize_t written;
> +       int fd;
> +
> +       btdev = btdev_create(hciemu->btdev_type, 0x00);
> +       if (!btdev)
> +               return false;
> +
> +       btdev_set_command_handler(btdev, master_command_callback, hciemu);
> +
> +       fd = open("/dev/vhci", O_RDWR | O_NONBLOCK | O_CLOEXEC);
> +       if (fd < 0) {
> +               perror("Opening /dev/vhci failed");
> +               btdev_destroy(btdev);
> +               return false;
> +       }
> +
> +       create_req[0] = HCI_VENDOR_PKT;
> +       create_req[1] = HCI_PRIMARY;
> +
> +       written = write(fd, create_req, sizeof(create_req));
> +       if (written < 0) {
> +               close(fd);
> +               btdev_destroy(btdev);
> +               return false;
> +       }
> +
> +       hciemu->master_dev = btdev;
> +
> +       hciemu->master_io = create_io_btdev(fd, btdev);
> +
> +       return true;
> +}
> +
> +struct bthost *hciemu_client_get_host(struct hciemu *hciemu)
> +{
> +       if (!hciemu)
> +               return NULL;
> +
> +       return hciemu->host_stack;
> +}
> +
> +static bool create_stack(struct hciemu *hciemu)
> +{
> +       struct btdev *btdev;
> +       struct bthost *bthost;
> +       int sv[2];
> +
> +       btdev = btdev_create(hciemu->btdev_type, 0x00);
> +       if (!btdev)
> +               return false;
> +
> +       bthost = bthost_create();
> +       if (!bthost) {
> +               btdev_destroy(btdev);
> +               return false;
> +       }
> +
> +       btdev_set_command_handler(btdev, client_command_callback, hciemu);
> +
> +       if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC,
> +                                                               0, sv) < 0) {
> +               bthost_destroy(bthost);
> +               btdev_destroy(btdev);
> +               return false;
> +       }
> +
> +       hciemu->client_dev = btdev;
> +       hciemu->host_stack = bthost;
> +
> +       hciemu->client_io = create_io_btdev(sv[0], btdev);
> +       hciemu->host_io = create_io_bthost(sv[1], bthost);
> +
> +       return true;
> +}
> +
> +static void start_stack(void *user_data)
> +{
> +       struct hciemu *hciemu = user_data;
> +
> +       bthost_start(hciemu->host_stack);
> +}
> +
> +struct hciemu *hciemu_new(enum hciemu_type type)
> +{
> +       struct hciemu *hciemu;
> +
> +       hciemu = new0(struct hciemu, 1);
> +       if (!hciemu)
> +               return NULL;
> +
> +       switch (type) {
> +       case HCIEMU_TYPE_BREDRLE:
> +               hciemu->btdev_type = BTDEV_TYPE_BREDRLE;
> +               break;
> +       case HCIEMU_TYPE_BREDR:
> +               hciemu->btdev_type = BTDEV_TYPE_BREDR;
> +               break;
> +       case HCIEMU_TYPE_LE:
> +               hciemu->btdev_type = BTDEV_TYPE_LE;
> +               break;
> +       case HCIEMU_TYPE_LEGACY:
> +               hciemu->btdev_type = BTDEV_TYPE_BREDR20;
> +               break;
> +       case HCIEMU_TYPE_BREDRLE50:
> +               hciemu->btdev_type = BTDEV_TYPE_BREDRLE50;
> +               break;
> +       case HCIEMU_TYPE_BREDRLE52:
> +               hciemu->btdev_type = BTDEV_TYPE_BREDRLE52;
> +               break;
> +       default:
> +               return NULL;
> +       }
> +
> +       hciemu->post_command_hooks = queue_new();
> +       if (!hciemu->post_command_hooks) {
> +               free(hciemu);
> +               return NULL;
> +       }
> +
> +       if (!create_vhci(hciemu)) {
> +               queue_destroy(hciemu->post_command_hooks, NULL);
> +               free(hciemu);
> +               return NULL;
> +       }
> +
> +       if (!create_stack(hciemu)) {
> +               l_io_destroy(hciemu->master_io);
> +               btdev_destroy(hciemu->master_dev);
> +               queue_destroy(hciemu->post_command_hooks, NULL);
> +               free(hciemu);
> +               return NULL;
> +       }
> +
> +       l_idle_oneshot(start_stack, hciemu, NULL);
> +
> +       return hciemu_ref(hciemu);
> +}
> +
> +struct hciemu *hciemu_ref(struct hciemu *hciemu)
> +{
> +       if (!hciemu)
> +               return NULL;
> +
> +       __sync_fetch_and_add(&hciemu->ref_count, 1);
> +
> +       return hciemu;
> +}
> +
> +void hciemu_unref(struct hciemu *hciemu)
> +{
> +       if (!hciemu)
> +               return;
> +
> +       if (__sync_sub_and_fetch(&hciemu->ref_count, 1))
> +               return;
> +
> +       queue_destroy(hciemu->post_command_hooks, destroy_command_hook);
> +
> +       l_io_destroy(hciemu->host_io);
> +       l_io_destroy(hciemu->client_io);
> +       l_io_destroy(hciemu->master_io);
> +
> +       bthost_destroy(hciemu->host_stack);
> +       btdev_destroy(hciemu->client_dev);
> +       btdev_destroy(hciemu->master_dev);
> +
> +       free(hciemu);
> +}
> +
> +static void bthost_debug(const char *str, void *user_data)
> +{
> +       struct hciemu *hciemu = user_data;
> +
> +       util_debug(hciemu->debug_callback, hciemu->debug_data,
> +                                       "bthost: %s", str);
> +}
> +
> +static void btdev_master_debug(const char *str, void *user_data)
> +{
> +       struct hciemu *hciemu = user_data;
> +
> +       util_debug(hciemu->debug_callback, hciemu->debug_data,
> +                                       "btdev: %s", str);
> +}
> +
> +static void btdev_client_debug(const char *str, void *user_data)
> +{
> +       struct hciemu *hciemu = user_data;
> +
> +       util_debug(hciemu->debug_callback, hciemu->debug_data,
> +                                       "btdev[bthost]: %s", str);
> +}
> +
> +bool hciemu_set_debug(struct hciemu *hciemu, hciemu_debug_func_t callback,
> +                       void *user_data, hciemu_destroy_func_t destroy)
> +{
> +       if (!hciemu)
> +               return false;
> +
> +       if (hciemu->debug_destroy)
> +               hciemu->debug_destroy(hciemu->debug_data);
> +
> +       hciemu->debug_callback = callback;
> +       hciemu->debug_destroy = destroy;
> +       hciemu->debug_data = user_data;
> +
> +       btdev_set_debug(hciemu->master_dev, btdev_master_debug, hciemu, NULL);
> +       btdev_set_debug(hciemu->client_dev, btdev_client_debug, hciemu, NULL);
> +       bthost_set_debug(hciemu->host_stack, bthost_debug, hciemu, NULL);
> +
> +       return true;
> +}
> +
> +const char *hciemu_get_address(struct hciemu *hciemu)
> +{
> +       const uint8_t *addr;
> +
> +       if (!hciemu || !hciemu->master_dev)
> +               return NULL;
> +
> +       addr = btdev_get_bdaddr(hciemu->master_dev);
> +       sprintf(hciemu->bdaddr_str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
> +                       addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
> +       return hciemu->bdaddr_str;
> +}
> +
> +uint8_t *hciemu_get_features(struct hciemu *hciemu)
> +{
> +       if (!hciemu || !hciemu->master_dev)
> +               return NULL;
> +
> +       return btdev_get_features(hciemu->master_dev);
> +}
> +
> +const uint8_t *hciemu_get_master_bdaddr(struct hciemu *hciemu)
> +{
> +       if (!hciemu || !hciemu->master_dev)
> +               return NULL;
> +
> +       return btdev_get_bdaddr(hciemu->master_dev);
> +}
> +
> +const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu)
> +{
> +       if (!hciemu || !hciemu->client_dev)
> +               return NULL;
> +
> +       return btdev_get_bdaddr(hciemu->client_dev);
> +}
> +
> +uint8_t hciemu_get_master_scan_enable(struct hciemu *hciemu)
> +{
> +       if (!hciemu || !hciemu->master_dev)
> +               return 0;
> +
> +       return btdev_get_scan_enable(hciemu->master_dev);
> +}
> +
> +uint8_t hciemu_get_master_le_scan_enable(struct hciemu *hciemu)
> +{
> +       if (!hciemu || !hciemu->master_dev)
> +               return 0;
> +
> +       return btdev_get_le_scan_enable(hciemu->master_dev);
> +}
> +
> +void hciemu_set_master_le_states(struct hciemu *hciemu,
> +                                               const uint8_t *le_states)
> +{
> +       if (!hciemu || !hciemu->master_dev)
> +               return;
> +
> +       btdev_set_le_states(hciemu->master_dev, le_states);
> +}
> +
> +bool hciemu_add_master_post_command_hook(struct hciemu *hciemu,
> +                       hciemu_command_func_t function, void *user_data)
> +{
> +       struct hciemu_command_hook *hook;
> +
> +       if (!hciemu)
> +               return false;
> +
> +       hook = new0(struct hciemu_command_hook, 1);
> +       if (!hook)
> +               return false;
> +
> +       hook->function = function;
> +       hook->user_data = user_data;
> +
> +       if (!queue_push_tail(hciemu->post_command_hooks, hook)) {
> +               free(hook);
> +               return false;
> +       }
> +
> +       return true;
> +}
> +
> +bool hciemu_clear_master_post_command_hooks(struct hciemu *hciemu)
> +{
> +       if (!hciemu)
> +               return false;
> +
> +       queue_remove_all(hciemu->post_command_hooks,
> +                                       NULL, NULL, destroy_command_hook);
> +       return true;
> +}
> +
> +int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type,
> +                               uint16_t opcode, hciemu_hook_func_t function,
> +                               void *user_data)
> +{
> +       enum btdev_hook_type hook_type;
> +
> +       if (!hciemu)
> +               return -1;
> +
> +       switch (type) {
> +       case HCIEMU_HOOK_PRE_CMD:
> +               hook_type = BTDEV_HOOK_PRE_CMD;
> +               break;
> +       case HCIEMU_HOOK_POST_CMD:
> +               hook_type = BTDEV_HOOK_POST_CMD;
> +               break;
> +       case HCIEMU_HOOK_PRE_EVT:
> +               hook_type = BTDEV_HOOK_PRE_EVT;
> +               break;
> +       case HCIEMU_HOOK_POST_EVT:
> +               hook_type = BTDEV_HOOK_POST_EVT;
> +               break;
> +       default:
> +               return -1;
> +       }
> +
> +       return btdev_add_hook(hciemu->master_dev, hook_type, opcode, function,
> +                                                               user_data);
> +}
> +
> +bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type,
> +                                                               uint16_t opcode)
> +{
> +       enum btdev_hook_type hook_type;
> +
> +       if (!hciemu)
> +               return false;
> +
> +       switch (type) {
> +       case HCIEMU_HOOK_PRE_CMD:
> +               hook_type = BTDEV_HOOK_PRE_CMD;
> +               break;
> +       case HCIEMU_HOOK_POST_CMD:
> +               hook_type = BTDEV_HOOK_POST_CMD;
> +               break;
> +       case HCIEMU_HOOK_PRE_EVT:
> +               hook_type = BTDEV_HOOK_PRE_EVT;
> +               break;
> +       case HCIEMU_HOOK_POST_EVT:
> +               hook_type = BTDEV_HOOK_POST_EVT;
> +               break;
> +       default:
> +               return false;
> +       }
> +
> +       return btdev_del_hook(hciemu->master_dev, hook_type, opcode);
> +}
> --
> 2.26.2
>
Luiz Augusto von Dentz Nov. 10, 2020, 10:10 p.m. UTC | #2
Hi Inga,

On Tue, Nov 10, 2020 at 1:55 PM Stotland, Inga <inga.stotland@intel.com> wrote:
>
> Hi Luiz,
>
> On Mon, 2020-11-09 at 10:04 -0800, Luiz Augusto von Dentz wrote:
>
> Hi Inga,
>
>
> On Fri, Nov 6, 2020 at 11:06 PM Inga Stotland <
>
> inga.stotland@intel.com
>
> > wrote:
>
>
> This adds a separate implementtion of hciemu code, hciemu-ell.c,
>
> that uses ELL library primitives.
>
>
> I wonder if this should really be separated like this or we just make
>
> use of struct io instead of l_io, that way we don't need to keep
>
> duplicating things on the emulator, the other option would be to drop
>
> entirely the glib version but I guess you haven't done that because
>
> there are quite a few dependencies to get rid in order to run the
>
> tester with ell.
>
>
> I would've made one version of hciemu.c with the unified io, but there's a reference to g_idel_add that has equivalent in ELL, but not the internal bluez libs.
>
> Also, hciemu is linked with android test build (android/Makefile.am) and android is a completely different animal: from what I can see it relies heavily on GLib
>
> and I wouldn't know how to test the android stuff once I make the changes. So I chose to err on the side of caution and create a duplicate.

Im planning to remove android actually, just need to move a few things
around since some unit test uses the android version.

>
>
>
>
>
> ---
>
>  emulator/hciemu-ell.c | 564 ++++++++++++++++++++++++++++++++++++++++++
>
>  1 file changed, 564 insertions(+)
>
>  create mode 100644 emulator/hciemu-ell.c
>
>
> diff --git a/emulator/hciemu-ell.c b/emulator/hciemu-ell.c
>
> new file mode 100644
>
> index 000000000..40342e99b
>
> --- /dev/null
>
> +++ b/emulator/hciemu-ell.c
>
> @@ -0,0 +1,564 @@
>
> +// SPDX-License-Identifier: LGPL-2.1-or-later
>
> +/*
>
> + *
>
> + *  BlueZ - Bluetooth protocol stack for Linux
>
> + *
>
> + *  Copyright (C) 2012-2014, 2020  Intel Corporation. All rights reserved.
>
> + *
>
> + *
>
> + */
>
> +
>
> +#ifdef HAVE_CONFIG_H
>
> +#include <config.h>
>
> +#endif
>
> +
>
> +#define _GNU_SOURCE
>
> +#include <stdio.h>
>
> +#include <fcntl.h>
>
> +#include <unistd.h>
>
> +#include <stdlib.h>
>
> +#include <string.h>
>
> +#include <stdbool.h>
>
> +#include <errno.h>
>
> +#include <sys/socket.h>
>
> +
>
> +#include <ell/ell.h>
>
> +
>
> +#include "lib/bluetooth.h"
>
> +#include "lib/hci.h"
>
> +
>
> +#include "monitor/bt.h"
>
> +#include "emulator/btdev.h"
>
> +#include "emulator/bthost.h"
>
> +#include "src/shared/util.h"
>
> +#include "src/shared/queue.h"
>
> +#include "emulator/hciemu.h"
>
> +
>
> +struct hciemu {
>
> +       int ref_count;
>
> +       enum btdev_type btdev_type;
>
> +       struct bthost *host_stack;
>
> +       struct btdev *master_dev;
>
> +       struct btdev *client_dev;
>
> +       struct l_io *host_io;
>
> +       struct l_io *master_io;
>
> +       struct l_io *client_io;
>
> +       struct queue *post_command_hooks;
>
> +       char bdaddr_str[18];
>
> +
>
> +       hciemu_debug_func_t debug_callback;
>
> +       hciemu_destroy_func_t debug_destroy;
>
> +       void *debug_data;
>
> +};
>
> +
>
> +struct hciemu_command_hook {
>
> +       hciemu_command_func_t function;
>
> +       void *user_data;
>
> +};
>
> +
>
> +static void destroy_command_hook(void *data)
>
> +{
>
> +       struct hciemu_command_hook *hook = data;
>
> +
>
> +       free(hook);
>
> +}
>
> +
>
> +struct run_data {
>
> +       uint16_t opcode;
>
> +       const void *data;
>
> +       uint8_t len;
>
> +};
>
> +
>
> +static void run_command_hook(void *data, void *user_data)
>
> +{
>
> +       struct hciemu_command_hook *hook = data;
>
> +       struct run_data *run_data = user_data;
>
> +
>
> +       if (hook->function)
>
> +               hook->function(run_data->opcode, run_data->data,
>
> +                                       run_data->len, hook->user_data);
>
> +}
>
> +
>
> +static void master_command_callback(uint16_t opcode,
>
> +                               const void *data, uint8_t len,
>
> +                               btdev_callback callback, void *user_data)
>
> +{
>
> +       struct hciemu *hciemu = user_data;
>
> +       struct run_data run_data = { .opcode = opcode,
>
> +                                               .data = data, .len = len };
>
> +
>
> +       btdev_command_default(callback);
>
> +
>
> +       queue_foreach(hciemu->post_command_hooks, run_command_hook, &run_data);
>
> +}
>
> +
>
> +static void client_command_callback(uint16_t opcode,
>
> +                               const void *data, uint8_t len,
>
> +                               btdev_callback callback, void *user_data)
>
> +{
>
> +       btdev_command_default(callback);
>
> +}
>
> +
>
> +static void writev_callback(const struct iovec *iov, int iovlen,
>
> +                                                               void *user_data)
>
> +{
>
> +       struct l_io *io = user_data;
>
> +       ssize_t written;
>
> +       int fd;
>
> +
>
> +       fd = l_io_get_fd(io);
>
> +
>
> +       written = writev(fd, iov, iovlen);
>
> +       if (written < 0)
>
> +               return;
>
> +}
>
> +
>
> +static bool receive_bthost(struct l_io *io, void *user_data)
>
> +{
>
> +       struct bthost *bthost = user_data;
>
> +       unsigned char buf[4096];
>
> +       ssize_t len;
>
> +       int fd;
>
> +
>
> +       fd = l_io_get_fd(io);
>
> +
>
> +       len = read(fd, buf, sizeof(buf));
>
> +       if (len < 0)
>
> +               return false;
>
> +
>
> +       bthost_receive_h4(bthost, buf, len);
>
> +
>
> +       return true;
>
> +}
>
> +
>
> +static struct l_io *create_io_bthost(int fd, struct bthost *bthost)
>
> +{
>
> +       struct l_io *io;
>
> +
>
> +       io = l_io_new(fd);
>
> +
>
> +       l_io_set_close_on_destroy(io, true);
>
> +
>
> +       bthost_set_send_handler(bthost, writev_callback, io);
>
> +
>
> +       l_io_set_read_handler(io, receive_bthost, bthost, NULL);
>
> +
>
> +       return io;
>
> +}
>
> +
>
> +static bool receive_btdev(struct l_io *io, void *user_data)
>
> +
>
> +{
>
> +       struct btdev *btdev = user_data;
>
> +       unsigned char buf[4096];
>
> +       ssize_t len;
>
> +       int fd;
>
> +
>
> +       fd = l_io_get_fd(io);
>
> +
>
> +       len = read(fd, buf, sizeof(buf));
>
> +       if (len < 0) {
>
> +               if (errno == EAGAIN || errno == EINTR)
>
> +                       return true;
>
> +
>
> +               return false;
>
> +       }
>
> +
>
> +       if (len < 1)
>
> +               return false;
>
> +
>
> +       switch (buf[0]) {
>
> +       case BT_H4_CMD_PKT:
>
> +       case BT_H4_ACL_PKT:
>
> +       case BT_H4_SCO_PKT:
>
> +               btdev_receive_h4(btdev, buf, len);
>
> +               break;
>
> +       }
>
> +
>
> +       return true;
>
> +}
>
> +
>
> +static struct l_io *create_io_btdev(int fd, struct btdev *btdev)
>
> +{
>
> +       struct l_io *io;
>
> +
>
> +       io = l_io_new(fd);
>
> +
>
> +       l_io_set_close_on_destroy(io, true);
>
> +
>
> +       btdev_set_send_handler(btdev, writev_callback, io);
>
> +
>
> +       l_io_set_read_handler(io, receive_btdev, btdev, NULL);
>
> +
>
> +       return io;
>
> +}
>
> +
>
> +static bool create_vhci(struct hciemu *hciemu)
>
> +{
>
> +       struct btdev *btdev;
>
> +       uint8_t create_req[2];
>
> +       ssize_t written;
>
> +       int fd;
>
> +
>
> +       btdev = btdev_create(hciemu->btdev_type, 0x00);
>
> +       if (!btdev)
>
> +               return false;
>
> +
>
> +       btdev_set_command_handler(btdev, master_command_callback, hciemu);
>
> +
>
> +       fd = open("/dev/vhci", O_RDWR | O_NONBLOCK | O_CLOEXEC);
>
> +       if (fd < 0) {
>
> +               perror("Opening /dev/vhci failed");
>
> +               btdev_destroy(btdev);
>
> +               return false;
>
> +       }
>
> +
>
> +       create_req[0] = HCI_VENDOR_PKT;
>
> +       create_req[1] = HCI_PRIMARY;
>
> +
>
> +       written = write(fd, create_req, sizeof(create_req));
>
> +       if (written < 0) {
>
> +               close(fd);
>
> +               btdev_destroy(btdev);
>
> +               return false;
>
> +       }
>
> +
>
> +       hciemu->master_dev = btdev;
>
> +
>
> +       hciemu->master_io = create_io_btdev(fd, btdev);
>
> +
>
> +       return true;
>
> +}
>
> +
>
> +struct bthost *hciemu_client_get_host(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu)
>
> +               return NULL;
>
> +
>
> +       return hciemu->host_stack;
>
> +}
>
> +
>
> +static bool create_stack(struct hciemu *hciemu)
>
> +{
>
> +       struct btdev *btdev;
>
> +       struct bthost *bthost;
>
> +       int sv[2];
>
> +
>
> +       btdev = btdev_create(hciemu->btdev_type, 0x00);
>
> +       if (!btdev)
>
> +               return false;
>
> +
>
> +       bthost = bthost_create();
>
> +       if (!bthost) {
>
> +               btdev_destroy(btdev);
>
> +               return false;
>
> +       }
>
> +
>
> +       btdev_set_command_handler(btdev, client_command_callback, hciemu);
>
> +
>
> +       if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC,
>
> +                                                               0, sv) < 0) {
>
> +               bthost_destroy(bthost);
>
> +               btdev_destroy(btdev);
>
> +               return false;
>
> +       }
>
> +
>
> +       hciemu->client_dev = btdev;
>
> +       hciemu->host_stack = bthost;
>
> +
>
> +       hciemu->client_io = create_io_btdev(sv[0], btdev);
>
> +       hciemu->host_io = create_io_bthost(sv[1], bthost);
>
> +
>
> +       return true;
>
> +}
>
> +
>
> +static void start_stack(void *user_data)
>
> +{
>
> +       struct hciemu *hciemu = user_data;
>
> +
>
> +       bthost_start(hciemu->host_stack);
>
> +}
>
> +
>
> +struct hciemu *hciemu_new(enum hciemu_type type)
>
> +{
>
> +       struct hciemu *hciemu;
>
> +
>
> +       hciemu = new0(struct hciemu, 1);
>
> +       if (!hciemu)
>
> +               return NULL;
>
> +
>
> +       switch (type) {
>
> +       case HCIEMU_TYPE_BREDRLE:
>
> +               hciemu->btdev_type = BTDEV_TYPE_BREDRLE;
>
> +               break;
>
> +       case HCIEMU_TYPE_BREDR:
>
> +               hciemu->btdev_type = BTDEV_TYPE_BREDR;
>
> +               break;
>
> +       case HCIEMU_TYPE_LE:
>
> +               hciemu->btdev_type = BTDEV_TYPE_LE;
>
> +               break;
>
> +       case HCIEMU_TYPE_LEGACY:
>
> +               hciemu->btdev_type = BTDEV_TYPE_BREDR20;
>
> +               break;
>
> +       case HCIEMU_TYPE_BREDRLE50:
>
> +               hciemu->btdev_type = BTDEV_TYPE_BREDRLE50;
>
> +               break;
>
> +       case HCIEMU_TYPE_BREDRLE52:
>
> +               hciemu->btdev_type = BTDEV_TYPE_BREDRLE52;
>
> +               break;
>
> +       default:
>
> +               return NULL;
>
> +       }
>
> +
>
> +       hciemu->post_command_hooks = queue_new();
>
> +       if (!hciemu->post_command_hooks) {
>
> +               free(hciemu);
>
> +               return NULL;
>
> +       }
>
> +
>
> +       if (!create_vhci(hciemu)) {
>
> +               queue_destroy(hciemu->post_command_hooks, NULL);
>
> +               free(hciemu);
>
> +               return NULL;
>
> +       }
>
> +
>
> +       if (!create_stack(hciemu)) {
>
> +               l_io_destroy(hciemu->master_io);
>
> +               btdev_destroy(hciemu->master_dev);
>
> +               queue_destroy(hciemu->post_command_hooks, NULL);
>
> +               free(hciemu);
>
> +               return NULL;
>
> +       }
>
> +
>
> +       l_idle_oneshot(start_stack, hciemu, NULL);
>
> +
>
> +       return hciemu_ref(hciemu);
>
> +}
>
> +
>
> +struct hciemu *hciemu_ref(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu)
>
> +               return NULL;
>
> +
>
> +       __sync_fetch_and_add(&hciemu->ref_count, 1);
>
> +
>
> +       return hciemu;
>
> +}
>
> +
>
> +void hciemu_unref(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu)
>
> +               return;
>
> +
>
> +       if (__sync_sub_and_fetch(&hciemu->ref_count, 1))
>
> +               return;
>
> +
>
> +       queue_destroy(hciemu->post_command_hooks, destroy_command_hook);
>
> +
>
> +       l_io_destroy(hciemu->host_io);
>
> +       l_io_destroy(hciemu->client_io);
>
> +       l_io_destroy(hciemu->master_io);
>
> +
>
> +       bthost_destroy(hciemu->host_stack);
>
> +       btdev_destroy(hciemu->client_dev);
>
> +       btdev_destroy(hciemu->master_dev);
>
> +
>
> +       free(hciemu);
>
> +}
>
> +
>
> +static void bthost_debug(const char *str, void *user_data)
>
> +{
>
> +       struct hciemu *hciemu = user_data;
>
> +
>
> +       util_debug(hciemu->debug_callback, hciemu->debug_data,
>
> +                                       "bthost: %s", str);
>
> +}
>
> +
>
> +static void btdev_master_debug(const char *str, void *user_data)
>
> +{
>
> +       struct hciemu *hciemu = user_data;
>
> +
>
> +       util_debug(hciemu->debug_callback, hciemu->debug_data,
>
> +                                       "btdev: %s", str);
>
> +}
>
> +
>
> +static void btdev_client_debug(const char *str, void *user_data)
>
> +{
>
> +       struct hciemu *hciemu = user_data;
>
> +
>
> +       util_debug(hciemu->debug_callback, hciemu->debug_data,
>
> +                                       "btdev[bthost]: %s", str);
>
> +}
>
> +
>
> +bool hciemu_set_debug(struct hciemu *hciemu, hciemu_debug_func_t callback,
>
> +                       void *user_data, hciemu_destroy_func_t destroy)
>
> +{
>
> +       if (!hciemu)
>
> +               return false;
>
> +
>
> +       if (hciemu->debug_destroy)
>
> +               hciemu->debug_destroy(hciemu->debug_data);
>
> +
>
> +       hciemu->debug_callback = callback;
>
> +       hciemu->debug_destroy = destroy;
>
> +       hciemu->debug_data = user_data;
>
> +
>
> +       btdev_set_debug(hciemu->master_dev, btdev_master_debug, hciemu, NULL);
>
> +       btdev_set_debug(hciemu->client_dev, btdev_client_debug, hciemu, NULL);
>
> +       bthost_set_debug(hciemu->host_stack, bthost_debug, hciemu, NULL);
>
> +
>
> +       return true;
>
> +}
>
> +
>
> +const char *hciemu_get_address(struct hciemu *hciemu)
>
> +{
>
> +       const uint8_t *addr;
>
> +
>
> +       if (!hciemu || !hciemu->master_dev)
>
> +               return NULL;
>
> +
>
> +       addr = btdev_get_bdaddr(hciemu->master_dev);
>
> +       sprintf(hciemu->bdaddr_str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
>
> +                       addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
>
> +       return hciemu->bdaddr_str;
>
> +}
>
> +
>
> +uint8_t *hciemu_get_features(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu || !hciemu->master_dev)
>
> +               return NULL;
>
> +
>
> +       return btdev_get_features(hciemu->master_dev);
>
> +}
>
> +
>
> +const uint8_t *hciemu_get_master_bdaddr(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu || !hciemu->master_dev)
>
> +               return NULL;
>
> +
>
> +       return btdev_get_bdaddr(hciemu->master_dev);
>
> +}
>
> +
>
> +const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu || !hciemu->client_dev)
>
> +               return NULL;
>
> +
>
> +       return btdev_get_bdaddr(hciemu->client_dev);
>
> +}
>
> +
>
> +uint8_t hciemu_get_master_scan_enable(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu || !hciemu->master_dev)
>
> +               return 0;
>
> +
>
> +       return btdev_get_scan_enable(hciemu->master_dev);
>
> +}
>
> +
>
> +uint8_t hciemu_get_master_le_scan_enable(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu || !hciemu->master_dev)
>
> +               return 0;
>
> +
>
> +       return btdev_get_le_scan_enable(hciemu->master_dev);
>
> +}
>
> +
>
> +void hciemu_set_master_le_states(struct hciemu *hciemu,
>
> +                                               const uint8_t *le_states)
>
> +{
>
> +       if (!hciemu || !hciemu->master_dev)
>
> +               return;
>
> +
>
> +       btdev_set_le_states(hciemu->master_dev, le_states);
>
> +}
>
> +
>
> +bool hciemu_add_master_post_command_hook(struct hciemu *hciemu,
>
> +                       hciemu_command_func_t function, void *user_data)
>
> +{
>
> +       struct hciemu_command_hook *hook;
>
> +
>
> +       if (!hciemu)
>
> +               return false;
>
> +
>
> +       hook = new0(struct hciemu_command_hook, 1);
>
> +       if (!hook)
>
> +               return false;
>
> +
>
> +       hook->function = function;
>
> +       hook->user_data = user_data;
>
> +
>
> +       if (!queue_push_tail(hciemu->post_command_hooks, hook)) {
>
> +               free(hook);
>
> +               return false;
>
> +       }
>
> +
>
> +       return true;
>
> +}
>
> +
>
> +bool hciemu_clear_master_post_command_hooks(struct hciemu *hciemu)
>
> +{
>
> +       if (!hciemu)
>
> +               return false;
>
> +
>
> +       queue_remove_all(hciemu->post_command_hooks,
>
> +                                       NULL, NULL, destroy_command_hook);
>
> +       return true;
>
> +}
>
> +
>
> +int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type,
>
> +                               uint16_t opcode, hciemu_hook_func_t function,
>
> +                               void *user_data)
>
> +{
>
> +       enum btdev_hook_type hook_type;
>
> +
>
> +       if (!hciemu)
>
> +               return -1;
>
> +
>
> +       switch (type) {
>
> +       case HCIEMU_HOOK_PRE_CMD:
>
> +               hook_type = BTDEV_HOOK_PRE_CMD;
>
> +               break;
>
> +       case HCIEMU_HOOK_POST_CMD:
>
> +               hook_type = BTDEV_HOOK_POST_CMD;
>
> +               break;
>
> +       case HCIEMU_HOOK_PRE_EVT:
>
> +               hook_type = BTDEV_HOOK_PRE_EVT;
>
> +               break;
>
> +       case HCIEMU_HOOK_POST_EVT:
>
> +               hook_type = BTDEV_HOOK_POST_EVT;
>
> +               break;
>
> +       default:
>
> +               return -1;
>
> +       }
>
> +
>
> +       return btdev_add_hook(hciemu->master_dev, hook_type, opcode, function,
>
> +                                                               user_data);
>
> +}
>
> +
>
> +bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type,
>
> +                                                               uint16_t opcode)
>
> +{
>
> +       enum btdev_hook_type hook_type;
>
> +
>
> +       if (!hciemu)
>
> +               return false;
>
> +
>
> +       switch (type) {
>
> +       case HCIEMU_HOOK_PRE_CMD:
>
> +               hook_type = BTDEV_HOOK_PRE_CMD;
>
> +               break;
>
> +       case HCIEMU_HOOK_POST_CMD:
>
> +               hook_type = BTDEV_HOOK_POST_CMD;
>
> +               break;
>
> +       case HCIEMU_HOOK_PRE_EVT:
>
> +               hook_type = BTDEV_HOOK_PRE_EVT;
>
> +               break;
>
> +       case HCIEMU_HOOK_POST_EVT:
>
> +               hook_type = BTDEV_HOOK_POST_EVT;
>
> +               break;
>
> +       default:
>
> +               return false;
>
> +       }
>
> +
>
> +       return btdev_del_hook(hciemu->master_dev, hook_type, opcode);
>
> +}
>
> --
>
> 2.26.2
>
>
>
>
diff mbox series

Patch

diff --git a/emulator/hciemu-ell.c b/emulator/hciemu-ell.c
new file mode 100644
index 000000000..40342e99b
--- /dev/null
+++ b/emulator/hciemu-ell.c
@@ -0,0 +1,564 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2012-2014, 2020  Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <ell/ell.h>
+
+#include "lib/bluetooth.h"
+#include "lib/hci.h"
+
+#include "monitor/bt.h"
+#include "emulator/btdev.h"
+#include "emulator/bthost.h"
+#include "src/shared/util.h"
+#include "src/shared/queue.h"
+#include "emulator/hciemu.h"
+
+struct hciemu {
+	int ref_count;
+	enum btdev_type btdev_type;
+	struct bthost *host_stack;
+	struct btdev *master_dev;
+	struct btdev *client_dev;
+	struct l_io *host_io;
+	struct l_io *master_io;
+	struct l_io *client_io;
+	struct queue *post_command_hooks;
+	char bdaddr_str[18];
+
+	hciemu_debug_func_t debug_callback;
+	hciemu_destroy_func_t debug_destroy;
+	void *debug_data;
+};
+
+struct hciemu_command_hook {
+	hciemu_command_func_t function;
+	void *user_data;
+};
+
+static void destroy_command_hook(void *data)
+{
+	struct hciemu_command_hook *hook = data;
+
+	free(hook);
+}
+
+struct run_data {
+	uint16_t opcode;
+	const void *data;
+	uint8_t len;
+};
+
+static void run_command_hook(void *data, void *user_data)
+{
+	struct hciemu_command_hook *hook = data;
+	struct run_data *run_data = user_data;
+
+	if (hook->function)
+		hook->function(run_data->opcode, run_data->data,
+					run_data->len, hook->user_data);
+}
+
+static void master_command_callback(uint16_t opcode,
+				const void *data, uint8_t len,
+				btdev_callback callback, void *user_data)
+{
+	struct hciemu *hciemu = user_data;
+	struct run_data run_data = { .opcode = opcode,
+						.data = data, .len = len };
+
+	btdev_command_default(callback);
+
+	queue_foreach(hciemu->post_command_hooks, run_command_hook, &run_data);
+}
+
+static void client_command_callback(uint16_t opcode,
+				const void *data, uint8_t len,
+				btdev_callback callback, void *user_data)
+{
+	btdev_command_default(callback);
+}
+
+static void writev_callback(const struct iovec *iov, int iovlen,
+								void *user_data)
+{
+	struct l_io *io = user_data;
+	ssize_t written;
+	int fd;
+
+	fd = l_io_get_fd(io);
+
+	written = writev(fd, iov, iovlen);
+	if (written < 0)
+		return;
+}
+
+static bool receive_bthost(struct l_io *io, void *user_data)
+{
+	struct bthost *bthost = user_data;
+	unsigned char buf[4096];
+	ssize_t len;
+	int fd;
+
+	fd = l_io_get_fd(io);
+
+	len = read(fd, buf, sizeof(buf));
+	if (len < 0)
+		return false;
+
+	bthost_receive_h4(bthost, buf, len);
+
+	return true;
+}
+
+static struct l_io *create_io_bthost(int fd, struct bthost *bthost)
+{
+	struct l_io *io;
+
+	io = l_io_new(fd);
+
+	l_io_set_close_on_destroy(io, true);
+
+	bthost_set_send_handler(bthost, writev_callback, io);
+
+	l_io_set_read_handler(io, receive_bthost, bthost, NULL);
+
+	return io;
+}
+
+static bool receive_btdev(struct l_io *io, void *user_data)
+
+{
+	struct btdev *btdev = user_data;
+	unsigned char buf[4096];
+	ssize_t len;
+	int fd;
+
+	fd = l_io_get_fd(io);
+
+	len = read(fd, buf, sizeof(buf));
+	if (len < 0) {
+		if (errno == EAGAIN || errno == EINTR)
+			return true;
+
+		return false;
+	}
+
+	if (len < 1)
+		return false;
+
+	switch (buf[0]) {
+	case BT_H4_CMD_PKT:
+	case BT_H4_ACL_PKT:
+	case BT_H4_SCO_PKT:
+		btdev_receive_h4(btdev, buf, len);
+		break;
+	}
+
+	return true;
+}
+
+static struct l_io *create_io_btdev(int fd, struct btdev *btdev)
+{
+	struct l_io *io;
+
+	io = l_io_new(fd);
+
+	l_io_set_close_on_destroy(io, true);
+
+	btdev_set_send_handler(btdev, writev_callback, io);
+
+	l_io_set_read_handler(io, receive_btdev, btdev, NULL);
+
+	return io;
+}
+
+static bool create_vhci(struct hciemu *hciemu)
+{
+	struct btdev *btdev;
+	uint8_t create_req[2];
+	ssize_t written;
+	int fd;
+
+	btdev = btdev_create(hciemu->btdev_type, 0x00);
+	if (!btdev)
+		return false;
+
+	btdev_set_command_handler(btdev, master_command_callback, hciemu);
+
+	fd = open("/dev/vhci", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+	if (fd < 0) {
+		perror("Opening /dev/vhci failed");
+		btdev_destroy(btdev);
+		return false;
+	}
+
+	create_req[0] = HCI_VENDOR_PKT;
+	create_req[1] = HCI_PRIMARY;
+
+	written = write(fd, create_req, sizeof(create_req));
+	if (written < 0) {
+		close(fd);
+		btdev_destroy(btdev);
+		return false;
+	}
+
+	hciemu->master_dev = btdev;
+
+	hciemu->master_io = create_io_btdev(fd, btdev);
+
+	return true;
+}
+
+struct bthost *hciemu_client_get_host(struct hciemu *hciemu)
+{
+	if (!hciemu)
+		return NULL;
+
+	return hciemu->host_stack;
+}
+
+static bool create_stack(struct hciemu *hciemu)
+{
+	struct btdev *btdev;
+	struct bthost *bthost;
+	int sv[2];
+
+	btdev = btdev_create(hciemu->btdev_type, 0x00);
+	if (!btdev)
+		return false;
+
+	bthost = bthost_create();
+	if (!bthost) {
+		btdev_destroy(btdev);
+		return false;
+	}
+
+	btdev_set_command_handler(btdev, client_command_callback, hciemu);
+
+	if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC,
+								0, sv) < 0) {
+		bthost_destroy(bthost);
+		btdev_destroy(btdev);
+		return false;
+	}
+
+	hciemu->client_dev = btdev;
+	hciemu->host_stack = bthost;
+
+	hciemu->client_io = create_io_btdev(sv[0], btdev);
+	hciemu->host_io = create_io_bthost(sv[1], bthost);
+
+	return true;
+}
+
+static void start_stack(void *user_data)
+{
+	struct hciemu *hciemu = user_data;
+
+	bthost_start(hciemu->host_stack);
+}
+
+struct hciemu *hciemu_new(enum hciemu_type type)
+{
+	struct hciemu *hciemu;
+
+	hciemu = new0(struct hciemu, 1);
+	if (!hciemu)
+		return NULL;
+
+	switch (type) {
+	case HCIEMU_TYPE_BREDRLE:
+		hciemu->btdev_type = BTDEV_TYPE_BREDRLE;
+		break;
+	case HCIEMU_TYPE_BREDR:
+		hciemu->btdev_type = BTDEV_TYPE_BREDR;
+		break;
+	case HCIEMU_TYPE_LE:
+		hciemu->btdev_type = BTDEV_TYPE_LE;
+		break;
+	case HCIEMU_TYPE_LEGACY:
+		hciemu->btdev_type = BTDEV_TYPE_BREDR20;
+		break;
+	case HCIEMU_TYPE_BREDRLE50:
+		hciemu->btdev_type = BTDEV_TYPE_BREDRLE50;
+		break;
+	case HCIEMU_TYPE_BREDRLE52:
+		hciemu->btdev_type = BTDEV_TYPE_BREDRLE52;
+		break;
+	default:
+		return NULL;
+	}
+
+	hciemu->post_command_hooks = queue_new();
+	if (!hciemu->post_command_hooks) {
+		free(hciemu);
+		return NULL;
+	}
+
+	if (!create_vhci(hciemu)) {
+		queue_destroy(hciemu->post_command_hooks, NULL);
+		free(hciemu);
+		return NULL;
+	}
+
+	if (!create_stack(hciemu)) {
+		l_io_destroy(hciemu->master_io);
+		btdev_destroy(hciemu->master_dev);
+		queue_destroy(hciemu->post_command_hooks, NULL);
+		free(hciemu);
+		return NULL;
+	}
+
+	l_idle_oneshot(start_stack, hciemu, NULL);
+
+	return hciemu_ref(hciemu);
+}
+
+struct hciemu *hciemu_ref(struct hciemu *hciemu)
+{
+	if (!hciemu)
+		return NULL;
+
+	__sync_fetch_and_add(&hciemu->ref_count, 1);
+
+	return hciemu;
+}
+
+void hciemu_unref(struct hciemu *hciemu)
+{
+	if (!hciemu)
+		return;
+
+	if (__sync_sub_and_fetch(&hciemu->ref_count, 1))
+		return;
+
+	queue_destroy(hciemu->post_command_hooks, destroy_command_hook);
+
+	l_io_destroy(hciemu->host_io);
+	l_io_destroy(hciemu->client_io);
+	l_io_destroy(hciemu->master_io);
+
+	bthost_destroy(hciemu->host_stack);
+	btdev_destroy(hciemu->client_dev);
+	btdev_destroy(hciemu->master_dev);
+
+	free(hciemu);
+}
+
+static void bthost_debug(const char *str, void *user_data)
+{
+	struct hciemu *hciemu = user_data;
+
+	util_debug(hciemu->debug_callback, hciemu->debug_data,
+					"bthost: %s", str);
+}
+
+static void btdev_master_debug(const char *str, void *user_data)
+{
+	struct hciemu *hciemu = user_data;
+
+	util_debug(hciemu->debug_callback, hciemu->debug_data,
+					"btdev: %s", str);
+}
+
+static void btdev_client_debug(const char *str, void *user_data)
+{
+	struct hciemu *hciemu = user_data;
+
+	util_debug(hciemu->debug_callback, hciemu->debug_data,
+					"btdev[bthost]: %s", str);
+}
+
+bool hciemu_set_debug(struct hciemu *hciemu, hciemu_debug_func_t callback,
+			void *user_data, hciemu_destroy_func_t destroy)
+{
+	if (!hciemu)
+		return false;
+
+	if (hciemu->debug_destroy)
+		hciemu->debug_destroy(hciemu->debug_data);
+
+	hciemu->debug_callback = callback;
+	hciemu->debug_destroy = destroy;
+	hciemu->debug_data = user_data;
+
+	btdev_set_debug(hciemu->master_dev, btdev_master_debug, hciemu, NULL);
+	btdev_set_debug(hciemu->client_dev, btdev_client_debug, hciemu, NULL);
+	bthost_set_debug(hciemu->host_stack, bthost_debug, hciemu, NULL);
+
+	return true;
+}
+
+const char *hciemu_get_address(struct hciemu *hciemu)
+{
+	const uint8_t *addr;
+
+	if (!hciemu || !hciemu->master_dev)
+		return NULL;
+
+	addr = btdev_get_bdaddr(hciemu->master_dev);
+	sprintf(hciemu->bdaddr_str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+			addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
+	return hciemu->bdaddr_str;
+}
+
+uint8_t *hciemu_get_features(struct hciemu *hciemu)
+{
+	if (!hciemu || !hciemu->master_dev)
+		return NULL;
+
+	return btdev_get_features(hciemu->master_dev);
+}
+
+const uint8_t *hciemu_get_master_bdaddr(struct hciemu *hciemu)
+{
+	if (!hciemu || !hciemu->master_dev)
+		return NULL;
+
+	return btdev_get_bdaddr(hciemu->master_dev);
+}
+
+const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu)
+{
+	if (!hciemu || !hciemu->client_dev)
+		return NULL;
+
+	return btdev_get_bdaddr(hciemu->client_dev);
+}
+
+uint8_t hciemu_get_master_scan_enable(struct hciemu *hciemu)
+{
+	if (!hciemu || !hciemu->master_dev)
+		return 0;
+
+	return btdev_get_scan_enable(hciemu->master_dev);
+}
+
+uint8_t hciemu_get_master_le_scan_enable(struct hciemu *hciemu)
+{
+	if (!hciemu || !hciemu->master_dev)
+		return 0;
+
+	return btdev_get_le_scan_enable(hciemu->master_dev);
+}
+
+void hciemu_set_master_le_states(struct hciemu *hciemu,
+						const uint8_t *le_states)
+{
+	if (!hciemu || !hciemu->master_dev)
+		return;
+
+	btdev_set_le_states(hciemu->master_dev, le_states);
+}
+
+bool hciemu_add_master_post_command_hook(struct hciemu *hciemu,
+			hciemu_command_func_t function, void *user_data)
+{
+	struct hciemu_command_hook *hook;
+
+	if (!hciemu)
+		return false;
+
+	hook = new0(struct hciemu_command_hook, 1);
+	if (!hook)
+		return false;
+
+	hook->function = function;
+	hook->user_data = user_data;
+
+	if (!queue_push_tail(hciemu->post_command_hooks, hook)) {
+		free(hook);
+		return false;
+	}
+
+	return true;
+}
+
+bool hciemu_clear_master_post_command_hooks(struct hciemu *hciemu)
+{
+	if (!hciemu)
+		return false;
+
+	queue_remove_all(hciemu->post_command_hooks,
+					NULL, NULL, destroy_command_hook);
+	return true;
+}
+
+int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type,
+				uint16_t opcode, hciemu_hook_func_t function,
+				void *user_data)
+{
+	enum btdev_hook_type hook_type;
+
+	if (!hciemu)
+		return -1;
+
+	switch (type) {
+	case HCIEMU_HOOK_PRE_CMD:
+		hook_type = BTDEV_HOOK_PRE_CMD;
+		break;
+	case HCIEMU_HOOK_POST_CMD:
+		hook_type = BTDEV_HOOK_POST_CMD;
+		break;
+	case HCIEMU_HOOK_PRE_EVT:
+		hook_type = BTDEV_HOOK_PRE_EVT;
+		break;
+	case HCIEMU_HOOK_POST_EVT:
+		hook_type = BTDEV_HOOK_POST_EVT;
+		break;
+	default:
+		return -1;
+	}
+
+	return btdev_add_hook(hciemu->master_dev, hook_type, opcode, function,
+								user_data);
+}
+
+bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type,
+								uint16_t opcode)
+{
+	enum btdev_hook_type hook_type;
+
+	if (!hciemu)
+		return false;
+
+	switch (type) {
+	case HCIEMU_HOOK_PRE_CMD:
+		hook_type = BTDEV_HOOK_PRE_CMD;
+		break;
+	case HCIEMU_HOOK_POST_CMD:
+		hook_type = BTDEV_HOOK_POST_CMD;
+		break;
+	case HCIEMU_HOOK_PRE_EVT:
+		hook_type = BTDEV_HOOK_PRE_EVT;
+		break;
+	case HCIEMU_HOOK_POST_EVT:
+		hook_type = BTDEV_HOOK_POST_EVT;
+		break;
+	default:
+		return false;
+	}
+
+	return btdev_del_hook(hciemu->master_dev, hook_type, opcode);
+}