| /* |
| * This file is part of the UWB stack for linux. |
| * |
| * Copyright (c) 2021 Qorvo US, Inc. |
| * |
| * This software is provided under the GNU General Public License, version 2 |
| * (GPLv2), as well as under a Qorvo commercial license. |
| * |
| * You may choose to use this software under the terms of the GPLv2 License, |
| * version 2 ("GPLv2"), as published by the Free Software Foundation. |
| * You should have received a copy of the GPLv2 along with this program. If |
| * not, see <http://www.gnu.org/licenses/>. |
| * |
| * This program is distributed under the GPLv2 in the hope that it will be |
| * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more |
| * details. |
| * |
| * If you cannot meet the requirements of the GPLv2, you may not use this |
| * software for any purpose without first obtaining a commercial license from |
| * Qorvo. Please contact Qorvo to inquire about licensing terms. |
| */ |
| |
| #include "dw3000_nfcc_coex_msg.h" |
| #include "dw3000_nfcc_coex_buffer.h" |
| #include "dw3000_nfcc_coex_core.h" |
| #include "dw3000.h" |
| #include "dw3000_trc.h" |
| #include "dw3000_core.h" |
| |
| /* TLVs len helpers. */ |
| #define TLV_TYPELEN_LEN 2 /* type 1 byte, len 1 byte. */ |
| #define TLV_U32_LEN (4 + 1) /* u32 + ack/nack. */ |
| #define TLV_SLOTS_LEN(nbslots) \ |
| (1 + (8 * (nbslots)) + 1) /* nslots + slots + ack/nack. */ |
| |
| /* Error codes for TLV_ERROR type. */ |
| #define CCC_ERR_LATE_SPIMAVAIL 0 |
| #define CCC_ERR_SLOT_CONFLICT 1 |
| #define CCC_ERR_CODE_SZ 2 |
| |
| #define MSG_NEXT_TLV(buffer, offset) \ |
| (struct dw3000_nfcc_coex_tlv *)((buffer)->msg.tlvs + (offset)) |
| |
| /** |
| * struct dw3000_nfcc_coex_tlv - Type Length Value. |
| */ |
| struct dw3000_nfcc_coex_tlv { |
| /** |
| * @type: Identifier of TLV. |
| */ |
| u8 type; |
| /** |
| * @len: Number of byte of TLV array. |
| */ |
| u8 len; |
| /** |
| * @tlv: Value of the TLV. |
| */ |
| u8 tlv[]; |
| } __attribute__((packed)); |
| |
| /** |
| * dw3000_nfcc_coex_header_put() - Fill NFCC frame header. |
| * @dw: Driver context. |
| * @buffer: Message buffer to fill. |
| */ |
| void dw3000_nfcc_coex_header_put(struct dw3000 *dw, |
| struct dw3000_nfcc_coex_buffer *buffer) |
| { |
| struct dw3000_nfcc_coex_msg *msg = &buffer->msg; |
| |
| memcpy(msg->signature, DW3000_NFCC_COEX_SIGNATURE_STR, |
| DW3000_NFCC_COEX_SIGNATURE_LEN); |
| msg->ver_id = DW3000_NFCC_COEX_VER_ID; |
| msg->seqnum = dw->nfcc_coex.tx_seq_num; |
| msg->nb_tlv = 0; |
| buffer->tlvs_len = 0; |
| } |
| |
| /** |
| * dw3000_nfcc_coex_single_tlv_slot_put() - Fill buffer payload for a single TLV slot. |
| * @buffer: Message buffer to fill. |
| * @start: Start date of NFCC session in ms. |
| * @end: End date of NFCC session in ms. |
| */ |
| void dw3000_nfcc_coex_single_tlv_slot_put( |
| struct dw3000_nfcc_coex_buffer *buffer, u32 start, u32 end) |
| { |
| struct dw3000_nfcc_coex_msg *msg = &buffer->msg; |
| struct dw3000_nfcc_coex_tlv *tlv; |
| struct dw3000_nfcc_coex_tlv_slot_list *slot_list; |
| |
| tlv = MSG_NEXT_TLV(buffer, buffer->tlvs_len); |
| msg->nb_tlv++; |
| tlv->type = TLV_SLOT_LIST; |
| tlv->len = TLV_SLOTS_LEN(1); |
| slot_list = (struct dw3000_nfcc_coex_tlv_slot_list *)&tlv->tlv; |
| slot_list->nb_slots = 1; |
| slot_list->slots[0].start_sys_time = start; |
| slot_list->slots[0].end_sys_time = end; |
| buffer->tlvs_len += TLV_TYPELEN_LEN + tlv->len; |
| } |
| |
| /** |
| * dw3000_nfcc_coex_tlv_u32_put() - Fill buffer payload for a TLV. |
| * @buffer: Message buffer to fill. |
| * @type: Type id of the TLV. |
| * @value: Value of TLV. |
| */ |
| void dw3000_nfcc_coex_tlv_u32_put(struct dw3000_nfcc_coex_buffer *buffer, |
| u8 type, u32 value) |
| { |
| struct dw3000_nfcc_coex_msg *msg = &buffer->msg; |
| struct dw3000_nfcc_coex_tlv *tlv; |
| u32 *v; |
| |
| tlv = MSG_NEXT_TLV(buffer, buffer->tlvs_len); |
| msg->nb_tlv++; |
| tlv->type = type; |
| tlv->len = 4; |
| v = (u32 *)&tlv->tlv; |
| *v = value; |
| buffer->tlvs_len += TLV_TYPELEN_LEN + TLV_U32_LEN; |
| } |
| |
| /** |
| * dw3000_nfcc_coex_tlv_slots_put() - Fill buffer payload for a TLV slots. |
| * @buffer: Message buffer to fill. |
| * @slot_list: TLV slots. |
| */ |
| void dw3000_nfcc_coex_tlv_slots_put( |
| struct dw3000_nfcc_coex_buffer *buffer, |
| const struct dw3000_nfcc_coex_tlv_slot_list *slot_list) |
| { |
| struct dw3000_nfcc_coex_msg *msg = &buffer->msg; |
| struct dw3000_nfcc_coex_tlv *tlv; |
| |
| tlv = MSG_NEXT_TLV(buffer, buffer->tlvs_len); |
| msg->nb_tlv++; |
| tlv->type = TLV_SLOT_LIST; |
| tlv->len = TLV_SLOTS_LEN(slot_list->nb_slots); |
| memcpy(&tlv->tlv, slot_list, sizeof(*slot_list)); |
| buffer->tlvs_len += TLV_TYPELEN_LEN + tlv->len; |
| } |
| |
| /** |
| * dw3000_nfcc_coex_clock_sync_payload_put() - Fill clock sync frame payload. |
| * @dw: Driver context. |
| * @buffer: Buffer to set with help of handle_access. |
| */ |
| static void |
| dw3000_nfcc_coex_clock_sync_payload_put(struct dw3000 *dw, |
| struct dw3000_nfcc_coex_buffer *buffer) |
| { |
| u32 session_time0_sys_time = |
| dw3000_dtu_to_sys_time(dw, dw->nfcc_coex.session_start_dtu); |
| |
| trace_dw3000_nfcc_coex_clock_sync_payload_put(dw, |
| session_time0_sys_time); |
| dw3000_nfcc_coex_header_put(dw, buffer); |
| dw3000_nfcc_coex_tlv_u32_put(buffer, TLV_SESSION_TIME0, |
| session_time0_sys_time); |
| } |
| |
| /** |
| * dw3000_nfcc_coex_clock_offset_payload_put() - Fill clock offset payload. |
| * @dw: Driver context. |
| * @buffer: Buffer to set with help of handle_access. |
| * @clock_offset_sys_time: Offset to add to next ccc schedule. |
| */ |
| static void dw3000_nfcc_coex_clock_offset_payload_put( |
| struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer, |
| u32 clock_offset_sys_time) |
| { |
| trace_dw3000_nfcc_coex_clock_offset_payload_put(dw, |
| clock_offset_sys_time); |
| dw3000_nfcc_coex_header_put(dw, buffer); |
| dw3000_nfcc_coex_tlv_u32_put(buffer, TLV_UWBCNT_OFFS, |
| clock_offset_sys_time); |
| } |
| |
| /** |
| * dw3000_nfcc_coex_message_send() - Write message for NFCC and release SPI1. |
| * @dw: Driver context. |
| * |
| * Return: 0 on success, else an error. |
| */ |
| int dw3000_nfcc_coex_message_send(struct dw3000 *dw) |
| { |
| struct dw3000_nfcc_coex_buffer buffer = {}; |
| /* Build the absolute sys time offset. */ |
| u32 offset_sys_time = |
| (dw->dtu_sync << DW3000_DTU_PER_SYS_POWER) - dw->sys_time_sync; |
| |
| if (dw->nfcc_coex.sync_time_needed) { |
| dw->nfcc_coex.sync_time_needed = false; |
| dw3000_nfcc_coex_clock_sync_payload_put(dw, &buffer); |
| } else { |
| /* Compute the clock correction to forward to NFCC. */ |
| u32 clock_offset_sys_time = |
| offset_sys_time - dw->nfcc_coex.prev_offset_sys_time; |
| /* Build the message with the clock update to forward. */ |
| dw3000_nfcc_coex_clock_offset_payload_put( |
| dw, &buffer, -clock_offset_sys_time); |
| } |
| |
| dw->nfcc_coex.prev_offset_sys_time = offset_sys_time; |
| /* Write message to NFCC and release SP1. */ |
| return dw3000_nfcc_coex_write_buffer(dw, &buffer, MSG_LEN(buffer)); |
| } |
| |
| /** |
| * dw3000_nfcc_coex_header_check() - Check header message. |
| * @dw: Driver context. |
| * @buffer: Buffer to check. |
| * |
| * Return: 0 when buffer contain a valid message, else an error. |
| */ |
| static int |
| dw3000_nfcc_coex_header_check(struct dw3000 *dw, |
| const struct dw3000_nfcc_coex_buffer *buffer) |
| { |
| const struct dw3000_nfcc_coex_msg *msg = &buffer->msg; |
| |
| trace_dw3000_nfcc_coex_header_check(dw, msg->signature, msg->ver_id, |
| msg->seqnum, msg->nb_tlv); |
| /* Check signature. */ |
| if (memcmp(msg->signature, DW3000_NFCC_COEX_SIGNATURE_STR, |
| DW3000_NFCC_COEX_SIGNATURE_LEN)) { |
| return -EINVAL; |
| } |
| /* Check AP_NFCC Interface Version ID. */ |
| if (msg->ver_id != DW3000_NFCC_COEX_VER_ID) { |
| return -EINVAL; |
| } |
| /* Read number of TLVs. */ |
| if (msg->nb_tlv > DW3000_NFCC_COEX_MAX_NB_TLV) { |
| return -EINVAL; |
| } |
| /* Check if message is a new one with the sequence number. */ |
| if (!dw->nfcc_coex.first_rx_message && |
| (msg->seqnum - dw->nfcc_coex.rx_seq_num) > 0) { |
| /* TODO: Reject message with bad seqnum. */ |
| } |
| |
| dw->nfcc_coex.rx_seq_num = msg->seqnum; |
| dw->nfcc_coex.first_rx_message = false; |
| return 0; |
| } |
| |
| /** |
| * dw3000_nfcc_coex_tlvs_check() - Set information on a received message. |
| * @dw: Driver context. |
| * @buffer: Buffer to read. |
| * @rx_msg_info: information updated on valid message. |
| * |
| * Return: 0 on success, else an error. |
| */ |
| static int |
| dw3000_nfcc_coex_tlvs_check(struct dw3000 *dw, |
| const struct dw3000_nfcc_coex_buffer *buffer, |
| struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info) |
| { |
| static const int tlvs_len_max = |
| DW3000_NFCC_COEX_MSG_MAX_SIZE - MSG_HEADER_LEN; |
| const struct dw3000_nfcc_coex_msg *msg = &buffer->msg; |
| const struct dw3000_nfcc_coex_tlv_slot_list *slot_list = NULL; |
| int tlvs_len = 0; /* Start parsing at first TLV. */ |
| int i; |
| |
| /* Process tlvs. */ |
| for (i = 0; i < msg->nb_tlv; i++) { |
| struct dw3000_nfcc_coex_tlv *tlv; |
| |
| if ((tlvs_len + sizeof(*tlv)) > tlvs_len_max) |
| return -EINVAL; |
| |
| tlv = MSG_NEXT_TLV(buffer, tlvs_len); |
| tlvs_len += tlv->len; |
| |
| if (tlvs_len > tlvs_len_max) |
| return -EINVAL; |
| |
| switch (tlv->type) { |
| case TLV_SLOT_LIST: |
| /* Reject a new TLV with same type. Behavior not defined. */ |
| if (slot_list) |
| return -EINVAL; |
| slot_list = (const struct dw3000_nfcc_coex_tlv_slot_list |
| *)&tlv->tlv; |
| /* Keep the pointer ref for testmode. */ |
| rx_msg_info->slot_list = slot_list; |
| /* Update rx_msg_info. */ |
| if (slot_list->nb_slots > 0) { |
| const struct dw3000_nfcc_coex_tlv_slot *slot = |
| &slot_list->slots[0]; |
| |
| rx_msg_info->next_slot_found = true; |
| rx_msg_info->next_timestamp_sys_time = |
| slot->start_sys_time; |
| rx_msg_info->next_duration_sys_time = |
| slot->end_sys_time - |
| slot->start_sys_time; |
| } |
| break; |
| case TLV_ERROR: |
| trace_dw3000_nfcc_coex_err(dw, "nfcc sent an error"); |
| break; |
| default: |
| trace_dw3000_nfcc_coex_warn( |
| dw, "ignoring unexpected TLV type"); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * dw3000_nfcc_coex_process_tlvs() - Set data from message parsing. |
| * @dw: Driver context. |
| * @buffer: Buffer to read. |
| * @rx_msg_info: Result of message parsed updated on success. |
| * |
| * Return: 0 on success, else an error. |
| */ |
| int dw3000_nfcc_coex_message_check( |
| struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer, |
| struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info) |
| { |
| int r; |
| |
| r = dw3000_nfcc_coex_header_check(dw, buffer); |
| if (r) |
| return r; |
| |
| r = dw3000_nfcc_coex_tlvs_check(dw, buffer, rx_msg_info); |
| if (r) |
| return r; |
| |
| if (rx_msg_info->next_slot_found) |
| trace_dw3000_nfcc_coex_rx_msg_info( |
| dw, rx_msg_info->next_timestamp_sys_time, |
| rx_msg_info->next_duration_sys_time); |
| return 0; |
| } |