/**************************************************************************** * wireless/bluetooth/bt_buf.c * * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2016, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bt_hcicore.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #if !defined(CONFIG_BLUETOOTH_BUFFER_PREALLOC) || \ CONFIG_BLUETOOTH_BUFFER_PREALLOC < 1 # undef CONFIG_BLUETOOTH_BUFFER_PREALLOC # define CONFIG_BLUETOOTH_BUFFER_PREALLOC 20 #endif #if !defined(CONFIG_BLUETOOTH_BUFFER_IRQRESERVE) || \ CONFIG_BLUETOOTH_BUFFER_IRQRESERVE < 0 # undef CONFIG_BLUETOOTH_BUFFER_IRQRESERVE # define CONFIG_BLUETOOTH_BUFFER_IRQRESERVE 0 #endif #if CONFIG_BLUETOOTH_BUFFER_IRQRESERVE > CONFIG_BLUETOOTH_BUFFER_PREALLOC # undef CONFIG_BLUETOOTH_BUFFER_IRQRESERVE # define CONFIG_BLUETOOTH_BUFFER_IRQRESERVE CONFIG_BLUETOOTH_BUFFER_PREALLOC #endif /* Memory Pools */ #define POOL_BUFFER_GENERAL 0 #define POOL_BUFFER_IRQ 1 #define POOL_BUFFER_DYNAMIC 2 /**************************************************************************** * Private Data ****************************************************************************/ #if CONFIG_BLUETOOTH_BUFFER_PREALLOC > CONFIG_BLUETOOTH_BUFFER_IRQRESERVE /* g_buf_free is a list of buffers that are available for general use. The * number of messages in this list is a system configuration item. */ static struct bt_buf_s *g_buf_free; #endif #if CONFIG_BLUETOOTH_BUFFER_IRQRESERVE > 0 /* The g_buf_free_irq is a list of buffer structures that are reserved for * use by only by interrupt handlers. */ static struct bt_buf_s *g_buf_free_irq; #endif /* Pool of pre-allocated buffer structures */ static struct bt_buf_s g_buf_pool[CONFIG_BLUETOOTH_BUFFER_PREALLOC]; static bool g_poolinit = false; /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: bt_buf_initialize * * Description: * This function initializes the buffer allocator. This function must * be called early in the initialization sequence before any radios * begin operation. * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ void bt_buf_initialize(void) { FAR struct bt_buf_s *pool = g_buf_pool; int remaining = CONFIG_BLUETOOTH_BUFFER_PREALLOC; /* Only allow the pool to be initialized once */ if (g_poolinit) { return; } g_poolinit = true; #if CONFIG_BLUETOOTH_BUFFER_PREALLOC > CONFIG_BLUETOOTH_BUFFER_IRQRESERVE /* Initialize g_buf_free, the list of buffer structures that are available * for general use. */ g_buf_free = NULL; while (remaining > CONFIG_BLUETOOTH_BUFFER_IRQRESERVE) { FAR struct bt_buf_s *buf = pool; /* Add the next meta data structure from the pool to the list of * general structures. */ buf->flink = g_buf_free; g_buf_free = buf; /* Set up for the next structure from the pool */ pool++; remaining--; } #endif #if CONFIG_BLUETOOTH_BUFFER_IRQRESERVE > 0 /* Initialize g_buf_free_irq is a list of buffer structures reserved for * use by only by interrupt handlers. */ g_buf_free_irq = NULL; while (remaining > 0) { FAR struct bt_buf_s *buf = pool; /* Add the next meta data structure from the pool to the list of * general structures. */ buf->flink = g_buf_free_irq; g_buf_free_irq = buf; /* Set up for the next structure from the pool */ pool++; remaining--; } #endif } /**************************************************************************** * Name: bt_buf_alloc * * Description: * The bt_buf_alloc function will get a free buffer for use by the * Bluetooth stack with specified type and reserved headroom. The * reference count is initially set to one. * * Interrupt handling logic will first attempt to allocate from the * g_buf_free list. If that list is empty, it will attempt to allocate * from its reserve, g_buf_free_irq. If that list is empty, then the * allocation fails (NULL is returned). * * Non-interrupt handler logic will attempt to allocate from g_buf_free * list. If that the list is empty, then the buffer structure will be * allocated from the dynamic memory pool with some performance hit. * * Input Parameters: * type - Buffer type. * iob - The raw I/O buffer. If NULL, then bt_buf_alloc() will * allocate the frame IOB. * reserve_head - How much headroom to reserve. Will be ignored if * a non-NULL iob is provided. In that case, the head * room is determined by the actual data offset. * * Returned Value: * A reference to the allocated buffer structure. All user fields in this * structure have been zeroed. On a failure to allocate, NULL is * returned. * ****************************************************************************/ FAR struct bt_buf_s *bt_buf_alloc(enum bt_buf_type_e type, FAR struct iob_s *iob, size_t reserve_head) { FAR struct bt_buf_s *buf; irqstate_t flags; uint8_t pool; /* If we were called from an interrupt handler, then try to get the meta- * data structure from generally available list of messages. If this fails, * then try the list of messages reserved for interrupt handlers */ flags = spin_lock_irqsave(NULL); /* Always necessary in SMP mode */ if (up_interrupt_context()) { #if CONFIG_BLUETOOTH_BUFFER_PREALLOC > CONFIG_BLUETOOTH_BUFFER_IRQRESERVE /* Try the general free list */ if (g_buf_free != NULL) { buf = g_buf_free; g_buf_free = buf->flink; spin_unlock_irqrestore(NULL, flags); pool = POOL_BUFFER_GENERAL; } else #endif #if CONFIG_BLUETOOTH_BUFFER_IRQRESERVE > 0 /* Try the list list reserved for interrupt handlers */ if (g_buf_free_irq != NULL) { buf = g_buf_free_irq; g_buf_free_irq = buf->flink; spin_unlock_irqrestore(NULL, flags); pool = POOL_BUFFER_IRQ; } else #endif { spin_unlock_irqrestore(NULL, flags); return NULL; } } /* We were not called from an interrupt handler. */ else { #if CONFIG_BLUETOOTH_BUFFER_PREALLOC > CONFIG_BLUETOOTH_BUFFER_IRQRESERVE /* Try the general free list */ if (g_buf_free != NULL) { buf = g_buf_free; g_buf_free = buf->flink; spin_unlock_irqrestore(NULL, flags); pool = POOL_BUFFER_GENERAL; } else #endif { /* If we cannot get a buffer structure from the free list, then we * will have to allocate one from the kernel memory pool. */ spin_unlock_irqrestore(NULL, flags); buf = (FAR struct bt_buf_s *) kmm_malloc((sizeof (struct bt_buf_s))); /* Check if we successfully allocated the buffer structure */ if (buf == NULL) { /* No.. memory not available */ wlerr("ERROR: Failed to allocate buffer.\n"); return NULL; } /* Remember that this buffer structure was dynamically allocated */ pool = POOL_BUFFER_DYNAMIC; } } /* We have successfully allocated memory from some source. Initialize the * allocated buffer structure. */ memset(buf, 0, sizeof(struct bt_buf_s)); buf->pool = pool; buf->ref = 1; buf->type = type; /* Were we provided with an IOB? */ if (iob != NULL) { /* Yes.. use that IOB */ DEBUGASSERT(iob->io_len >= iob->io_offset && iob->io_len <= BLUETOOTH_MAX_FRAMELEN); buf->frame = iob; buf->data = &iob->io_data[iob->io_offset]; buf->len = iob->io_len - iob->io_offset; /* IOB length includes offset */ } else { /* No.. Allocate an IOB to hold the actual frame data. This call will * normally block in the event that there is no available IOB memory. * It will return NULL is called from an interrupt handler with no * available buffers. */ buf->frame = iob_alloc(false); if (!buf->frame) { wlerr("ERROR: Failed to allocate an IOB\n"); bt_buf_release(buf); return NULL; } buf->frame->io_offset = reserve_head; buf->frame->io_len = reserve_head; /* IOB length includes offset */ buf->frame->io_pktlen = reserve_head; buf->data = buf->frame->io_data + reserve_head; } wlinfo("buf %p type %d reserve %zu\n", buf, buf->type, reserve_head); return buf; } /**************************************************************************** * Name: bt_buf_release * * Description: * The bt_buf_release function will return a buffer structure to * the free pool of buffers if it was a pre-allocated buffer structure. * If the buffer structure was allocated dynamically it will be * deallocated. * * Input Parameters: * buf - Buffer structure to free * * Returned Value: * None * ****************************************************************************/ void bt_buf_release(FAR struct bt_buf_s *buf) { #ifdef CONFIG_WIRELESS_BLUETOOTH_HOST enum bt_buf_type_e type; uint16_t handle; #endif irqstate_t flags; wlinfo("buf %p ref %u type %d\n", buf, buf->ref, buf->type); DEBUGASSERT(buf->ref > 0); if (--buf->ref > 0) { wlinfo("Remaining references: %d\n", buf->ref); return; } #ifdef CONFIG_WIRELESS_BLUETOOTH_HOST handle = buf->u.acl.handle; type = buf->type; #endif /* Free the contained frame and return the container to the correct memory * pool. */ if (buf->frame != NULL) { iob_free(buf->frame); buf->frame = NULL; } #if CONFIG_BLUETOOTH_BUFFER_PREALLOC > CONFIG_BLUETOOTH_BUFFER_IRQRESERVE /* If this is a generally available pre-allocated buffer structure, * then just put it back in the free list. */ if (buf->pool == POOL_BUFFER_GENERAL) { /* Make sure we avoid concurrent access to the free * list from interrupt handlers. */ flags = spin_lock_irqsave(NULL); buf->flink = g_buf_free; g_buf_free = buf; spin_unlock_irqrestore(NULL, flags); } else #endif #if CONFIG_BLUETOOTH_BUFFER_IRQRESERVE > 0 /* If this is a buffer structure pre-allocated for interrupts, * then put it back in the correct free list. */ if (buf->pool == POOL_BUFFER_IRQ) { /* Make sure we avoid concurrent access to the free * list from interrupt handlers. */ flags = spin_lock_irqsave(NULL); buf->flink = g_buf_free_irq; g_buf_free_irq = buf; spin_unlock_irqrestore(NULL, flags); } else #endif { /* Otherwise, deallocate it. */ DEBUGASSERT(buf->pool == POOL_BUFFER_DYNAMIC); kmm_free(buf); } wlinfo("Buffer freed: %p\n", buf); #ifdef CONFIG_WIRELESS_BLUETOOTH_HOST if (type == BT_ACL_IN) { FAR struct bt_hci_cp_host_num_completed_packets_s *cp; FAR struct bt_hci_handle_count_s *hc; wlinfo("Reporting completed packet for handle %u\n", handle); buf = bt_hci_cmd_create(BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS, sizeof(*cp) + sizeof(*hc)); if (buf == NULL) { wlerr("ERROR: Unable to allocate new HCI command\n"); return; } cp = bt_buf_extend(buf, sizeof(*cp)); cp->num_handles = BT_HOST2LE16(1); hc = bt_buf_extend(buf, sizeof(*hc)); hc->handle = BT_HOST2LE16(handle); hc->count = BT_HOST2LE16(1); bt_hci_cmd_send(BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS, buf); } #endif } /**************************************************************************** * Name: bt_buf_addref * * Description: * Increment the reference count of a buffer. * * Input Parameters: * buf - Buffer. * ****************************************************************************/ FAR struct bt_buf_s *bt_buf_addref(FAR struct bt_buf_s *buf) { wlinfo("buf %p (old) ref %u type %d\n", buf, buf->ref, buf->type); buf->ref++; return buf; } /**************************************************************************** * Name: bt_buf_extend * * Description: * Increments the data length of a buffer to account for more data * at the end of the buffer. * * Input Parameters: * buf - Buffer to update. * len - Number of bytes to increment the length with. * * Returned Value: * The original tail of the buffer. * ****************************************************************************/ FAR void *bt_buf_extend(FAR struct bt_buf_s *buf, size_t len) { FAR uint8_t *tail = bt_buf_tail(buf); wlinfo("buf %p len %zu\n", buf, len); DEBUGASSERT(bt_buf_tailroom(buf) >= len); buf->len += len; return tail; } /**************************************************************************** * Name: bt_buf_put_le16 * * Description: * Adds 16-bit value in little endian format at the end of buffer. * Increments the data length of a buffer to account for more data * at the end. * * Input Parameters: * buf - Buffer to update. * value - 16-bit value to be added. * * Returned Value: * None * ****************************************************************************/ void bt_buf_put_le16(FAR struct bt_buf_s *buf, uint16_t value) { wlinfo("buf %p value %u\n", buf, value); value = BT_HOST2LE16(value); memcpy(bt_buf_extend(buf, sizeof(value)), &value, sizeof(value)); } /**************************************************************************** * Name: bt_buf_provide * * Description: * Modifies the data pointer and buffer length to account for more data * in the beginning of the buffer. * * Input Parameters: * buf - Buffer to update. * len - Number of bytes to add to the beginning. * * Returned Value: * The new beginning of the buffer data. * ****************************************************************************/ FAR void *bt_buf_provide(FAR struct bt_buf_s *buf, size_t len) { wlinfo("buf %p len %zu\n", buf, len); DEBUGASSERT(buf != NULL && buf->frame != NULL && bt_buf_headroom(buf) >= len); buf->data -= len; buf->len += len; return buf->data; } /**************************************************************************** * Name: bt_buf_consume * * Description: * Removes data from the beginning of the buffer by modifying the data * pointer and buffer length. * * Input Parameters: * len - Number of bytes to remove. * * Returned Value: * New beginning of the buffer data. * ****************************************************************************/ FAR void *bt_buf_consume(FAR struct bt_buf_s *buf, size_t len) { wlinfo("buf %p len %zu\n", buf, len); DEBUGASSERT(buf->len >= len); buf->len -= len; return buf->data += len; } /**************************************************************************** * Name: bt_buf_get_le16 * * Description: * Same idea as with bt_buf_pull(), but a helper for operating on * 16-bit little endian data. * * Input Parameters: * buf - Buffer. * * Returned Value: * 16-bit value converted from little endian to host endian. * ****************************************************************************/ uint16_t bt_buf_get_le16(FAR struct bt_buf_s * buf) { uint16_t value; value = BT_GETUINT16((FAR uint8_t *)buf->data); bt_buf_consume(buf, sizeof(value)); return value; } /**************************************************************************** * Name: bt_buf_headroom * * Description: * Check how much free space there is in the beginning of the buffer. * * Returned Value: * Number of bytes available in the beginning of the buffer. * ****************************************************************************/ size_t bt_buf_headroom(FAR struct bt_buf_s *buf) { DEBUGASSERT(buf != NULL && buf->frame != NULL); return buf->data - buf->frame->io_data; } /**************************************************************************** * Name: bt_buf_tailroom * * Description: * Check how much free space there is at the end of the buffer. * * Returned Value: * Number of bytes available at the end of the buffer. * ****************************************************************************/ size_t bt_buf_tailroom(FAR struct bt_buf_s * buf) { return BLUETOOTH_MAX_FRAMELEN - bt_buf_headroom(buf) - buf->len; }