[posix] add radio url (#4935)

Radio url provides a unified interface for users to specify the radio
peripheral device and arguments to the OpenThread core stack.
This commit is contained in:
Jiacheng Guo
2020-05-30 08:34:29 +08:00
committed by GitHub
parent b0b2591bc0
commit 115616c6bf
20 changed files with 497 additions and 220 deletions
+1
View File
@@ -281,6 +281,7 @@ LOCAL_SRC_FILES := \
src/posix/platform/misc.cpp \
src/posix/platform/netif.cpp \
src/posix/platform/radio.cpp \
src/posix/platform/radio_url.cpp \
src/posix/platform/settings.cpp \
src/posix/platform/spi_interface.cpp \
src/posix/platform/system.cpp \
+5 -4
View File
@@ -93,12 +93,12 @@ EOF
echo "Step 2. Start retrieving dataset from Radio..."
RADIO_NCP_PATH="$(pwd)/$(ls output/*linux*/bin/ot-ncp-ftd)"
"$(pwd)/$(ls output/posix/*linux*/bin/ot-ncp)" -n --radio-version --ncp-dataset -- "${RADIO_NCP_PATH}" 1
"$(pwd)/$(ls output/posix/*linux*/bin/ot-ncp)" -n --radio-version "spinel+hdlc+forkpty://${RADIO_NCP_PATH}?arg=1&ncp-dataset=1"
echo "Step 3. Start posix app and check whether PAN dataset is the same..."
RADIO_RCP_PATH="$(pwd)/$(ls output/*linux*/bin/ot-rcp)"
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli) ${RADIO_RCP_PATH} 1"
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli) spinel+hdlc+forkpty://${RADIO_RCP_PATH}?arg=1"
expect <<EOF
spawn ${OT_CLI_CMD}
@@ -126,8 +126,9 @@ expect eof
EOF
echo "Step 4. Start posix app and check whether it can get radio firmware version..."
RADIO_VERSION="$("$(pwd)/$(ls output/posix/*linux*/bin/ot-cli)" -n --radio-version --ncp-dataset -- "${RADIO_RCP_PATH}" 1)" || true
test -n "${RADIO_VERSION}"
RADIO_VERSION="$("$(pwd)/$(ls output/posix/*linux*/bin/ot-cli)" -n --radio-version "spinel+hdlc+forkpty://${RADIO_RCP_PATH}?arg=1&ncp-dataset=1")" || true
echo "${RADIO_VERSION}"
test -n "{RADIO_VERSION}"
}
main()
+5 -7
View File
@@ -81,12 +81,10 @@ check()
# Cover setting a valid network interface name.
readonly VALID_NETIF_NAME="wan$(date +%H%M%S)"
local options=(
'--max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26'
)
RADIO_URL="spinel+hdlc+uart://${CORE_PTY}?max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26"
if [[ ${DAEMON} == 1 ]]; then
sudo "$(pwd)/$(ls output/posix/*linux*/bin/ot-daemon)" "${options[@]}" -I "${VALID_NETIF_NAME}" "${CORE_PTY}" &
sudo "$(pwd)/$(ls output/posix/*linux*/bin/ot-daemon)" -I "${VALID_NETIF_NAME}" "${RADIO_URL}" &
sleep 1
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-ctl)"
sudo "${OT_CLI_CMD}" panid 0xface | grep 'Done' || die 'failed to set panid with ot-ctl'
@@ -96,14 +94,14 @@ check()
sudo "${OT_CLI_CMD}" factoryreset
else
OT_CLI="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli)"
sudo "${OT_CLI}" -I "${VALID_NETIF_NAME}" -n "${CORE_PTY}"
sudo "${OT_CLI}" -I "${VALID_NETIF_NAME}" -n "${RADIO_URL}"
# Cover setting a too long(max is 15 characters) network interface name.
# Expect exit code to be 2(OT_EXIT_INVALID_ARGUMENTS).
readonly INVALID_NETIF_NAME="wan0123456789123"
sudo "${OT_CLI}" -I "${INVALID_NETIF_NAME}" -n "${CORE_PTY}" || test $? = 2
sudo "${OT_CLI}" -I "${INVALID_NETIF_NAME}" -n "${RADIO_URL}" || test $? = 2
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli) ${CORE_PTY}"
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli) ${RADIO_URL}"
fi
sudo expect <<EOF | tee "${OT_OUTPUT}" &
+6 -115
View File
@@ -75,6 +75,7 @@
#error "Unknown posix app type!"
#endif
#include <common/code_utils.hpp>
#include <common/logging.hpp>
#include <lib/platform/exit_code.h>
#include <openthread/openthread-system.h>
@@ -102,88 +103,31 @@ void __gcov_flush();
enum
{
ARG_PRINT_RADIO_VERSION = 1001,
ARG_NO_RADIO_RESET = 1002,
ARG_RESTORE_NCP_DATASET = 1003,
ARG_SPI_GPIO_INT_DEV = 1011,
ARG_SPI_GPIO_INT_LINE = 1012,
ARG_SPI_GPIO_RESET_DEV = 1013,
ARG_SPI_GPIO_RESET_LINE = 1014,
ARG_SPI_MODE = 1015,
ARG_SPI_SPEED = 1016,
ARG_SPI_CS_DELAY = 1017,
ARG_SPI_RESET_DELAY = 1018,
ARG_SPI_ALIGN_ALLOWANCE = 1019,
ARG_SPI_SMALL_PACKET = 1020,
ARG_MAX_POWER_TABLE = 1021,
};
static const struct option kOptions[] = {{"debug-level", required_argument, NULL, 'd'},
{"dry-run", no_argument, NULL, 'n'},
{"help", no_argument, NULL, 'h'},
{"interface-name", required_argument, NULL, 'I'},
{"no-reset", no_argument, NULL, ARG_NO_RADIO_RESET},
{"radio-version", no_argument, NULL, ARG_PRINT_RADIO_VERSION},
{"ncp-dataset", no_argument, NULL, ARG_RESTORE_NCP_DATASET},
{"time-speed", required_argument, NULL, 's'},
{"verbose", no_argument, NULL, 'v'},
#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
{"max-power-table", required_argument, NULL, ARG_MAX_POWER_TABLE},
#endif
#if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI
{"gpio-int-dev", required_argument, NULL, ARG_SPI_GPIO_INT_DEV},
{"gpio-int-line", required_argument, NULL, ARG_SPI_GPIO_INT_LINE},
{"gpio-reset-dev", required_argument, NULL, ARG_SPI_GPIO_RESET_DEV},
{"gpio-reset-line", required_argument, NULL, ARG_SPI_GPIO_RESET_LINE},
{"spi-mode", required_argument, NULL, ARG_SPI_MODE},
{"spi-speed", required_argument, NULL, ARG_SPI_SPEED},
{"spi-cs-delay", required_argument, NULL, ARG_SPI_CS_DELAY},
{"spi-reset-delay", required_argument, NULL, ARG_SPI_RESET_DELAY},
{"spi-align-allowance", required_argument, NULL, ARG_SPI_ALIGN_ALLOWANCE},
{"spi-small-packet", required_argument, NULL, ARG_SPI_SMALL_PACKET},
#endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI
{0, 0, 0, 0}};
static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode)
{
fprintf(aStream,
"Syntax:\n"
" %s [Options] NodeId|Device|Command [DeviceConfig|CommandArgs]\n"
" %s [Options] RadioURL\n"
"Options:\n"
" -d --debug-level Debug level of logging.\n"
" -h --help Display this usage information.\n"
" -I --interface-name name Thread network interface name.\n"
" -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n"
" --no-reset Do not send Spinel reset command to RCP on initialization.\n"
" --radio-version Print radio firmware version.\n"
" --ncp-dataset Retrieve and save NCP dataset to file.\n"
" -s --time-speed factor Time speed up factor.\n"
" -v --verbose Also log to stderr.\n",
aProgramName);
#if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI
fprintf(aStream,
" --gpio-int-dev[=gpio-device-path]\n"
" Specify a path to the Linux sysfs-exported GPIO device for the\n"
" `I̅N̅T̅` pin. If not specified, `SPI` interface will fall back to\n"
" polling, which is inefficient.\n"
" --gpio-int-line[=line-offset]\n"
" The offset index of `I̅N̅T̅` pin for the associated GPIO device.\n"
" If not specified, `SPI` interface will fall back to polling,\n"
" which is inefficient.\n"
" --gpio-reset-dev[=gpio-device-path]\n"
" Specify a path to the Linux sysfs-exported GPIO device for the\n"
" `R̅E̅S̅` pin.\n"
" --gpio-reset-line[=line-offset]"
" The offset index of `R̅E̅S̅` pin for the associated GPIO device.\n"
" --spi-mode[=mode] Specify the SPI mode to use (0-3).\n"
" --spi-speed[=hertz] Specify the SPI speed in hertz.\n"
" --spi-cs-delay[=usec] Specify the delay after C̅S̅ assertion, in µsec.\n"
" --spi-reset-delay[=ms] Specify the delay after R̅E̅S̅E̅T̅ assertion, in milliseconds.\n"
" --spi-align-allowance[=n] Specify the maximum number of 0xFF bytes to clip from start of\n"
" MISO frame. Max value is 16.\n"
" --spi-small-packet=[n] Specify the smallest packet we can receive in a single transaction.\n"
" (larger packets will require two transactions). Default value is 32.\n");
#endif
#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
fprintf(aStream,
" --max-power-table Max power for channels in ascending order separated by commas,\n"
@@ -191,6 +135,7 @@ static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode)
" the last value will be applied to all remaining channels.\n"
" Special value 0x7f disables a channel.\n");
#endif
fprintf(aStream, "%s", otSysGetRadioUrlHelpString());
exit(aExitCode);
}
@@ -198,15 +143,8 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig)
{
memset(aConfig, 0, sizeof(*aConfig));
aConfig->mPlatformConfig.mSpeedUpFactor = 1;
aConfig->mPlatformConfig.mResetRadio = true;
aConfig->mPlatformConfig.mSpiSpeed = OT_PLATFORM_CONFIG_SPI_DEFAULT_SPEED_HZ;
aConfig->mPlatformConfig.mSpiCsDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_CS_DELAY_US;
aConfig->mPlatformConfig.mSpiResetDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_RESET_DELAY_MS;
aConfig->mPlatformConfig.mSpiAlignAllowance = OT_PLATFORM_CONFIG_SPI_DEFAULT_ALIGN_ALLOWANCE;
aConfig->mPlatformConfig.mSpiSmallPacketSize = OT_PLATFORM_CONFIG_SPI_DEFAULT_SMALL_PACKET_SIZE;
aConfig->mPlatformConfig.mSpiMode = OT_PLATFORM_CONFIG_SPI_DEFAULT_MODE;
aConfig->mLogLevel = OT_LOG_LEVEL_CRIT;
aConfig->mPlatformConfig.mSpeedUpFactor = 1;
aConfig->mLogLevel = OT_LOG_LEVEL_CRIT;
optind = 1;
@@ -253,47 +191,6 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig)
case ARG_PRINT_RADIO_VERSION:
aConfig->mPrintRadioVersion = true;
break;
case ARG_NO_RADIO_RESET:
aConfig->mPlatformConfig.mResetRadio = false;
break;
case ARG_RESTORE_NCP_DATASET:
aConfig->mPlatformConfig.mRestoreDatasetFromNcp = true;
break;
case ARG_SPI_GPIO_INT_DEV:
aConfig->mPlatformConfig.mSpiGpioIntDevice = optarg;
break;
case ARG_SPI_GPIO_INT_LINE:
aConfig->mPlatformConfig.mSpiGpioIntLine = (uint8_t)atoi(optarg);
break;
case ARG_SPI_GPIO_RESET_DEV:
aConfig->mPlatformConfig.mSpiGpioResetDevice = optarg;
break;
case ARG_SPI_GPIO_RESET_LINE:
aConfig->mPlatformConfig.mSpiGpioResetLine = (uint8_t)atoi(optarg);
break;
case ARG_SPI_MODE:
aConfig->mPlatformConfig.mSpiMode = (uint8_t)atoi(optarg);
break;
case ARG_SPI_SPEED:
aConfig->mPlatformConfig.mSpiSpeed = (uint32_t)atoi(optarg);
break;
case ARG_SPI_CS_DELAY:
aConfig->mPlatformConfig.mSpiCsDelay = (uint16_t)atoi(optarg);
break;
case ARG_SPI_RESET_DELAY:
aConfig->mPlatformConfig.mSpiResetDelay = (uint32_t)atoi(optarg);
break;
case ARG_SPI_ALIGN_ALLOWANCE:
aConfig->mPlatformConfig.mSpiAlignAllowance = (uint8_t)atoi(optarg);
break;
case ARG_SPI_SMALL_PACKET:
aConfig->mPlatformConfig.mSpiSmallPacketSize = (uint8_t)atoi(optarg);
break;
#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
case ARG_MAX_POWER_TABLE:
aConfig->mPlatformConfig.mMaxPowerTable = optarg;
break;
#endif
case '?':
PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS);
break;
@@ -307,13 +204,7 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig)
{
PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS);
}
aConfig->mPlatformConfig.mRadioFile = aArgVector[optind];
if (optind + 1 < aArgCount)
{
aConfig->mPlatformConfig.mRadioConfig = aArgVector[optind + 1];
}
aConfig->mPlatformConfig.mRadioUrl = aArgVector[optind];
}
static otInstance *InitInstance(int aArgCount, char *aArgVector[])
+1
View File
@@ -67,6 +67,7 @@ add_library(openthread-posix
misc.cpp
netif.cpp
radio.cpp
radio_url.cpp
settings.cpp
spi_interface.cpp
system.cpp
+17 -1
View File
@@ -50,6 +50,7 @@ libopenthread_posix_a_SOURCES = \
misc.cpp \
netif.cpp \
radio.cpp \
radio_url.cpp \
settings.cpp \
spi_interface.cpp \
system.cpp \
@@ -62,6 +63,7 @@ noinst_HEADERS = \
hdlc_interface.hpp \
openthread-posix-config.h \
platform-posix.h \
radio_url.hpp \
$(NULL)
openthread_HEADERS = \
@@ -75,7 +77,7 @@ if OPENTHREAD_BUILD_COVERAGE
CLEANFILES = $(wildcard *.gcda *.gcno)
endif # OPENTHREAD_BUILD_COVERAGE
check_PROGRAMS = test-settings
check_PROGRAMS = test-settings test-radio-url
test_settings_CPPFLAGS = \
-I$(top_srcdir)/include \
@@ -90,8 +92,22 @@ test_settings_SOURCES = \
settings.cpp \
$(NULL)
test_radio_url_CPPFLAGS = \
-I$(top_srcdir)/include \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/core \
-I$(top_srcdir)/src/posix/platform/include \
-DOPENTHREAD_CONFIG_LOG_PLATFORM=0 \
-DSELF_TEST \
$(NULL)
test_radio_url_SOURCES = \
radio_url.cpp \
$(NULL)
TESTS = \
test-settings \
test-radio-url \
$(NULL)
include $(abs_top_nlbuild_autotools_dir)/automake/post.am
+35 -39
View File
@@ -138,30 +138,30 @@ HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback,
{
}
otError HdlcInterface::Init(const otPlatformConfig &aPlatformConfig)
otError HdlcInterface::Init(Arguments &aArguments)
{
otError error = OT_ERROR_NONE;
struct stat st;
VerifyOrExit(mSockFd == -1, error = OT_ERROR_ALREADY);
VerifyOrDie(stat(aPlatformConfig.mRadioFile, &st) == 0, OT_EXIT_INVALID_ARGUMENTS);
VerifyOrDie(stat(aArguments.GetPath(), &st) == 0, OT_EXIT_INVALID_ARGUMENTS);
if (S_ISCHR(st.st_mode))
{
mSockFd = OpenFile(aPlatformConfig.mRadioFile, aPlatformConfig.mRadioConfig);
mSockFd = OpenFile(aArguments.GetPath(), aArguments);
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
else if (S_ISREG(st.st_mode))
{
mSockFd = ForkPty(aPlatformConfig.mRadioFile, aPlatformConfig.mRadioConfig);
mSockFd = ForkPty(aArguments.GetPath(), aArguments.GetValue("arg"));
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#endif // OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
else
{
otLogCritPlat("Radio file '%s' not supported", aPlatformConfig.mRadioFile);
otLogCritPlat("Radio file '%s' not supported", aArguments.GetPath());
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
@@ -407,10 +407,12 @@ exit:
return error;
}
int HdlcInterface::OpenFile(const char *aFile, const char *aConfig)
int HdlcInterface::OpenFile(const char *aFile, Arguments &aArguments)
{
int fd = -1;
int rval = 0;
int fd = -1;
int rval = 0;
const char *parity = aArguments.GetValue("uart-parity");
uint32_t baudrate = 115200;
fd = open(aFile, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
if (fd == -1)
@@ -423,10 +425,8 @@ int HdlcInterface::OpenFile(const char *aFile, const char *aConfig)
{
struct termios tios;
unsigned int speed = 115200;
int cstopb = 1;
char parity = 'N';
char flow = 'N';
unsigned int speed = 115200;
int stopBit = 1;
VerifyOrExit((rval = tcgetattr(fd, &tios)) == 0, OT_NOOP);
@@ -434,29 +434,29 @@ int HdlcInterface::OpenFile(const char *aFile, const char *aConfig)
tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
// example: 115200N1H
if (aConfig != NULL)
if (parity)
{
sscanf(aConfig, "%u%c%d%c", &speed, &parity, &cstopb, &flow);
if (strncmp(parity, "odd", 3) == 0)
{
tios.c_cflag |= PARENB;
tios.c_cflag |= PARODD;
}
else if (strncmp(parity, "even", 4) == 0)
{
tios.c_cflag |= PARENB;
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
}
switch (parity)
if (aArguments.GetValue("uart-stop"))
{
case 'N':
break;
case 'E':
tios.c_cflag |= PARENB;
break;
case 'O':
tios.c_cflag |= (PARENB | PARODD);
break;
default:
// not supported
DieNow(OT_EXIT_INVALID_ARGUMENTS);
break;
stopBit = atoi(aArguments.GetValue("uart-stop"));
}
switch (cstopb)
switch (stopBit)
{
case 1:
tios.c_cflag &= static_cast<unsigned long>(~CSTOPB);
@@ -469,7 +469,11 @@ int HdlcInterface::OpenFile(const char *aFile, const char *aConfig)
break;
}
switch (speed)
if (aArguments.GetValue("baudrate"))
{
baudrate = static_cast<uint32_t>(atoi(aArguments.GetValue("baudrate")));
}
switch (baudrate)
{
case 9600:
speed = B9600;
@@ -556,17 +560,9 @@ int HdlcInterface::OpenFile(const char *aFile, const char *aConfig)
break;
}
switch (flow)
if (aArguments.GetValue("uart-flow-control") != NULL)
{
case 'N':
break;
case 'H':
tios.c_cflag |= CRTSCTS;
break;
default:
// not supported
DieNow(OT_EXIT_INVALID_ARGUMENTS);
break;
}
VerifyOrExit((rval = cfsetspeed(&tios, static_cast<speed_t>(speed))) == 0, perror("cfsetspeed"));
+3 -3
View File
@@ -74,14 +74,14 @@ public:
*
* @note This method should be called before reading and sending spinel frames to the interface.
*
* @param[in] aPlatformConfig Platform configuration structure.
* @param[in] aArguments Arguments parsed from radio url.
*
* @retval OT_ERROR_NONE The interface is initialized successfully
* @retval OT_ERROR_ALREADY The interface is already initialized.
* @retval OT_ERROR_INVALID_ARGS The UART device or executable cannot be found or failed to open/run.
*
*/
otError Init(const otPlatformConfig &aPlatformConfig);
otError Init(Arguments &aArguments);
/**
* This method deinitializes the interface to the RCP.
@@ -198,7 +198,7 @@ private:
static void HandleHdlcFrame(void *aContext, otError aError);
void HandleHdlcFrame(otError aError);
static int OpenFile(const char *aFile, const char *aConfig);
static int OpenFile(const char *aFile, Arguments &aArguments);
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
static int ForkPty(const char *aCommand, const char *aArguments);
#endif
@@ -38,6 +38,7 @@
#include <setjmp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/select.h>
#include <openthread/error.h>
@@ -69,25 +70,9 @@ enum
*/
typedef struct otPlatformConfig
{
uint64_t mNodeId; ///< Unique node ID.
uint32_t mSpeedUpFactor; ///< Speed up factor.
const char *mInterfaceName; ///< Thread network interface name.
const char *mRadioFile; ///< Radio file path.
const char *mRadioConfig; ///< Radio configurations.
const char *mMaxPowerTable; ///< Radio max transmit power table.
bool mResetRadio; ///< Whether to reset RCP when initializing.
bool mRestoreDatasetFromNcp; ///< Whether to retrieve dataset from NCP and save to file.
char * mSpiGpioIntDevice; ///< Path to the Linux GPIO character device for the `I̅N̅T̅` pin.
char * mSpiGpioResetDevice; ///< Path to the Linux GPIO character device for the `R̅E̅S̅E̅T̅` pin.
uint8_t mSpiGpioIntLine; ///< Line index of the `I̅N̅T̅` pin for the associated GPIO character device.
uint8_t mSpiGpioResetLine; ///< Line index of the `R̅E̅S̅E̅T̅` pin for the associated GPIO character device.
uint8_t mSpiMode; ///< SPI mode to use (0-3).
uint32_t mSpiSpeed; ///< SPI speed in hertz.
uint32_t mSpiResetDelay; ///< The delay after R̅E̅S̅E̅T̅ assertion, in miliseconds.
uint16_t mSpiCsDelay; ///< The delay after SPI C̅S̅ assertion, in µsec.
uint8_t mSpiAlignAllowance; ///< Maximum number of 0xFF bytes to clip from start of MISO frame.
uint8_t mSpiSmallPacketSize; ///< Smallest SPI packet size we can receive in a single transaction.
uint32_t mSpeedUpFactor; ///< Speed up factor.
const char *mInterfaceName; ///< Thread network interface name.
const char *mRadioUrl; ///< Radio url.
} otPlatformConfig;
/**
@@ -156,6 +141,14 @@ int otSysMainloopPoll(otSysMainloopContext *aMainloop);
*/
void otSysMainloopProcess(otInstance *aInstance, const otSysMainloopContext *aMainloop);
/**
* This method returns the radio url help string.
*
* @returns the radio url help string.
*
*/
const char *otSysGetRadioUrlHelpString(void);
#ifdef __cplusplus
} // end of extern "C"
#endif
+2 -1
View File
@@ -50,6 +50,7 @@
#include "common/logging.hpp"
#include "radio_url.hpp"
#include "lib/platform/exit_code.h"
/**
@@ -162,7 +163,7 @@ void platformAlarmAdvanceNow(uint64_t aDelta);
* @param[in] aPlatformConfig Platform configuration structure.
*
*/
void platformRadioInit(const otPlatformConfig *aPlatformConfig);
void platformRadioInit(otPosixRadioArguments *aArguments);
/**
* This function shuts down the radio service used by OpenThread.
+12 -8
View File
@@ -94,18 +94,22 @@ void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable)
SuccessOrDie(sRadioSpinel.SetPromiscuous(aEnable));
}
void platformRadioInit(const otPlatformConfig *aPlatformConfig)
void platformRadioInit(otPosixRadioArguments *aArguments)
{
ot::Posix::Arguments *args = reinterpret_cast<ot::Posix::Arguments *>(aArguments);
bool resetRadio = (args->GetValue("no-reset") == NULL);
bool restoreDataset = (args->GetValue("ncp-dataset") != NULL);
#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
uint8_t channel = ot::Radio::kChannelMin;
int8_t power = ot::Posix::MaxPowerTable::kPowerDefault;
uint8_t channel = ot::Radio::kChannelMin;
int8_t power = ot::Posix::MaxPowerTable::kPowerDefault;
const char *maxPowerTable = args->GetValue("max-power-table");
if (aPlatformConfig->mMaxPowerTable != NULL)
if (maxPowerTable != NULL)
{
const char *str = NULL;
for (str = strtok(const_cast<char *>(aPlatformConfig->mMaxPowerTable), ",");
str != NULL && channel <= ot::Radio::kChannelMax; str = strtok(NULL, ","))
for (str = strtok(const_cast<char *>(maxPowerTable), ","); str != NULL && channel <= ot::Radio::kChannelMax;
str = strtok(NULL, ","))
{
power = static_cast<int8_t>(strtol(str, NULL, 0));
sMaxPowerTable.SetTransmitPower(channel++, power);
@@ -122,8 +126,8 @@ void platformRadioInit(const otPlatformConfig *aPlatformConfig)
}
#endif
SuccessOrDie(sRadioSpinel.GetSpinelInterface().Init(*aPlatformConfig));
sRadioSpinel.Init(aPlatformConfig->mResetRadio, aPlatformConfig->mRestoreDatasetFromNcp);
SuccessOrDie(sRadioSpinel.GetSpinelInterface().Init(*args));
sRadioSpinel.Init(resetRadio, restoreDataset);
}
void platformRadioDeinit(void)
+224
View File
@@ -0,0 +1,224 @@
/*
* Copyright (c) 2020, The OpenThread Authors.
* 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.
*/
#include "posix/platform/radio_url.hpp"
#include <stdio.h>
#include <string.h>
#include <openthread/openthread-system.h>
#include "core/common/code_utils.hpp"
#include "posix/platform/platform-posix.h"
const char *otSysGetRadioUrlHelpString(void)
{
#if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI
#define RADIO_URL_HELP_BUS \
"Radio URL:\n" \
"spinel+spi://${PATH_TO_SPI_DEVICE}?${arguments}\n" \
"arguments:\n" \
" gpio-int-device[=gpio-device-path]\n" \
" Specify a path to the Linux sysfs-exported GPIO device for the\n" \
" `I̅N̅T̅` pin. If not specified, `SPI` interface will fall back to\n" \
" polling, which is inefficient.\n" \
" gpio-int-line[=line-offset]\n" \
" The offset index of `I̅N̅T̅` pin for the associated GPIO device.\n" \
" If not specified, `SPI` interface will fall back to polling,\n" \
" which is inefficient.\n" \
" gpio-reset-dev[=gpio-device-path]\n" \
" Specify a path to the Linux sysfs-exported GPIO device for the\n" \
" `R̅E̅S̅` pin.\n" \
" gpio-reset-line[=line-offset]" \
" The offset index of `R̅E̅S̅` pin for the associated GPIO device.\n" \
" spi-mode[=mode] Specify the SPI mode to use (0-3).\n" \
" spi-speed[=hertz] Specify the SPI speed in hertz.\n" \
" spi-cs-delay[=usec] Specify the delay after C̅S̅ assertion, in µsec.\n" \
" spi-reset-delay[=ms] Specify the delay after R̅E̅S̅E̅T̅ assertion, in milliseconds.\n" \
" spi-align-allowance[=n] Specify the maximum number of 0xFF bytes to clip from start of\n" \
" MISO frame. Max value is 16.\n" \
" spi-small-packet=[n] Specify the smallest packet we can receive in a single transaction.\n" \
" (larger packets will require two transactions). Default value is 32.\n"
#else
#define RADIO_URL_HELP_BUS \
"Radio URL:\n" \
"spinel+hdlc+uart://${PATH_TO_UART_DEVICE}?${arguments} for real uart device\n" \
"spinel+hdlc+fortpty://${PATH_TO_UART_DEVICE}?${arguments} for forking a pty subprocess.\n" \
"arguments:\n" \
" uart-parity[=even|odd] Uart parity config, optional.\n" \
" uart-stop[=number-of-bits] Uart stop bit, default is 1.\n" \
" baudrate[=baudrate] Uart baud rate, default is 115200.\n" \
" arg[=argument string] Command line arguments for subprocess, can be repeated.\n"
#endif
return RADIO_URL_HELP_BUS " no-reset Skip resetting the radio device.\n"
" ncp-dataset Retrieve dataset from ncp.\n"
" max-power-table Max power table for each channel, splitted by comma.\n";
}
namespace ot {
namespace Posix {
Arguments::Arguments(const char *aUrl)
{
char *url = &mUrl[0];
mPath = NULL;
mStart = NULL;
mEnd = NULL;
VerifyOrExit(aUrl != NULL, OT_NOOP);
VerifyOrExit(strnlen(aUrl, sizeof(mUrl)) < sizeof(mUrl), OT_NOOP);
strncpy(mUrl, aUrl, sizeof(mUrl) - 1);
url = strstr(url, "://");
VerifyOrExit(url != NULL, OT_NOOP);
url += sizeof("://") - 1;
mPath = url;
mStart = strstr(url, "?");
if (mStart != NULL)
{
mStart[0] = '\0';
mStart++;
mEnd = mStart + strlen(mStart);
for (char *cur = strtok(mStart, "&"); cur != NULL; cur = strtok(NULL, "&"))
;
}
else
{
mEnd = url + strlen(url);
mStart = mEnd;
}
exit:
return;
}
const char *Arguments::GetValue(const char *aName, const char *aLastValue)
{
const char * rval = NULL;
const size_t len = strlen(aName);
char * start = (aLastValue == NULL ? mStart : (const_cast<char *>(aLastValue) + strlen(aLastValue) + 1));
while (start < mEnd)
{
char *last = NULL;
if (!strncmp(aName, start, len))
{
if (start[len] == '=')
{
ExitNow(rval = &start[len + 1]);
}
else if (start[len] == '&' || start[len] == '\0')
{
ExitNow(rval = "");
}
}
last = start;
start = last + strlen(last) + 1;
}
exit:
return rval;
}
} // namespace Posix
} // namespace ot
#ifndef SELF_TEST
#define SELF_TEST 0
#endif
#if SELF_TEST
#include <assert.h>
void TestSimple()
{
char url[] = "spinel:///dev/ttyUSB0?baudrate=115200";
ot::Posix::Arguments args(url);
assert(!strcmp(args.GetPath(), "/dev/ttyUSB0"));
assert(!strcmp(args.GetValue("baudrate"), "115200"));
printf("PASS %s\r\n", __func__);
}
void TestSimpleNoArguments()
{
char url[] = "spinel:///dev/ttyUSB0";
ot::Posix::Arguments args(url);
assert(!strcmp(args.GetPath(), "/dev/ttyUSB0"));
printf("PASS %s\r\n", __func__);
}
void TestMultipleProtocols()
{
char url[] = "spinel+spi:///dev/ttyUSB0?baudrate=115200";
ot::Posix::Arguments args(url);
assert(!strcmp(args.GetPath(), "/dev/ttyUSB0"));
assert(!strcmp(args.GetValue("baudrate"), "115200"));
printf("PASS %s\r\n", __func__);
}
void TestMultipleProtocolsAndDuplicateParameters()
{
char url[] = "spinel+exec:///path/to/ot-rcp?arg=1&arg=arg2&arg=3";
ot::Posix::Arguments args(url);
const char * arg = NULL;
assert(!strcmp(args.GetPath(), "/path/to/ot-rcp"));
arg = args.GetValue("arg");
assert(!strcmp(arg, "1"));
arg = args.GetValue("arg", arg);
assert(!strcmp(arg, "arg2"));
arg = args.GetValue("arg", arg);
assert(!strcmp(arg, "3"));
printf("PASS %s\r\n", __func__);
}
int main()
{
TestSimple();
TestSimpleNoArguments();
TestMultipleProtocols();
TestMultipleProtocolsAndDuplicateParameters();
return 0;
}
#endif // SELF_TEST
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2020, The OpenThread Authors.
* 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.
*/
#ifndef POSIX_PLATFORM_RADIO_URL_HPP_
#define POSIX_PLATFORM_RADIO_URL_HPP_
#include <stdio.h>
#include <stdlib.h>
#include <openthread/openthread-system.h>
extern "C" {
typedef struct otPosixRadioArguments
{
const char *mPath; ///< The path to the executable or device
} otPosixRadioArguments;
}
namespace ot {
namespace Posix {
class Arguments : public otPosixRadioArguments
{
public:
Arguments(const char *aUrl);
/**
* This method gets the path in url
*
* @returns The path in url.
*
*/
const char *GetPath(void) const { return mPath; }
/**
* This method returns the url agument value.
*
* @param[in] aName Argument name.
* @param[in] aLastValue The last iterated argument value, NULL for first value.
*
* @returns The argument value.
*
*/
const char *GetValue(const char *aName, const char *aLastValue = NULL);
private:
enum
{
kRadioUrlMaxSize = 512,
};
char mUrl[kRadioUrlMaxSize];
char *mStart;
char *mEnd;
};
} // namespace Posix
} // namespace ot
#endif // POSIX_PLATFORM_RADIO_URL_HPP_
+69 -10
View File
@@ -91,32 +91,91 @@ SpiInterface::SpiInterface(SpinelInterface::ReceiveFrameCallback aCallback,
{
}
otError SpiInterface::Init(const otPlatformConfig &aPlatformConfig)
otError SpiInterface::Init(Arguments &aArguments)
{
VerifyOrDie(aPlatformConfig.mSpiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_FAILURE);
const char *spiGpioIntDevice;
const char *spiGpioResetDevice;
uint8_t spiGpioIntLine = 0;
uint8_t spiGpioResetLine = 0;
uint8_t spiMode = OT_PLATFORM_CONFIG_SPI_DEFAULT_MODE;
uint32_t spiSpeed = SPI_IOC_WR_MAX_SPEED_HZ;
uint32_t spiResetDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_RESET_DELAY_MS;
uint16_t spiCsDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_CS_DELAY_US;
uint8_t spiAlignAllowance = OT_PLATFORM_CONFIG_SPI_DEFAULT_ALIGN_ALLOWANCE;
uint8_t spiSmallPacketSize = OT_PLATFORM_CONFIG_SPI_DEFAULT_SMALL_PACKET_SIZE;
mSpiCsDelayUs = aPlatformConfig.mSpiCsDelay;
mSpiSmallPacketSize = aPlatformConfig.mSpiSmallPacketSize;
mSpiAlignAllowance = aPlatformConfig.mSpiAlignAllowance;
spiGpioIntDevice = aArguments.GetValue("gpio-int-device");
spiGpioResetDevice = aArguments.GetValue("gpio-reset-device");
if (!spiGpioIntDevice || !spiGpioResetDevice)
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
if (aPlatformConfig.mSpiGpioIntDevice != NULL)
if (aArguments.GetValue("gpio-int-line"))
{
spiGpioIntLine = static_cast<uint8_t>(atoi(aArguments.GetValue("gpio-int-line")));
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
if (aArguments.GetValue("gpio-reset-line"))
{
spiGpioResetLine = static_cast<uint8_t>(atoi(aArguments.GetValue("gpio-reset-line")));
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
if (aArguments.GetValue("spi-mode"))
{
spiMode = static_cast<uint8_t>(atoi(aArguments.GetValue("spi-mode")));
}
if (aArguments.GetValue("spi-speed"))
{
spiSpeed = static_cast<uint32_t>(atoi(aArguments.GetValue("spi-speed")));
}
if (aArguments.GetValue("spi-reset-delay"))
{
spiResetDelay = static_cast<uint32_t>(atoi(aArguments.GetValue("spi-reset-delay")));
}
if (aArguments.GetValue("spi-cs-delay"))
{
spiCsDelay = static_cast<uint16_t>(atoi(aArguments.GetValue("spi-cs-delay")));
}
if (aArguments.GetValue("spi-align-allowance"))
{
spiAlignAllowance = static_cast<uint8_t>(atoi(aArguments.GetValue("spi-align-allowance")));
}
if (aArguments.GetValue("spi-small-packet"))
{
spiSmallPacketSize = static_cast<uint8_t>(atoi(aArguments.GetValue("spi-small-packet")));
}
VerifyOrDie(spiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_FAILURE);
mSpiCsDelayUs = spiCsDelay;
mSpiSmallPacketSize = spiSmallPacketSize;
mSpiAlignAllowance = spiAlignAllowance;
if (spiGpioIntDevice != NULL)
{
// If the interrupt pin is not set, SPI interface will use polling mode.
InitIntPin(aPlatformConfig.mSpiGpioIntDevice, aPlatformConfig.mSpiGpioIntLine);
InitIntPin(spiGpioIntDevice, spiGpioIntLine);
}
else
{
otLogNotePlat("SPI interface enters polling mode.");
}
InitResetPin(aPlatformConfig.mSpiGpioResetDevice, aPlatformConfig.mSpiGpioResetLine);
InitSpiDev(aPlatformConfig.mRadioFile, aPlatformConfig.mSpiMode, aPlatformConfig.mSpiSpeed);
InitResetPin(spiGpioResetDevice, spiGpioResetLine);
InitSpiDev(aArguments.GetPath(), spiMode, spiSpeed);
// Reset RCP chip.
TrigerReset();
// Waiting for the RCP chip starts up.
usleep(static_cast<useconds_t>(aPlatformConfig.mSpiResetDelay) * kUsecPerMsec);
usleep(static_cast<useconds_t>(spiResetDelay) * kUsecPerMsec);
return OT_ERROR_NONE;
}
+2 -2
View File
@@ -79,14 +79,14 @@ public:
*
* @note This method should be called before reading and sending spinel frames to the interface.
*
* @param[in] aPlatformConfig Platform configuration structure.
* @param[in] aArguments Arguments parsed from radio url.
*
* @retval OT_ERROR_NONE The interface is initialized successfully.
* @retval OT_ERROR_ALREADY The interface is already initialized.
* @retval OT_ERROR_INVALID_ARGS The UART device or executable cannot be found or failed to open/run.
*
*/
otError Init(const otPlatformConfig &aPlatformConfig);
otError Init(Arguments &aArguments);
/**
* This method deinitializes the interface to the RCP.
+6 -3
View File
@@ -49,13 +49,16 @@ uint64_t gNodeId = 0;
otInstance *otSysInit(otPlatformConfig *aPlatformConfig)
{
otInstance *instance = NULL;
otInstance * instance = NULL;
ot::Posix::Arguments args(aPlatformConfig->mRadioUrl);
#if OPENTHREAD_POSIX_VIRTUAL_TIME
virtualTimeInit(static_cast<uint16_t>(atoi(aPlatformConfig->mRadioConfig)));
virtualTimeInit(static_cast<uint16_t>(atoi(args.GetValue("arg"))));
#endif
VerifyOrDie(args.GetPath() != NULL, OT_EXIT_INVALID_ARGUMENTS);
platformAlarmInit(aPlatformConfig->mSpeedUpFactor);
platformRadioInit(aPlatformConfig);
platformRadioInit(&args);
platformRandomInit();
instance = otInstanceInitSingle();
+1 -1
View File
@@ -27,7 +27,7 @@
# POSSIBILITY OF SUCH DAMAGE.
#
spawn $env(OT_COMMAND) $env(RCP_COMMAND) 1
spawn $env(OT_COMMAND) "spinel+hdlc+uart://$env(RCP_COMMAND)?arg=1"
set timeout 1
expect_after {
timeout { exit 1 }
@@ -28,7 +28,7 @@
#
# allows 11-25 and forbidden 26
spawn $env(OT_COMMAND) --max-power-table 11,12,13,14,15,16,17,18,19,20,21,22,23,24,-1,0x7f $env(RCP_COMMAND) 1
spawn $env(OT_COMMAND) "spinel+hdlc+uart://$env(RCP_COMMAND)?max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,-1,0x7f&arg=1"
set timeout 1
expect_after {
timeout { exit 1 }
@@ -42,7 +42,7 @@ expect "Done"
send "\x04"
expect eof
# allows all channels by default
spawn $env(OT_COMMAND) $env(RCP_COMMAND) 1
spawn $env(OT_COMMAND) "spinel+hdlc+uart://$env(RCP_COMMAND)?arg=1"
set timeout 1
expect_after {
timeout { exit 1 }
+8 -4
View File
@@ -108,7 +108,8 @@ class Node:
cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode)
if 'RADIO_DEVICE' in os.environ:
cmd += ' -v %s' % os.environ['RADIO_DEVICE']
cmd += ' -v spinel+hdlc+uart://%s?arg=%d' % (
os.environ['RADIO_DEVICE'], nodeid)
# Load Thread 1.1 node when testing Thread 1.2 scenarios for interoperability
elif self.version == '1.1':
@@ -120,7 +121,8 @@ class Node:
cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode)
if 'RADIO_DEVICE_1_1' in os.environ:
cmd += ' -v %s' % os.environ['RADIO_DEVICE_1_1']
cmd += ' -v spinel+hdlc+uart://%s?arg=%d' % (
os.environ['RADIO_DEVICE_1_1'], nodeid)
cmd += ' %d' % nodeid
print("%s" % cmd)
@@ -146,7 +148,8 @@ class Node:
# If Thread version of node matches the testing environment version.
if self.version == self.env_version:
if 'RADIO_DEVICE' in os.environ:
args = ' %s' % os.environ['RADIO_DEVICE']
args = ' spinel+hdlc+uart://%s?arg=%d' % (
os.environ['RADIO_DEVICE'], nodeid)
else:
args = ''
@@ -184,7 +187,8 @@ class Node:
# Load Thread 1.1 node when testing Thread 1.2 scenarios for interoperability.
elif self.version == '1.1':
if 'RADIO_DEVICE_1_1' in os.environ:
args = ' %s' % os.environ['RADIO_DEVICE_1_1']
args = ' spinel+hdlc+uart://%s?arg=%d' % (
os.environ['RADIO_DEVICE_1_1'], nodeid)
else:
args = ''
+1 -1
View File
@@ -330,7 +330,7 @@ class Node(object):
self._use_posix_with_rcp = False
if self._use_posix_with_rcp:
ncp_socket_path = 'system:{} -s {} {} {}'.format(
ncp_socket_path = 'system:{} -s {} spinel+hdlc+uart://{}?arg={}'.format(
self._OT_NCP_FTD_POSIX, self._SPEED_UP_FACTOR, self._OT_RCP,
index)
else: