| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2018 Intel Corporation. All rights reserved. |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <ell/ell.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/mgmt.h" |
| #include "src/shared/mgmt.h" |
| |
| #include "mesh/mesh-defs.h" |
| #include "mesh/mesh-mgmt.h" |
| #include "mesh/mesh-io.h" |
| #include "mesh/mesh-io-api.h" |
| |
| /* List of Mesh-IO Type headers */ |
| #include "mesh/mesh-io-mgmt.h" |
| #include "mesh/mesh-io-generic.h" |
| #include "mesh/mesh-io-unit.h" |
| |
| struct loop_data { |
| uint16_t len; |
| uint8_t data[]; |
| }; |
| |
| /* List of Supported Mesh-IO Types */ |
| static const struct mesh_io_table table[] = { |
| {MESH_IO_TYPE_MGMT, &mesh_io_mgmt}, |
| {MESH_IO_TYPE_GENERIC, &mesh_io_generic}, |
| {MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit}, |
| }; |
| |
| static const uint8_t unprv_filter[] = { MESH_AD_TYPE_BEACON, 0 }; |
| |
| static struct mesh_io *default_io; |
| static struct l_timeout *loop_adv_to; |
| |
| static const struct mesh_io_api *io_api(enum mesh_io_type type) |
| { |
| uint16_t i; |
| |
| for (i = 0; i < L_ARRAY_SIZE(table); i++) { |
| if (table[i].type == type) |
| return table[i].api; |
| } |
| |
| return NULL; |
| } |
| |
| static void refresh_rx(void *a, void *b) |
| { |
| struct mesh_io_reg *rx_reg = a; |
| struct mesh_io *io = b; |
| |
| if (io->api && io->api->reg) |
| io->api->reg(io, rx_reg->filter, rx_reg->len, rx_reg->cb, |
| rx_reg->user_data); |
| } |
| |
| static void ctl_alert(int index, bool up, bool pwr, bool mesh, void *user_data) |
| { |
| enum mesh_io_type type = L_PTR_TO_UINT(user_data); |
| const struct mesh_io_api *api = NULL; |
| |
| l_warn("index %u up:%d pwr: %d mesh: %d", index, up, pwr, mesh); |
| |
| /* If specific IO controller requested, honor it */ |
| if (default_io->favored_index != MGMT_INDEX_NONE) { |
| if (default_io->favored_index != index) |
| return; |
| |
| if (!up | pwr) { |
| l_warn("HCI%u failed to start generic IO %s", |
| index, pwr ? ": already powered on" : ""); |
| if (default_io->ready) |
| default_io->ready(default_io->user_data, false); |
| } |
| } |
| |
| if (!up && default_io->index == index) { |
| /* Our controller has disappeared */ |
| if (default_io->api && default_io->api->destroy) { |
| default_io->api->destroy(default_io); |
| default_io->api = NULL; |
| } |
| |
| /* Re-enumerate controllers */ |
| mesh_mgmt_list(ctl_alert, user_data); |
| return; |
| } |
| |
| /* If we already have an API, keep using it */ |
| if (!up || default_io->api) |
| return; |
| |
| if (mesh && type != MESH_IO_TYPE_GENERIC) |
| api = io_api(MESH_IO_TYPE_MGMT); |
| else if (!pwr) |
| api = io_api(MESH_IO_TYPE_GENERIC); |
| |
| if (api) { |
| default_io->index = index; |
| default_io->api = api; |
| api->init(default_io, &index, default_io->user_data); |
| l_queue_foreach(default_io->rx_regs, refresh_rx, default_io); |
| } |
| } |
| |
| static void free_io(struct mesh_io *io) |
| { |
| if (io) { |
| if (io->api && io->api->destroy) |
| io->api->destroy(io); |
| |
| l_queue_destroy(io->rx_regs, l_free); |
| io->rx_regs = NULL; |
| l_free(io); |
| l_warn("Destroy %p", io); |
| } |
| } |
| |
| static struct mesh_io_reg *find_by_filter(struct l_queue *rx_regs, |
| const uint8_t *filter, uint8_t len) |
| { |
| const struct l_queue_entry *entry; |
| |
| entry = l_queue_get_entries(rx_regs); |
| |
| for (; entry; entry = entry->next) { |
| struct mesh_io_reg *rx_reg = entry->data; |
| |
| if (rx_reg->len == len && !memcmp(rx_reg->filter, filter, len)) |
| return rx_reg; |
| } |
| |
| return NULL; |
| } |
| |
| struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts, |
| mesh_io_ready_func_t cb, void *user_data) |
| { |
| const struct mesh_io_api *api = NULL; |
| |
| /* Only allow one IO */ |
| if (default_io) |
| return NULL; |
| |
| default_io = l_new(struct mesh_io, 1); |
| default_io->ready = cb; |
| default_io->user_data = user_data; |
| default_io->favored_index = *(int *) opts; |
| default_io->rx_regs = l_queue_new(); |
| |
| if (type >= MESH_IO_TYPE_AUTO) { |
| if (!mesh_mgmt_list(ctl_alert, L_UINT_TO_PTR(type))) |
| goto fail; |
| |
| return default_io; |
| } |
| |
| api = io_api(type); |
| |
| if (!api || !api->init) |
| goto fail; |
| |
| default_io->api = api; |
| |
| if (!api->init(default_io, opts, user_data)) |
| goto fail; |
| |
| return default_io; |
| |
| fail: |
| free_io(default_io); |
| default_io = NULL; |
| return NULL; |
| } |
| |
| void mesh_io_destroy(struct mesh_io *io) |
| { |
| } |
| |
| bool mesh_io_get_caps(struct mesh_io *io, struct mesh_io_caps *caps) |
| { |
| if (io != default_io) |
| return false; |
| |
| if (io && io->api && io->api->caps) |
| return io->api->caps(io, caps); |
| |
| return false; |
| } |
| |
| bool mesh_io_register_recv_cb(struct mesh_io *io, const uint8_t *filter, |
| uint8_t len, mesh_io_recv_func_t cb, |
| void *user_data) |
| { |
| struct mesh_io_reg *rx_reg; |
| |
| if (io == NULL) |
| io = default_io; |
| |
| if (io != default_io || !cb || !filter || !len) |
| return false; |
| |
| rx_reg = find_by_filter(io->rx_regs, filter, len); |
| |
| l_free(rx_reg); |
| l_queue_remove(io->rx_regs, rx_reg); |
| |
| rx_reg = l_malloc(sizeof(struct mesh_io_reg) + len); |
| rx_reg->cb = cb; |
| rx_reg->len = len; |
| rx_reg->user_data = user_data; |
| memcpy(rx_reg->filter, filter, len); |
| |
| l_queue_push_head(io->rx_regs, rx_reg); |
| |
| if (io && io->api && io->api->reg) |
| return io->api->reg(io, filter, len, cb, user_data); |
| |
| return false; |
| } |
| |
| bool mesh_io_deregister_recv_cb(struct mesh_io *io, const uint8_t *filter, |
| uint8_t len) |
| { |
| struct mesh_io_reg *rx_reg; |
| |
| if (io != default_io) |
| return false; |
| |
| rx_reg = find_by_filter(io->rx_regs, filter, len); |
| |
| l_queue_remove(io->rx_regs, rx_reg); |
| l_free(rx_reg); |
| |
| if (io && io->api && io->api->dereg) |
| return io->api->dereg(io, filter, len); |
| |
| return false; |
| } |
| |
| static void loop_foreach(void *data, void *user_data) |
| { |
| struct mesh_io_reg *rx_reg = data; |
| struct loop_data *rx = user_data; |
| |
| if (!memcmp(rx_reg->filter, unprv_filter, sizeof(unprv_filter))) |
| rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len); |
| } |
| |
| static void loop_rx(struct l_timeout *timeout, void *user_data) |
| { |
| struct loop_data *rx = user_data; |
| |
| l_queue_foreach(default_io->rx_regs, loop_foreach, rx); |
| l_timeout_modify_ms(loop_adv_to, 500); |
| } |
| |
| static void loop_destroy(void *user_data) |
| { |
| l_free(user_data); |
| } |
| |
| static void loop_unprv_beacon(const uint8_t *data, uint16_t len) |
| { |
| struct loop_data *pkt = l_malloc(len + sizeof(struct loop_data)); |
| |
| memcpy(pkt->data, data, len); |
| pkt->len = len; |
| l_timeout_remove(loop_adv_to); |
| loop_adv_to = l_timeout_create_ms(500, loop_rx, pkt, loop_destroy); |
| } |
| |
| bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info, |
| const uint8_t *data, uint16_t len) |
| { |
| if (io && io != default_io) |
| return false; |
| |
| if (!io) |
| io = default_io; |
| |
| /* Loop unprovisioned beacons for local clients */ |
| if (!memcmp(data, unprv_filter, sizeof(unprv_filter))) |
| loop_unprv_beacon(data, len); |
| |
| if (io && io->api && io->api->send) |
| return io->api->send(io, info, data, len); |
| |
| return false; |
| } |
| |
| bool mesh_io_send_cancel(struct mesh_io *io, const uint8_t *pattern, |
| uint8_t len) |
| { |
| if (io && io != default_io) |
| return false; |
| |
| if (!io) |
| io = default_io; |
| |
| if (loop_adv_to && len >= 2 && !memcmp(pattern, unprv_filter, 2)) { |
| l_timeout_remove(loop_adv_to); |
| loop_adv_to = NULL; |
| } |
| |
| if (io && io->api && io->api->cancel) |
| return io->api->cancel(io, pattern, len); |
| |
| return false; |
| } |