Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2016 Nordic Semiconductor ASA
 * Copyright (c) 2016 Vinayak Kariappa Chettimada
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdint.h>
#include <string.h>

#include <soc.h>

#include "ccm.h"
#include "radio.h"

#include "mem.h"
#include "util.h"
#include "ticker.h"

#include "pdu.h"
#include "ctrl.h"
#include "ll.h"

#include "debug.h"

static struct {
	uint8_t pub_addr[BDADDR_SIZE];
	uint8_t rnd_addr[BDADDR_SIZE];
} _ll_context;

static struct {
	uint16_t interval;
	uint8_t adv_type:4;
	uint8_t tx_addr:1;
	uint8_t rx_addr:1;
	uint8_t filter_policy:2;
	uint8_t chl_map:3;
	uint8_t adv_addr[BDADDR_SIZE];
	uint8_t direct_addr[BDADDR_SIZE];
} _ll_adv_params;

static struct {
	uint16_t interval;
	uint16_t window;
	uint8_t scan_type:1;
	uint8_t tx_addr:1;
	uint8_t filter_policy:1;
} _ll_scan_params;

void ll_address_get(uint8_t addr_type, uint8_t *bdaddr)
{
	if (addr_type) {
		memcpy(bdaddr, &_ll_context.rnd_addr[0], BDADDR_SIZE);
	} else {
		memcpy(bdaddr, &_ll_context.pub_addr[0], BDADDR_SIZE);
	}
}

void ll_address_set(uint8_t addr_type, uint8_t const *const bdaddr)
{
	if (addr_type) {
		memcpy(&_ll_context.rnd_addr[0], bdaddr, BDADDR_SIZE);
	} else {
		memcpy(&_ll_context.pub_addr[0], bdaddr, BDADDR_SIZE);
	}
}

void ll_adv_params_set(uint16_t interval, uint8_t adv_type,
		       uint8_t own_addr_type, uint8_t direct_addr_type,
		       uint8_t const *const direct_addr, uint8_t chl_map,
		       uint8_t filter_policy)
{
	struct radio_adv_data *radio_adv_data;
	struct pdu_adv *pdu;

	/** @todo check and fail if adv role active else
	 * update (implemented below) current index elements for
	 * both adv and scan data.
	 */

	/* remember params so that set adv/scan data and adv enable
	 * interface can correctly update adv/scan data in the
	 * double buffer between caller and controller context.
	 */
	_ll_adv_params.interval = interval;
	_ll_adv_params.chl_map = chl_map;
	_ll_adv_params.filter_policy = filter_policy;
	_ll_adv_params.adv_type = adv_type;
	_ll_adv_params.tx_addr = own_addr_type;
	_ll_adv_params.rx_addr = 0;

	/* update the current adv data */
	radio_adv_data = radio_adv_data_get();
	pdu = (struct pdu_adv *)&radio_adv_data->data[radio_adv_data->last][0];
	pdu->type = _ll_adv_params.adv_type;
	pdu->tx_addr = _ll_adv_params.tx_addr;
	if (adv_type == PDU_ADV_TYPE_DIRECT_IND) {
		_ll_adv_params.rx_addr = direct_addr_type;
		memcpy(&_ll_adv_params.direct_addr[0], direct_addr,
			 BDADDR_SIZE);
		memcpy(&pdu->payload.direct_ind.init_addr[0],
			 direct_addr, BDADDR_SIZE);
		pdu->len = sizeof(struct pdu_adv_payload_direct_ind);
	} else if (pdu->len == 0) {
		pdu->len = BDADDR_SIZE;
	}
	pdu->rx_addr = _ll_adv_params.rx_addr;

	/* update the current scan data */
	radio_adv_data = radio_scan_data_get();
	pdu = (struct pdu_adv *)&radio_adv_data->data[radio_adv_data->last][0];
	pdu->type = PDU_ADV_TYPE_SCAN_RESP;
	pdu->tx_addr = _ll_adv_params.tx_addr;
	pdu->rx_addr = 0;
	if (pdu->len == 0) {
		pdu->len = BDADDR_SIZE;
	}
}

void ll_adv_data_set(uint8_t len, uint8_t const *const data)
{
	struct radio_adv_data *radio_adv_data;
	struct pdu_adv *pdu;
	uint8_t last;

	/** @todo dont update data if directed adv type. */

	/* use the last index in double buffer, */
	radio_adv_data = radio_adv_data_get();
	if (radio_adv_data->first == radio_adv_data->last) {
		last = radio_adv_data->last + 1;
		if (last == DOUBLE_BUFFER_SIZE) {
			last = 0;
		}
	} else {
		last = radio_adv_data->last;
	}

	/* update adv pdu fields. */
	pdu = (struct pdu_adv *)&radio_adv_data->data[last][0];
	pdu->type = _ll_adv_params.adv_type;
	pdu->tx_addr = _ll_adv_params.tx_addr;
	pdu->rx_addr = _ll_adv_params.rx_addr;
	memcpy(&pdu->payload.adv_ind.addr[0],
		 &_ll_adv_params.adv_addr[0], BDADDR_SIZE);
	if (_ll_adv_params.adv_type == PDU_ADV_TYPE_DIRECT_IND) {
		memcpy(&pdu->payload.direct_ind.init_addr[0],
			 &_ll_adv_params.direct_addr[0], BDADDR_SIZE);
		pdu->len = sizeof(struct pdu_adv_payload_direct_ind);
	} else {
		memcpy(&pdu->payload.adv_ind.data[0], data, len);
		pdu->len = BDADDR_SIZE + len;
	}

	/* commit the update so controller picks it. */
	radio_adv_data->last = last;
}

