drivers/wireless/lpwan/rn2xx3/rn2xx3.c (1,156 lines of code) (raw):

/**************************************************************************** * drivers/wireless/lpwan/rn2xx3/rn2xx3.c * * NOTE: EXPERIMENTAL DRIVER * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include <nuttx/config.h> #include <debug.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <stdint.h> #include <unistd.h> #include <math.h> #include <nuttx/fs/fs.h> #include <nuttx/kmalloc.h> #include <nuttx/mutex.h> #include <nuttx/semaphore.h> #include <nuttx/signal.h> #include <nuttx/wqueue.h> #include <nuttx/wireless/lpwan/rn2xx3.h> /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_LIBC_LONG_LONG #error "CONFIG_LIBC_LONG_LONG must be enabled for this driver" #endif /* Duration of maximum MAC layer pause in milliseconds */ #define MAC_PAUSE_DUR "4294967245" /* Helper to get array length */ #define array_len(arr) ((sizeof(arr)) / sizeof((arr)[0])) /**************************************************************************** * Private Data Types ****************************************************************************/ /* Radio transceiver modules */ enum rn2xx3_type_e { RN2903 = 0, /* RN2903 transceiver */ RN2483 = 1, /* RN2483 transceiver */ }; /* Transmit power level and associated dBm power */ struct txlvl_s { int8_t lvl; /* Transmit power level */ int32_t power; /* Transmit power in 0.01 dBm */ }; /* Radio device struct */ struct rn2xx3_dev_s { FAR struct file uart; /* UART interface */ bool receiving; /* Currently receiving */ mutex_t devlock; /* Exclusive access */ enum rn2xx3_type_e model; /* Transceiver model */ enum rn2xx3_mod_e mod; /* Modulation mode */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS int16_t crefs; /* Number of open references */ #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int rn2xx3_open(FAR struct file *filep); static int rn2xx3_close(FAR struct file *filep); #endif static ssize_t rn2xx3_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static ssize_t rn2xx3_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static int rn2xx3_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_rn2xx3fops = { #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS .open = rn2xx3_open, .close = rn2xx3_close, #else .open = NULL, .close = NULL, #endif .read = rn2xx3_read, .write = rn2xx3_write, .ioctl = rn2xx3_ioctl, }; /* Modulation types */ static const char *MODULATIONS[] = { "lora", /* RN2XX3_MOD_LORA */ "fsk", /* RN2XX3_MOD_FSK */ }; /* Coding rates */ static const char *CODING_RATES[] = { "4/5", /* RN2XX3_CR_4_5 */ "4/6", /* RN2XX3_CR_4_6 */ "4/7", /* RN2XX3_CR_4_7 */ "4/8", /* RN2XX3_CR_4_8 */ }; /* Max packet sizes for each modulation type. */ static const uint8_t MAX_PKTSIZE[] = { 255, /* RN2XX3_MOD_LORA */ 64, /* RN2XX3_MOD_FSK */ }; /* Transmit power levels and their output powers in dBm for the RN2903 * module, ordered from least to greatest */ static const struct txlvl_s RN2903_TXLVLS[] = { {.lvl = 2, .power = 300}, {.lvl = 3, .power = 400}, {.lvl = 4, .power = 500}, {.lvl = 5, .power = 600}, {.lvl = 6, .power = 700}, {.lvl = 7, .power = 800}, {.lvl = 8, .power = 900}, {.lvl = 9, .power = 1000}, {.lvl = 10, .power = 1100}, {.lvl = 11, .power = 1200}, {.lvl = 12, .power = 1300}, {.lvl = 14, .power = 1470}, {.lvl = 15, .power = 1550}, {.lvl = 16, .power = 1630}, {.lvl = 17, .power = 1700}, {.lvl = 20, .power = 1850}, }; /* Transmit power levels and their output powers in dBm for the RN2483 * module, ordered from least to greatest */ static const struct txlvl_s RN2483_TXLVLS[] = { {.lvl = -3, .power = -400}, {.lvl = -2, .power = -290}, {.lvl = -1, .power = -190}, {.lvl = 0, .power = -170}, {.lvl = 1, .power = -60}, {.lvl = 2, .power = 40}, {.lvl = 3, .power = 140}, {.lvl = 4, .power = 250}, {.lvl = 5, .power = 360}, {.lvl = 6, .power = 470}, {.lvl = 7, .power = 580}, {.lvl = 8, .power = 690}, {.lvl = 9, .power = 810}, {.lvl = 10, .power = 930}, {.lvl = 11, .power = 1040}, {.lvl = 12, .power = 1160}, {.lvl = 13, .power = 1250}, {.lvl = 14, .power = 1350}, {.lvl = 15, .power = 1410}, }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: radio_flush * * Description: * Flushes all characters in the receive buffer from the RN2xx3 radio. * Returns the number of bytes read. * ****************************************************************************/ static int radio_flush(FAR struct rn2xx3_dev_s *priv) { int err; int to_read; char buf[10]; err = file_ioctl(&priv->uart, FIONREAD, &to_read); if (err < 0) { return err; } while (to_read > 0) { err = file_read(&priv->uart, buf, sizeof(buf)); if (err < 0) { return err; } to_read -= err; } return 0; } /**************************************************************************** * Name: read_line * * Description: * Reads a whole line of response from the RN2xx3 radio. Returns the number * of bytes read, excluding the terminating \r\n and null terminator. * ****************************************************************************/ static ssize_t read_line(FAR struct rn2xx3_dev_s *priv, FAR char *buf, size_t nbytes) { ssize_t length = 0; ssize_t i = 0; bool line_complete = false; for (; i <= nbytes; ) { length = file_read(&priv->uart, &buf[i], nbytes - i); if (length < 0) { return length; } i += length; /* Check if the character we just read was '\n'. This only occurs when * the transceiver's response is complete. Length check ensures that * we're not checking uninitialized memory in the case that `length` is * 0. */ if (length > 0 && buf[i - 1] == '\n') { line_complete = true; break; } } /* Insufficient buffer space to handle response */ if (!line_complete) { return -ENOBUFS; } /* Overwrite preceding \r with null terminator */ buf[i - 2] = '\0'; return i - 2; /* Number of bytes read excluding \r\n */ } /**************************************************************************** * Name: mac_pause * * Description: * Pauses the MAC layer. Required before every transmission/receive. * ****************************************************************************/ static int mac_pause(FAR struct rn2xx3_dev_s *priv) { ssize_t length = 0; char response[30]; /* Issue pause command */ length = file_write(&priv->uart, "mac pause\r\n", sizeof("mac pause\r\n") - 1); if (length < 0) { return length; } /* Wait for response of watchdog timer timeout */ length = read_line(priv, response, sizeof(response)); if (length < 0) { return length; } /* Check for pause duration */ if (strstr(response, MAC_PAUSE_DUR) == NULL) { return -EIO; } return 0; }; /**************************************************************************** * Name: get_command_err * * Description: * Parses the command error response from the radio module and converts it * into an error code. 0 for 'ok', -EINVAL for 'invalid_param' and -EBUSY * for 'busy'. If an unknown error occurs, -EIO is returned. * ****************************************************************************/ static int get_command_err(FAR struct rn2xx3_dev_s *priv) { char response[18]; ssize_t length; length = read_line(priv, response, sizeof(response)); if (length < 0) { return length; } if (strstr(response, "ok")) { /* Do nothing, this is good */ return 0; } else if (strstr(response, "invalid_param")) { wlerr("RN2xx3 invalid_param\n"); return -EINVAL; } else if (strstr(response, "busy")) { wlerr("RN2xx3 busy"); return -EBUSY; } wlerr("Unknown error"); return -EIO; } /**************************************************************************** * Name: rn2xx3_txpwrlevel * * Description: * Get the transmission power level that is greater than or equal to the * requested transmission power in dBm. * The true dBm level that was selected is returned in the `dbm` pointer. * ****************************************************************************/ static int8_t rn2xx3_txpwrlevel(FAR int32_t *dbm, FAR const struct txlvl_s *arr, uint8_t len) { /* Look until a power level is found that is equal to or exceeds the * requested one. Starting from lowest power */ for (uint8_t i = 0; i < len; i++) { if (arr[i].power >= *dbm) { *dbm = arr[i].power; return arr[i].lvl; } } /* Requested power was higher than all options, return highest possible * power */ *dbm = arr[len - 1].power; return arr[len - 1].lvl; } /**************************************************************************** * Name: rn2xx3_txpwrdbm * * Description: * Get the transmission power in dBm that is equal to the passed tx power * level. * * Return: `INT32_MAX` if level doesn't exist. * ****************************************************************************/ static int32_t rn2xx3_txpwrdbm(int8_t lvl, FAR const struct txlvl_s *arr, uint8_t len) { /* Look until a power level is found that is equal to this one. */ for (uint8_t i = 0; i < len; i++) { if (arr[i].lvl == lvl) { return arr[i].power; } } /* Requested level does not exist */ return INT32_MAX; } /**************************************************************************** * Name: rn2xx3_get_param * * Description: * Send a command and read the response from the RN2XX3. * * Arguments: * priv - Pointer to radio device struct * cmd - Null terminated command string, including \r\n * resp - Buffer to store radio response * nbytes - Size of 'resp' buffer * * Returns: Number of bytes read in response. * ****************************************************************************/ static ssize_t rn2xx3_getparam(FAR struct rn2xx3_dev_s *priv, FAR char *cmd, FAR char *resp, size_t nbytes) { ssize_t length; length = file_write(&priv->uart, cmd, strlen(cmd)); if (length < 0) { return length; } length = read_line(priv, resp, nbytes); return length; } /**************************************************************************** * Name: rn2xx3_reset * * Description: * Reset the radio module. * ****************************************************************************/ static int rn2xx3_reset(FAR struct rn2xx3_dev_s *priv) { ssize_t ret; char response[50]; int rxavail = 0; ret = file_write(&priv->uart, "sys reset\r\n", sizeof("sys reset\r\n") - 1); if (ret < 0) { return ret; } /* Might take some time to reset, wait for something to appear in the * receive buffer */ while (rxavail <= 10) { ret = file_ioctl(&priv->uart, FIONREAD, &rxavail); if (ret < 0) { return ret; } } /* Clear out the receive buffer, we've now reset */ ret = read_line(priv, response, sizeof(response)); if (ret < 0) { return ret; } return 0; } /**************************************************************************** * Name: rn2xx3_getmodel * * Description: * Get the transceiver model type. * ****************************************************************************/ static int rn2xx3_getmodel(FAR struct rn2xx3_dev_s *priv, FAR enum rn2xx3_type_e *model) { ssize_t length; char response[40]; length = radio_flush(priv); if (length < 0) { return length; } length = rn2xx3_getparam(priv, "sys get ver\r\n", response, sizeof(response)); if (length < 0) { return length; } if (strstr(response, "RN2903") != NULL) { *model = RN2903; } else if (strstr(response, "RN2483") != NULL) { *model = RN2483; } else { /* Something went wrong, model number should have been returned */ return -EIO; } /* Flush any leftover radio data */ length = radio_flush(priv); if (length < 0) { return length; } return 0; } /**************************************************************************** * Name: rn2xx3_getsnr * * Description: * Get the signal to noise ratio of the last received packet. * ****************************************************************************/ static int rn2xx3_getsnr(FAR struct rn2xx3_dev_s *priv, FAR int8_t *snr) { ssize_t length; char response[10]; length = rn2xx3_getparam(priv, "radio get snr\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; *snr = strtol(response, NULL, 10); return -errno; /* Set by strtol on failure */ } /**************************************************************************** * Name: rn2xx3_crc_en * * Description: * Enabled/disable CRC for the RN2xx3. * ****************************************************************************/ static int rn2xx3_crc_en(FAR struct rn2xx3_dev_s *priv, bool enable) { char enstr[16]; ssize_t length; length = file_write(&priv->uart, "radio set crc ", sizeof("radio set crc ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(enstr, sizeof(enstr), "%s\r\n", enable ? "on" : "off"); length = file_write(&priv->uart, enstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_iqi_en * * Description: * Enabled/disable invert IQ for the RN2xx3. * ****************************************************************************/ static int rn2xx3_iqi_en(FAR struct rn2xx3_dev_s *priv, bool enable) { char enstr[16]; ssize_t length; length = file_write(&priv->uart, "radio set iqi ", sizeof("radio set iqi ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(enstr, sizeof(enstr), "%s\r\n", enable ? "on" : "off"); length = file_write(&priv->uart, enstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_setfreq * * Description: * Set the operating frequency of the RN2xx3 in Hz. * ****************************************************************************/ static int rn2xx3_setfreq(FAR struct rn2xx3_dev_s *priv, uint32_t freq) { char freqstr[18]; ssize_t length; length = file_write(&priv->uart, "radio set freq ", sizeof("radio set freq ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(freqstr, sizeof(freqstr), "%lu\r\n", freq); length = file_write(&priv->uart, freqstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getfreq * * Description: * Get the operating frequency of the RN2xx3 in Hz. * ****************************************************************************/ static int rn2xx3_getfreq(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *freq) { ssize_t length; char response[20]; length = rn2xx3_getparam(priv, "radio get freq\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; *freq = strtoul(response, NULL, 10); return -errno; } /**************************************************************************** * Name: rn2xx3_settxpwr * * Description: * Set the transmit power of the RN2xx3. * ****************************************************************************/ static int rn2xx3_settxpwr(FAR struct rn2xx3_dev_s *priv, FAR int32_t *pwr) { char powerstr[16]; ssize_t length; int8_t txpwr; if (priv->model == RN2903) { txpwr = rn2xx3_txpwrlevel(pwr, RN2903_TXLVLS, array_len(RN2903_TXLVLS)); } else { txpwr = rn2xx3_txpwrlevel(pwr, RN2483_TXLVLS, array_len(RN2483_TXLVLS)); } length = file_write(&priv->uart, "radio set pwr ", sizeof("radio set pwr ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(powerstr, sizeof(powerstr), "%d\r\n", txpwr); length = file_write(&priv->uart, powerstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_gettxpwr * * Description: * Get the transmission power of the RN2xx3. * ****************************************************************************/ static int rn2xx3_gettxpwr(FAR struct rn2xx3_dev_s *priv, FAR int32_t *txpwr) { ssize_t length; char response[20]; int8_t txlevel; length = rn2xx3_getparam(priv, "radio get pwr\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; txlevel = strtol(response, NULL, 10); if (errno) { return -errno; } /* Convert transmission power level to dBm */ if (priv->model == RN2903) { *txpwr = rn2xx3_txpwrdbm(txlevel, RN2903_TXLVLS, array_len(RN2903_TXLVLS)); } else { *txpwr = rn2xx3_txpwrdbm(txlevel, RN2483_TXLVLS, array_len(RN2483_TXLVLS)); } if (*txpwr == INT32_MAX) { /* Some transmission power that was not expected was returned */ return -EIO; } return 0; } /**************************************************************************** * Name: rn2xx3_setbw * * Description: * Set the bandwidth of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setbw(FAR struct rn2xx3_dev_s *priv, uint32_t bw) { char bwstr[16]; ssize_t length; length = file_write(&priv->uart, "radio set bw ", sizeof("radio set bw ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(bwstr, sizeof(bwstr), "%lu\r\n", bw); length = file_write(&priv->uart, bwstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getbw * * Description: * Get the bandwidth of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getbw(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *bw) { int length; char response[10]; length = rn2xx3_getparam(priv, "radio get bw\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; *bw = strtoul(response, NULL, 10); return -errno; } /**************************************************************************** * Name: rn2xx3_setsf * * Description: * Set the spread factor of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setsf(FAR struct rn2xx3_dev_s *priv, uint8_t sf) { char sfstr[15]; ssize_t length; length = file_write(&priv->uart, "radio set sf ", sizeof("radio set sf ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(sfstr, sizeof(sfstr), "sf%u\r\n", sf); length = file_write(&priv->uart, sfstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getsf * * Description: * Get the spread factor of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getsf(FAR struct rn2xx3_dev_s *priv, FAR uint8_t *sf) { ssize_t length; char response[20]; length = rn2xx3_getparam(priv, "radio get sf\r\n", response, sizeof(response)); if (length < 0) { return length; } /* String should contain at least `sf` followed by one digit */ if (length < 3) { return -EIO; } errno = 0; *sf = strtoul(&response[2], NULL, 10); /* Skip 'sf' */ return -errno; } /**************************************************************************** * Name: rn2xx3_setprlen * * Description: * Set the preamble length of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setprlen(FAR struct rn2xx3_dev_s *priv, uint16_t prlen) { char prstr[16]; ssize_t length; length = file_write(&priv->uart, "radio set prlen ", sizeof("radio set prlen ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(prstr, sizeof(prstr), "%u\r\n", prlen); length = file_write(&priv->uart, prstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getprlen * * Description: * Get the preamble length of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getprlen(FAR struct rn2xx3_dev_s *priv, FAR uint16_t *prlen) { ssize_t length; char response[20]; length = rn2xx3_getparam(priv, "radio get prlen\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; *prlen = strtoul(response, NULL, 10); return -errno; } /**************************************************************************** * Name: rn2xx3_setmod * * Description: * Set the modulation of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setmod(FAR struct rn2xx3_dev_s *priv, enum rn2xx3_mod_e mod) { int err; char modstr[16]; ssize_t length; DEBUGASSERT(mod == RN2XX3_MOD_LORA || mod == RN2XX3_MOD_FSK); length = file_write(&priv->uart, "radio set mod ", sizeof("radio set mod ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(modstr, sizeof(modstr), "%s\r\n", MODULATIONS[mod]); length = file_write(&priv->uart, modstr, length); if (length < 0) { return length; } err = get_command_err(priv); if (err < 0) { return err; } priv->mod = mod; return 0; } /**************************************************************************** * Name: rn2xx3_getmod * * Description: * Get the modulation type of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getmod(FAR struct rn2xx3_dev_s *priv, FAR enum rn2xx3_mod_e *mod) { ssize_t length; char response[12]; length = rn2xx3_getparam(priv, "radio get mod\r\n", response, sizeof(response)); if (length < 0) { return length; } /* Only 'lora' and 'fsk' are valid return values from the radio */ if (strstr(response, "lora")) { *mod = RN2XX3_MOD_LORA; } else if (strstr(response, "fsk")) { *mod = RN2XX3_MOD_FSK; } else { return -EIO; } return 0; } /**************************************************************************** * Name: rn2xx3_setcr * * Description: * Set the coding rate of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setcr(FAR struct rn2xx3_dev_s *priv, enum rn2xx3_cr_e cr) { char crstr[8]; ssize_t length; length = file_write(&priv->uart, "radio set cr ", sizeof("radio set cr ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(crstr, sizeof(crstr), "%s\r\n", CODING_RATES[cr]); length = file_write(&priv->uart, crstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getcr * * Description: * Get the coding rate of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getcr(FAR struct rn2xx3_dev_s *priv, FAR enum rn2xx3_cr_e *cr) { ssize_t length; char response[10]; length = rn2xx3_getparam(priv, "radio get cr\r\n", response, sizeof(response)); if (length < 0) { return length; } /* Check if the response contains any of the valid options */ for (int i = 0; i < array_len(CODING_RATES); i++) { if (strstr(response, CODING_RATES[i])) { *cr = i; return 0; } } /* No valid response found */ return -EIO; } /**************************************************************************** * Name: rn2xx3_setsync * * Description: * Set the sync word of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setsync(FAR struct rn2xx3_dev_s *priv, uint64_t sync) { char syncstr[20]; ssize_t length; length = file_write(&priv->uart, "radio set sync ", sizeof("radio set sync ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(syncstr, sizeof(syncstr), "%llX\r\n", sync); length = file_write(&priv->uart, syncstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getsync * * Description: * Get the sync word of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getsync(FAR struct rn2xx3_dev_s *priv, FAR uint64_t *sync) { ssize_t length; char response[20]; length = rn2xx3_getparam(priv, "radio get sync\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; *sync = strtoull(response, NULL, 16); return -errno; } /**************************************************************************** * Name: rn2xx3_setbitrate * * Description: * Set the bit rate of the RN2xx3. * ****************************************************************************/ static int rn2xx3_setbitrate(FAR struct rn2xx3_dev_s *priv, uint32_t bitrate) { char bitstr[20]; ssize_t length; length = file_write(&priv->uart, "radio set bitrate ", sizeof("radio set bitrate ") - 1); if (length < 0) { return length; } /* Write actual parameter and end command */ length = snprintf(bitstr, sizeof(bitstr), "%lu\r\n", bitrate); length = file_write(&priv->uart, bitstr, length); if (length < 0) { return length; } return get_command_err(priv); } /**************************************************************************** * Name: rn2xx3_getbitrate * * Description: * Get the bit rate of the RN2xx3. * ****************************************************************************/ static int rn2xx3_getbitrate(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *bitrate) { ssize_t length; char response[20]; length = rn2xx3_getparam(priv, "radio get bitrate\r\n", response, sizeof(response)); if (length < 0) { return length; } errno = 0; *bitrate = strtoull(response, NULL, 16); return -errno; } /**************************************************************************** * Name: rn2xx3_set_mode_recv * * Description: * Put the RN2xx3 into indefinite receive mode. * ****************************************************************************/ static int rn2xx3_set_mode_recv(FAR struct rn2xx3_dev_s *priv) { ssize_t ret; static const char receive_cmd[] = "radio rx 0\r\n"; /* Pause the MAC layer */ ret = mac_pause(priv); if (ret < 0) { return ret; } /* Put the radio into indefinite receive mode */ ret = file_write(&priv->uart, receive_cmd, sizeof(receive_cmd) - 1); if (ret < 0) { return ret; } /* Check for okay receipt of command */ ret = get_command_err(priv); if (ret == 0) { priv->receiving = true; } return ret; } #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS /**************************************************************************** * Name: rn2xx3_open ****************************************************************************/ static int rn2xx3_open(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct rn2xx3_dev_s *priv = inode->i_private; int err; err = nxmutex_lock(&priv->devlock); if (err < 0) { return err; } /* Only one user at a time */ if (priv->crefs > 0) { err = -EBUSY; wlerr("Too many fds"); goto early_ret; } priv->crefs++; early_ret: nxmutex_unlock(&priv->devlock); return err; } #endif #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS /**************************************************************************** * Name: rn2xx3_close ****************************************************************************/ static int rn2xx3_close(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct rn2xx3_dev_s *priv = inode->i_private; int err; err = nxmutex_lock(&priv->devlock); if (err < 0) { return err; } priv->crefs--; nxmutex_unlock(&priv->devlock); return err; } #endif /**************************************************************************** * Name: rn2xx3_read * * Description: * Puts the RN2xx3 into continuous receive mode and waits for data to be * received before returning it. * ****************************************************************************/ static ssize_t rn2xx3_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct rn2xx3_dev_s *priv = inode->i_private; ssize_t ret = 0; ssize_t received = 0; char response[30]; char hex_byte[3] = { 0, 0 /* Extra space for null terminator */ }; /* Exclusive access */ ret = nxmutex_lock(&priv->devlock); if (ret < 0) { return ret; } /* If the file position is non-zero and we're not receiving, return 0 to * indicate the end of the last packet. */ if (filep->f_pos > 0 && !priv->receiving) { filep->f_pos = 0; return nxmutex_unlock(&priv->devlock); } /* If we're still marked as receiving and the file position in non-zero, we * can skip initiating receive mode and just continue parsing the packet. */ else if (filep->f_pos > 0 && priv->receiving) { goto parse_packet; } /* Otherwise, we're not receiving and haven't been receiving, so we * initiate a new receive mode. */ /* Flush data */ ret = radio_flush(priv); if (ret < 0) { wlerr("Could not flush RN2xx3\n"); goto early_ret; } ret = rn2xx3_set_mode_recv(priv); if (ret < 0) { wlerr("Could not enter receive mode\n"); goto early_ret; } /* Begin slowly parsing the response to check if we either received data or * had an error (timeout) * 9 is the size of 'radio_rx ' or 'radio_err'. */ for (uint8_t i = 0; i < 10; ) { ret = file_read(&priv->uart, &response[i], 10 - i); if (ret < 0) { goto early_ret; } i += ret; } response[10] = '\0'; /* Check for either error or received message */ if (strstr(response, "radio_err") != NULL) { ret = -EAGAIN; /* Timeout */ wlerr("Timeout during receive\n"); goto early_ret; } else if (strstr(response, "radio_rx ") == NULL) { /* Unknown error, we didn't expect this output */ wlerr("Unknown error during receive\n"); ret = -EIO; goto early_ret; } /* Here we've received some actual data, and that data is what's currently * pending to be read. * We read until the end of message is signalled with \r\n. */ parse_packet: hex_byte[2] = '\0'; /* Appease strtoul */ while (received < buflen) { ret = file_read(&priv->uart, &hex_byte, 2); if (ret < 2) { goto early_ret; } if (hex_byte[0] == '\r' && hex_byte[1] == '\n') { break; } /* Convert ASCII hex byte into real bytes to store in the buffer */ buffer[received] = strtoul(hex_byte, NULL, 16); received++; } /* We've received everything we can possibly receive, let the user know by * returning how much of the buffer we've filled. * If we've seen the end of packet indicator (\r\n), we'll mark the receive * as complete. However, if we only stopped to not exceed the buffer * length, we will leave our status as receiving to continue on the next * read call. */ if (hex_byte[0] == '\r' && hex_byte[1] == '\n') { priv->receiving = false; } ret = received; filep->f_pos += received; /* Record our position in received packet */ early_ret: /* We can't continue receiving if we had an issue */ if (ret < 0) { priv->receiving = false; } nxmutex_unlock(&priv->devlock); return ret; } /**************************************************************************** * Name: rn2xx3_write * * Description: * Transmits the data from the user provided buffer over radio as a * packet. * ****************************************************************************/ static ssize_t rn2xx3_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct rn2xx3_dev_s *priv = inode->i_private; ssize_t length = 0; size_t i; char response[20]; char hexbyte[3]; /* Two hex characters per byte, plus null terminator */ /* Exclusive access */ length = nxmutex_lock(&priv->devlock); if (length < 0) { return length; } /* Receives in progress are forfeited */ priv->receiving = false; /* Flush data */ length = radio_flush(priv); if (length < 0) { wlerr("Could not flush RN2xx3\n"); } /* Pause the MAC layer */ length = mac_pause(priv); if (length < 0) { wlerr("Could not pause MAC layer\n"); goto early_ret; } /* Start the transmission with the initial 'radio tx', followed by all the * data in the user buffer converted into hexadecimal characters one by * one */ length = file_write(&priv->uart, "radio tx ", sizeof("radio tx ") - 1); if (length < 0) { goto early_ret; } /* Print out every character up to the buffer size or the packet size limit */ for (i = 0; i < buflen && i < MAX_PKTSIZE[priv->mod]; i++) { snprintf(hexbyte, sizeof(hexbyte), "%02X", buffer[i]); length = file_write(&priv->uart, hexbyte, 2); if (length < 0) { goto early_ret; } } /* Complete the packet */ length = file_write(&priv->uart, "\r\n", sizeof("\r\n") - 1); if (length < 0) { goto early_ret; } /* Check for okay message */ length = get_command_err(priv); if (length < 0) { goto early_ret; } /* Check for radio_tx_ok */ length = read_line(priv, response, sizeof(response)); if (length < 0) { goto early_ret; } if (strstr(response, "radio_tx_ok") == NULL) { length = -EIO; goto early_ret; } /* All transmissions were nominal, so we let the user know that `i` bytes * were sent */ length = i; early_ret: nxmutex_unlock(&priv->devlock); return length; } /**************************************************************************** * Name: rn2xx3_ioctl * * Description: * Execute commands on the RN2xx3. ****************************************************************************/ static int rn2xx3_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { int err; FAR struct inode *inode = filep->f_inode; FAR struct rn2xx3_dev_s *priv = inode->i_private; /* Exclusive access */ err = nxmutex_lock(&priv->devlock); if (err < 0) { return err; } /* Receives in progress are forfeited */ priv->receiving = false; /* Flush radio to clear any lingering messages */ err = radio_flush(priv); if (err < 0) { wlerr("Couldn't flush radio: %d\n", err); goto early_ret; } switch (cmd) { case WLIOC_RESET: { err = rn2xx3_reset(priv); break; } case WLIOC_IQIEN: { err = rn2xx3_iqi_en(priv, arg); break; } case WLIOC_CRCEN: { err = rn2xx3_crc_en(priv, arg); break; } case WLIOC_GETSNR: { FAR int8_t *snr = (FAR int8_t *)(arg); DEBUGASSERT(snr != NULL); err = rn2xx3_getsnr(priv, snr); break; } case WLIOC_SETRADIOFREQ: { err = rn2xx3_setfreq(priv, arg); break; } case WLIOC_GETRADIOFREQ: { FAR uint32_t *freq = (FAR uint32_t *)(arg); DEBUGASSERT(freq != NULL); err = rn2xx3_getfreq(priv, freq); break; } case WLIOC_SETTXPOWERF: { FAR int32_t *txpwr = (FAR int32_t *)(arg); DEBUGASSERT(txpwr != NULL); err = rn2xx3_settxpwr(priv, txpwr); break; } case WLIOC_GETTXPOWERF: { FAR int32_t *txpwr = (FAR int32_t *)(arg); DEBUGASSERT(txpwr != NULL); err = rn2xx3_gettxpwr(priv, txpwr); break; } case WLIOC_SETBANDWIDTH: { err = rn2xx3_setbw(priv, arg); break; } case WLIOC_GETBANDWIDTH: { FAR uint32_t *bw = (FAR uint32_t *)(arg); DEBUGASSERT(bw != NULL); err = rn2xx3_getbw(priv, bw); break; } case WLIOC_SETSPREAD: { err = rn2xx3_setsf(priv, arg); break; } case WLIOC_GETSPREAD: { FAR uint8_t *sf = (FAR uint8_t *)(arg); DEBUGASSERT(sf != NULL); err = rn2xx3_getsf(priv, sf); break; } case WLIOC_SETPRLEN: { err = rn2xx3_setprlen(priv, arg); break; } case WLIOC_GETPRLEN: { FAR uint16_t *prlen = (FAR uint16_t *)(arg); DEBUGASSERT(prlen != NULL); err = rn2xx3_getprlen(priv, prlen); break; } case WLIOC_SETMOD: { err = rn2xx3_setmod(priv, arg); break; } case WLIOC_GETMOD: { FAR enum rn2xx3_mod_e *mod = (FAR enum rn2xx3_mod_e *)(arg); DEBUGASSERT(mod != NULL); err = rn2xx3_getmod(priv, mod); break; } case WLIOC_SETSYNC: { FAR uint64_t *syncword = (FAR uint64_t *)(arg); DEBUGASSERT(syncword != NULL); err = rn2xx3_setsync(priv, *syncword); break; } case WLIOC_GETSYNC: { FAR uint64_t *sync = (FAR uint64_t *)(arg); DEBUGASSERT(sync != NULL); err = rn2xx3_getsync(priv, sync); break; } case WLIOC_SETBITRATE: { err = rn2xx3_setbitrate(priv, arg); break; } case WLIOC_GETBITRATE: { FAR uint32_t *bitrate = (FAR uint32_t *)(arg); DEBUGASSERT(bitrate != NULL); err = rn2xx3_getbitrate(priv, bitrate); break; } case WLIOC_SETCODERATE: { err = rn2xx3_setcr(priv, arg); break; } case WLIOC_GETCODERATE: { FAR enum rn2xx3_cr_e *cr = (FAR enum rn2xx3_cr_e *)(arg); DEBUGASSERT(cr != NULL); err = rn2xx3_getcr(priv, cr); break; } default: { err = -EINVAL; break; } } early_ret: nxmutex_unlock(&priv->devlock); return err; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: rn2xx3_register * * Description: * Register the RN2xx3 LoRa transceiver driver. * * Arguments: * devpath - The device path to use for the driver * uartpath - The path to the UART character driver connected to the * transceiver * ****************************************************************************/ int rn2xx3_register(FAR const char *devpath, FAR const char *uartpath) { FAR struct rn2xx3_dev_s *priv = NULL; int err = 0; int retries = 0; DEBUGASSERT(uartpath != NULL); DEBUGASSERT(devpath != NULL); /* Initialize device structure */ priv = kmm_zalloc(sizeof(struct rn2xx3_dev_s)); if (priv == NULL) { wlerr("Failed to allocate instance of RN2xx3 driver."); return -ENOMEM; } priv->receiving = false; /* Not receiving yet */ priv->mod = RN2XX3_MOD_LORA; /* Starts in LoRa */ /* Initialize mutex */ err = nxmutex_init(&priv->devlock); if (err < 0) { wlerr("Failed to initialize mutex for RN2xx3 device: %d\n", err); goto free_mem; } /* Open UART interface for use */ err = file_open(&priv->uart, uartpath, O_RDWR | O_CLOEXEC); if (err < 0) { wlerr("Failed to open UART interface %s for RN2xx3 driver: %d\n", uartpath, err); goto destroy_mutex; } /* Check model type of the transceiver. Try a few times since the * transceiver has some boot time delay. */ do { err = rn2xx3_getmodel(priv, &priv->model); retries++; usleep(10000); /* 10ms between tries */ } while (retries < 3 && err < 0); if (err < 0) { wlerr("Could not detect model: %d\n", err); goto close_file; } wlinfo("Detected model %s\n", priv->model == RN2903 ? "RN2903" : "RN2483"); /* Register driver */ err = register_driver(devpath, &g_rn2xx3fops, 0666, priv); if (err < 0) { wlerr("Failed to register RN2xx3 driver: %d\n", err); goto close_file; } /* Cleanup items on error */ if (err < 0) { close_file: file_close(&priv->uart); destroy_mutex: nxmutex_destroy(&priv->devlock); free_mem: kmm_free(priv); } return err; }