void ll_scan_data_set(uint8_t len, uint8_t const *const data)
{
	struct radio_adv_data *radio_scan_data;
	struct pdu_adv *pdu;
	uint8_t last;

	/* use the last index in double buffer, */
	radio_scan_data = radio_scan_data_get();
	if (radio_scan_data->first == radio_scan_data->last) {
		last = radio_scan_data->last + 1;
		if (last == DOUBLE_BUFFER_SIZE) {
			last = 0;
		}
	} else {
		last = radio_scan_data->last;
	}

	/* update scan pdu fields. */
	pdu = (struct pdu_adv *)&radio_scan_data->data[last][0];
	pdu->type = PDU_ADV_TYPE_SCAN_RESP;
	pdu->tx_addr = _ll_adv_params.tx_addr;
	pdu->rx_addr = 0;
	pdu->len = BDADDR_SIZE + len;
	memcpy(&pdu->payload.scan_resp.addr[0],
		 &_ll_adv_params.adv_addr[0], BDADDR_SIZE);
	memcpy(&pdu->payload.scan_resp.data[0], data, len);

	/* commit the update so controller picks it. */
	radio_scan_data->last = last;
}

uint32_t ll_adv_enable(uint8_t enable)
{
	uint32_t status;

	if (enable) {
		struct radio_adv_data *radio_adv_data;
		struct radio_adv_data *radio_scan_data;
		struct pdu_adv *pdu_adv;
		struct pdu_adv *pdu_scan;

		/** @todo move the addr remembered into controller
		 * this way when implementing Privacy 1.2, generated
		 * new resolvable addresses can be used instantly.
		 */

		/* remember addr to use and also update the addr in
		 * both adv and scan PDUs.
		 */
		radio_adv_data = radio_adv_data_get();
		radio_scan_data = radio_scan_data_get();
		pdu_adv = (struct pdu_adv *)&radio_adv_data->data
				[radio_adv_data->last][0];
		pdu_scan = (struct pdu_adv *)&radio_scan_data->data
				[radio_scan_data->last][0];
		if (_ll_adv_params.tx_addr) {
			memcpy(&_ll_adv_params.adv_addr[0],
				 &_ll_context.rnd_addr[0], BDADDR_SIZE);
			memcpy(&pdu_adv->payload.adv_ind.addr[0],
				 &_ll_context.rnd_addr[0], BDADDR_SIZE);
			memcpy(&pdu_scan->payload.scan_resp.addr[0],
				 &_ll_context.rnd_addr[0], BDADDR_SIZE);
		} else {
			memcpy(&_ll_adv_params.adv_addr[0],
				 &_ll_context.pub_addr[0], BDADDR_SIZE);
			memcpy(&pdu_adv->payload.adv_ind.addr[0],
				 &_ll_context.pub_addr[0], BDADDR_SIZE);
			memcpy(&pdu_scan->payload.scan_resp.addr[0],
				 &_ll_context.pub_addr[0], BDADDR_SIZE);
		}

		status = radio_adv_enable(_ll_adv_params.interval,
						_ll_adv_params.chl_map,
						_ll_adv_params.filter_policy);
	} else {
		status = radio_adv_disable();
	}

	return status;
}

void ll_scan_params_set(uint8_t scan_type, uint16_t interval, uint16_t window,
			uint8_t own_addr_type, uint8_t filter_policy)
{
	_ll_scan_params.scan_type = scan_type;
	_ll_scan_params.interval = interval;
	_ll_scan_params.window = window;
	_ll_scan_params.tx_addr = own_addr_type;
	_ll_scan_params.filter_policy = filter_policy;
}

uint32_t ll_scan_enable(uint8_t enable)
{
	uint32_t status;

	if (enable) {
		status = radio_scan_enable(_ll_scan_params.scan_type,
				_ll_scan_params.tx_addr,
				(_ll_scan_params.tx_addr) ?
					&_ll_context.rnd_addr[0] :
					&_ll_context.pub_addr[0],
				_ll_scan_params.interval,
				_ll_scan_params.window,
				_ll_scan_params.filter_policy);
	} else {
		status = radio_scan_disable();
	}

	return status;
}

uint32_t ll_create_connection(uint16_t scan_interval, uint16_t scan_window,
			      uint8_t filter_policy, uint8_t peer_addr_type,
			      uint8_t *peer_addr, uint8_t own_addr_type,
			      uint16_t interval, uint16_t latency,
			      uint16_t timeout)
{
	uint32_t status;

	status = radio_connect_enable(peer_addr_type, peer_addr, interval,
					latency, timeout);

	if (status) {
		return status;
	}

	return radio_scan_enable(0, own_addr_type, (own_addr_type) ?
			&_ll_context.rnd_addr[0] :
			&_ll_context.pub_addr[0],
		scan_interval, scan_window, filter_policy);
}