diff --git a/README.md b/README.md index d65a73aae..7dcf48be6 100644 --- a/README.md +++ b/README.md @@ -162,14 +162,15 @@ Configuration information for the examples and the tests can be found in the `cf A number of examples are provided with this repo: | Technology | Example | -|--------------|----------| +|--------------|---------| | Cellular | The [sockets](/example/sockets "socket example") example brings up a TCP/UDP socket by using the [device](/common/device "device API"), [network](/common/network "network API") and [sock](/common/sock "sock API") APIs. | -| Cellular | The [PSK generation](/example/security/psk "PSK example") example using the [security](/common/security "security API") API. | | Cellular | A [TLS-secured version](/example/sockets "TLS sockets example") of the sockets example. | | Cellular | A [DTLS-secured version](/example/sockets "DTLS sockets example") of the sockets example. | +| Cellular | A [PPP version](/example/sockets "PPP sockets example") of the sockets example that shows how the platform IP stack/applications can use a cellular connection. | | Cellular | An [MQTT/MQTT-SN client](/example/mqtt_client "MQTT/MQTT-SN example") using the [MQTT/MQTT-SN client](/common/mqtt_client "MQTT/MQTT-SN client API") API.| | Cellular | An [HTTP client](/example/http_client "HTTP example") using the [HTTP client](/common/http_client "HTTP client API") API.| | Cellular | [CellLocate](/example/location "CellLocate example") example.| +| Cellular | The [PSK generation](/example/security/psk "PSK example") example using the [security](/common/security "security API") API. | | Bluetooth | See the BLE examples in the [XPLR-IOT-1 ubxlib examples repo](https://github.com/u-blox/ubxlib_examples_xplr_iot/tree/master/examples). | | Wi-Fi | The [sockets](/example/sockets "sockets example") example brings up a TCP/UDP socket by using the [device](/common/device "device API"), [network](/common/network "network API") and [sock](/common/sock "sock API") APIs. | | GNSS | [location](/example/location "location example") example using a GNSS chip connected directly or via a cellular module.| @@ -227,7 +228,7 @@ The software in this repository is Apache 2.0 licensed and copyright u-blox with - The ARM callstack iterator in [port/platform/common/debug_utils/src/arch/arm/u_stack_frame_cortex.c](/port/platform/common/debug_utils/src/arch/arm/u_print_callstack_cortex.c) is copyright Armink, part of [CmBacktrace](https://github.com/armink/CmBacktrace). - The FreeRTOS additions [port/platform/common/debug_utils/src/freertos/additions](/port/platform/common/debug_utils/src/freertos/additions) are copied from the Apache licensed [ESP-IDF](https://github.com/espressif/esp-idf). - If you compile-in geofencing by defining the conditional compilation flag `U_CFG_GEOFENCE` for your build: - - For shapes larger than about 1 km, to employ a true-earth model you may choose to conditionally link the sub-module [common/geofence/geographiclib](geographiclib) from the MIT licensed [GeographicLib](https://github.com/geographiclib) by Charles A. Karney. + - For shapes larger than about 1 km, to employ a true-earth model you may choose to conditionally link the sub-module [common/geofence](geographiclib) from the MIT licensed [GeographicLib](https://github.com/geographiclib) by Charles A. Karney. - The default functions, which assume a spherical earth, are derived from the valuable advice (though not the code) of https://www.movable-type.co.uk/scripts/latlong.html, MIT licensed and copyright Chris Veness. In all cases copyright, and our thanks, remain with the original authors. diff --git a/cell/README.md b/cell/README.md index d3e858ff4..1acffc449 100644 --- a/cell/README.md +++ b/cell/README.md @@ -128,4 +128,13 @@ int app_start() { while(1); } -``` \ No newline at end of file +``` + +# PPP-Level Integration With A Platform +PPP-level integration between the bottom of a platform's IP stack and cellular is supported on some platforms and some module types, currently only ESP-IDF with SARA-R5 or SARA-R422. This allows the native clients of the platform (e.g. MQTT etc.) to be used in your application with a cellular transport beneath them. + +To enable this integration you must define `U_CFG_PPP_ENABLE` for your build. Other switches/components/whatevers may also be required on the platform side: see the README.md in the relevant platform directory for details. + +To use the integration, just make a cellular connection with `ubxlib` in the usual way and the connection will be available to the platform. + +Note: if you are required to supply a username and password for your connection then, when using PPP, you must call `uCellNetSetAuthenticationMode()` to set the authentication mode explicitly; automatic authentication mode will not work with PPP. \ No newline at end of file diff --git a/cell/api/u_cell_mux.h b/cell/api/u_cell_mux.h index 82b00571b..e38fc5412 100644 --- a/cell/api/u_cell_mux.h +++ b/cell/api/u_cell_mux.h @@ -53,10 +53,10 @@ extern "C" { #define U_CELL_MUX_CHANNEL_ID_GNSS 0xFF #ifndef U_CELL_MUX_MAX_CHANNELS -/** Enough room for the control channel, an AT channel and a - * GNSS serial channel. +/** Enough room for the control channel, an AT channel, a + * GNSS serial channel and potentially a PPP data channel. */ -# define U_CELL_MUX_MAX_CHANNELS 3 +# define U_CELL_MUX_MAX_CHANNELS 4 #endif /* ---------------------------------------------------------------- diff --git a/cell/api/u_cell_net.h b/cell/api/u_cell_net.h index 6980e7453..fe4afb458 100644 --- a/cell/api/u_cell_net.h +++ b/cell/api/u_cell_net.h @@ -270,6 +270,10 @@ typedef enum { } uCellNetRegDomain_t; /** The possible authentication modes for the network connection. + * + * Note: there is also a #uPortPppAuthenticationMode_t enumeration + * which is set to match this one. If you make a change here you + * may need to make a change there also. */ typedef enum { U_CELL_NET_AUTHENTICATION_MODE_NONE = 0, /**< \deprecated please use #U_CELL_NET_AUTHENTICATION_MODE_NOT_SET. */ diff --git a/cell/src/u_cell.c b/cell/src/u_cell.c index 93285214c..45df0bc5c 100644 --- a/cell/src/u_cell.c +++ b/cell/src/u_cell.c @@ -60,6 +60,7 @@ #include "u_cell_private.h" // don't change it #include "u_cell_mux.h" #include "u_cell_mux_private.h" +#include "u_cell_ppp_private.h" // The headers below necessary to work around an Espressif linker problem, see uCellInit() #include "u_sock.h" @@ -143,6 +144,8 @@ static void removeCellInstance(uCellPrivateInstance_t *pInstance) uPortFree(pInstance->pFotaContext); // Free any HTTP context uCellPrivateHttpRemoveContext(pInstance); + // Free any PPP context + uCellPppPrivateRemoveContext(pInstance); // Free any CMUX context uCellMuxPrivateRemoveContext(pInstance); // Free any CellTime context diff --git a/cell/src/u_cell_mux.c b/cell/src/u_cell_mux.c index 752432594..403bbb1ea 100644 --- a/cell/src/u_cell_mux.c +++ b/cell/src/u_cell_mux.c @@ -77,6 +77,8 @@ #include "u_device_shared.h" +#include "u_gnss_shared.h" // For uGnssUpdateAtHandle() + #include "u_cell_module_type.h" #include "u_cell_file.h" #include "u_cell.h" // Order is @@ -138,7 +140,7 @@ #ifndef U_CELL_MUX_CALLBACK_QUEUE_LENGTH /** The maximum length of the common callback queue for the serial devices. - * Each item in the queue will be sizeof(uCellMuxEvenTrampoline_t) bytes big. + * Each item in the queue will be sizeof(uCellMuxEventTrampoline_t) bytes big. */ # define U_CELL_MUX_CALLBACK_QUEUE_LENGTH 20 #endif @@ -162,7 +164,7 @@ typedef struct { uCellMuxPrivateContext_t *pContext; int32_t channel; uint32_t eventBitMap; -} uCellMuxEvenTrampoline_t; +} uCellMuxEventTrampoline_t; /* ---------------------------------------------------------------- * STATIC VARIABLES @@ -192,7 +194,7 @@ static const char gMuxCldCommandFrame[] = {0xf9, 0x03, 0xff, 0x05, 0xc3, 0x01, 0 // Event handler, common to all virtual serial ports. static void eventHandler(void *pParam, size_t paramLength) { - uCellMuxEvenTrampoline_t *pEventTrampoline = (uCellMuxEvenTrampoline_t *) pParam; + uCellMuxEventTrampoline_t *pEventTrampoline = (uCellMuxEventTrampoline_t *) pParam; uCellMuxPrivateContext_t *pContext = pEventTrampoline->pContext; uDeviceSerial_t *pDeviceSerial; uCellMuxPrivateChannelContext_t *pChannelContext; @@ -228,7 +230,7 @@ static int32_t sendEvent(uCellMuxPrivateContext_t *pContext, { int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; uCellMuxPrivateEventCallback_t *pEventCallback; - uCellMuxEvenTrampoline_t trampolineData; + uCellMuxEventTrampoline_t trampolineData; uint32_t eventCallbackFilter; int64_t startTime = uPortGetTickTimeMs(); @@ -406,11 +408,15 @@ static int32_t serialWriteInnards(struct uDeviceSerial_t *pDeviceSerial, lengthWritten); for (size_t x = 0; x < lengthWritten; x++) { char y = *(pBufferEncoded + x); +#ifndef U_CELL_MUX_HEX_DEBUG if (isprint((int32_t) y)) { uPortLog("%c", y); } else { +#endif uPortLog("[%02x]", y); +#ifndef U_CELL_MUX_HEX_DEBUG } +#endif } uPortLog(".\n"); } @@ -692,11 +698,33 @@ static int32_t serialRead(struct uDeviceSerial_t *pDeviceSerial, if (U_CELL_MUX_IS_OPEN(pChannelContext->state)) { pTraffic = &(pChannelContext->traffic); sizeOrErrorCode = serialReadInnards(pTraffic, pBuffer, sizeBytes); -#ifdef U_CELL_MUX_ENABLE_DEBUG +#if defined(U_CELL_MUX_ENABLE_DEBUG) || defined(U_CELL_MUX_ENABLE_USER_RX_DEBUG) if (sizeOrErrorCode > 0) { uPortLog("U_CELL_CMUX_%d: app read %d byte(s).\n", pChannelContext->channel, sizeOrErrorCode); } +#endif +#ifdef U_CELL_MUX_ENABLE_USER_RX_DEBUG + // Don't normally need this however it may be useful when + // debugging the behaviour of a destination that is out of + // reach, e.g. inside the IP stack of a platform, channeled + // via PPP + if (sizeOrErrorCode > 0) { + uPortLog("U_CELL_CMUX_%d: ", pChannelContext->channel); + for (int32_t x = 0; x < sizeOrErrorCode; x++) { + char y = *((char *) pBuffer + x); +#ifndef U_CELL_MUX_HEX_DEBUG + if (isprint((int32_t) y)) { + uPortLog("%c", y); + } else { +#endif + uPortLog("[%02x]", y); +#ifndef U_CELL_MUX_HEX_DEBUG + } +#endif + } + uPortLog(".\n"); + } #endif if (pTraffic->rxIsFlowControlledOff && (((pTraffic->rxBufferSizeBytes - serialGetReceiveSizeInnards(pDeviceSerial)) * 100) / @@ -1521,16 +1549,7 @@ static void cmuxReceiveCallback(const uAtClientStreamHandle_t *pStream, } /* ---------------------------------------------------------------- - * PUBLIC FUNCTIONS: WORKAROUND FOR LINKER ISSUE - * -------------------------------------------------------------- */ - -void uCellMuxPrivateLink() -{ - //dummy -} - -/* ---------------------------------------------------------------- - * PUBLIC FUNCTIONS + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO CELLULAR * -------------------------------------------------------------- */ // Enable multiplexer mode. This involves a few steps: @@ -1541,10 +1560,9 @@ void uCellMuxPrivateLink() // AT client on CMUX channel 1, the AT channel, copy the current // state there and begin using it. // 4. If not successful, unwind. -int32_t uCellMuxEnable(uDeviceHandle_t cellHandle) +int32_t uCellMuxPrivateEnable(uCellPrivateInstance_t *pInstance) { - int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; - uCellPrivateInstance_t *pInstance; + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; uAtClientHandle_t atHandle; uAtClientStreamHandle_t stream = U_AT_CLIENT_STREAM_HANDLE_DEFAULTS; uCellMuxPrivateContext_t *pContext; @@ -1552,170 +1570,350 @@ int32_t uCellMuxEnable(uDeviceHandle_t cellHandle) int32_t cmeeMode = 2; char tempBuffer[32]; - if (gUCellPrivateMutex != NULL) { - - U_PORT_MUTEX_LOCK(gUCellPrivateMutex); - - errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; - pInstance = pUCellPrivateGetInstance(cellHandle); - if (pInstance != NULL) { - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_CMUX)) { - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (pInstance->pMuxContext == NULL) { - errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; - // Allocate memory for our CMUX context; this will be - // deallocated only when the cellular instance is removed - pInstance->pMuxContext = pUPortMalloc(sizeof(uCellMuxPrivateContext_t)); - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - memset(pContext, 0, sizeof(*pContext)); - // To save memory, we use a single event queue for all callbacks - // from the CMUX channels, re-using the AT client sizes - pContext->eventQueueHandle = uPortEventQueueOpen(eventHandler, - "cmuxCallbacks", - sizeof(uCellMuxEvenTrampoline_t), - U_CELL_MUX_CALLBACK_TASK_STACK_SIZE_BYTES, - U_CELL_MUX_CALLBACK_TASK_PRIORITY, - U_CELL_MUX_CALLBACK_QUEUE_LENGTH); - if (pContext->eventQueueHandle >= 0) { - if (uRingBufferCreateWithReadHandle(&(pContext->ringBuffer), - pContext->linearBuffer, - sizeof(pContext->linearBuffer), 1) == 0) { - uRingBufferSetReadRequiresHandle(&(pContext->ringBuffer), true); - pContext->readHandle = uRingBufferTakeReadHandle(&(pContext->ringBuffer)); - } else { - // Clean up on error - uPortEventQueueClose(pContext->eventQueueHandle); - uPortFree(pInstance->pMuxContext); - pInstance->pMuxContext = NULL; - } + if (pInstance != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_CMUX)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pInstance->pMuxContext == NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + // Allocate memory for our CMUX context; this will be + // deallocated only when the cellular instance is removed + pInstance->pMuxContext = pUPortMalloc(sizeof(uCellMuxPrivateContext_t)); + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + memset(pContext, 0, sizeof(*pContext)); + // To save memory, we use a single event queue for all callbacks + // from the CMUX channels, re-using the AT client sizes + pContext->eventQueueHandle = uPortEventQueueOpen(eventHandler, + "cmuxCallbacks", + sizeof(uCellMuxEventTrampoline_t), + U_CELL_MUX_CALLBACK_TASK_STACK_SIZE_BYTES, + U_CELL_MUX_CALLBACK_TASK_PRIORITY, + U_CELL_MUX_CALLBACK_QUEUE_LENGTH); + if (pContext->eventQueueHandle >= 0) { + if (uRingBufferCreateWithReadHandle(&(pContext->ringBuffer), + pContext->linearBuffer, + sizeof(pContext->linearBuffer), 1) == 0) { + uRingBufferSetReadRequiresHandle(&(pContext->ringBuffer), true); + pContext->readHandle = uRingBufferTakeReadHandle(&(pContext->ringBuffer)); } else { // Clean up on error + uPortEventQueueClose(pContext->eventQueueHandle); uPortFree(pInstance->pMuxContext); pInstance->pMuxContext = NULL; } + } else { + // Clean up on error + uPortFree(pInstance->pMuxContext); + pInstance->pMuxContext = NULL; } } - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (pContext->savedAtHandle == NULL) { - // Initialise the other parts of [an existing] context - pContext->pInstance = pInstance; - pContext->channelGnss = getChannelGnss(pInstance); - pContext->holdingBufferIndex = 0; - // Initiate CMUX - atHandle = pInstance->atHandle; - uAtClientLock(atHandle); - uAtClientStreamGetExt(atHandle, &stream); - uRingBufferFlushHandle(&(pContext->ringBuffer), pContext->readHandle); - pContext->underlyingStreamHandle = stream.handle.int32; - uAtClientCommandStart(atHandle, "AT+CMUX="); - // Only basic mode and only UIH frames are supported by any - // of the cellular modules we support - uAtClientWriteInt(atHandle, 0); - uAtClientWriteInt(atHandle, 0); - // As advised in the u-blox multiplexer document, port - // speed is left empty for max compatibility - uAtClientWriteString(atHandle, "", false); - // Set the information field length - uAtClientWriteInt(atHandle, - U_CELL_MUX_PRIVATE_INFORMATION_LENGTH_MAX_BYTES); - // Everything else is left at defaults for max compatibility - uAtClientCommandStopReadResponse(atHandle); - // Not unlocking here, just check for errors - errorCode = uAtClientErrorGet(atHandle); + } + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pContext->savedAtHandle == NULL) { + // Initialise the other parts of [an existing] context + pContext->pInstance = pInstance; + pContext->channelGnss = getChannelGnss(pInstance); + pContext->holdingBufferIndex = 0; + // Initiate CMUX + atHandle = pInstance->atHandle; + uAtClientLock(atHandle); + uAtClientStreamGetExt(atHandle, &stream); + uRingBufferFlushHandle(&(pContext->ringBuffer), pContext->readHandle); + pContext->underlyingStreamHandle = stream.handle.int32; + uAtClientCommandStart(atHandle, "AT+CMUX="); + // Only basic mode and only UIH frames are supported by any + // of the cellular modules we support + uAtClientWriteInt(atHandle, 0); + uAtClientWriteInt(atHandle, 0); + // As advised in the u-blox multiplexer document, port + // speed is left empty for max compatibility + uAtClientWriteString(atHandle, "", false); + // Set the information field length + uAtClientWriteInt(atHandle, + U_CELL_MUX_PRIVATE_INFORMATION_LENGTH_MAX_BYTES); + // Everything else is left at defaults for max compatibility + uAtClientCommandStopReadResponse(atHandle); + // Not unlocking here, just check for errors + errorCode = uAtClientErrorGet(atHandle); + if (errorCode == 0) { + // Leave the AT client locked to stop it reacting to stuff coming + // back over the UART, which will shortly become the MUX + // control channel and not an AT interface at all. + // Replace the URC handler of the existing AT client + // with our own so that we get the received data + // and can decode it + uAtClientUrcHandlerHijackExt(atHandle, cmuxReceiveCallback, pContext); + // Give the module a moment for the MUX switcheroo + uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); + // Open the control channel, channel 0; for this we need no + // data buffer, since it does not carry user data + pContext->savedAtHandle = atHandle; + errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL, 0); if (errorCode == 0) { - // Leave the AT client locked to stop it reacting to stuff coming - // back over the UART, which will shortly become the MUX - // control channel and not an AT interface at all. - // Replace the URC handler of the existing AT client - // with our own so that we get the received data - // and can decode it - uAtClientUrcHandlerHijackExt(atHandle, cmuxReceiveCallback, pContext); - // Give the module a moment for the MUX switcheroo - uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); - // Open the control channel, channel 0; for this we need no - // data buffer, since it does not carry user data - pContext->savedAtHandle = atHandle; - errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL, 0); - if (errorCode == 0) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_0: control channel open.\n"); + uPortLog("U_CELL_CMUX_0: control channel open.\n"); #endif - // Channel 0 is up, now we need channel 1, on which - // we will need a data buffer for the information field carrying the - // user data (i.e. AT commands) - errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT, - U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); - if (errorCode == 0) { + // Channel 0 is up, now we need channel 1, on which + // we will need a data buffer for the information field carrying the + // user data (i.e. AT commands) + errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT, + U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); + if (errorCode == 0) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_1: AT channel open, flushing stored URCs...\n"); + uPortLog("U_CELL_CMUX_1: AT channel open, flushing stored URCs...\n"); #endif - pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); - // Some modules (e.g. SARA-R422) can have stored up loads of URCs - // which they like to emit over the new mux channel; flush these - // out here - uPortTaskBlock(500); - do { - uPortTaskBlock(10); - } while (pDeviceSerial->read(pDeviceSerial, tempBuffer, sizeof(tempBuffer)) > 0); - // Create a copy of the current AT client on this serial port - stream.handle.pDeviceSerial = pDeviceSerial; - stream.type = U_AT_CLIENT_STREAM_TYPE_VIRTUAL_SERIAL; - atHandle = uAtClientAddExt(&stream, NULL, U_CELL_AT_BUFFER_LENGTH_BYTES); - if (atHandle != NULL) { + pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); + // Some modules (e.g. SARA-R422) can have stored up loads of URCs + // which they like to emit over the new mux channel; flush these + // out here + uPortTaskBlock(500); + do { + uPortTaskBlock(10); + } while (pDeviceSerial->read(pDeviceSerial, tempBuffer, sizeof(tempBuffer)) > 0); + // Create a copy of the current AT client on this serial port + stream.handle.pDeviceSerial = pDeviceSerial; + stream.type = U_AT_CLIENT_STREAM_TYPE_VIRTUAL_SERIAL; + atHandle = uAtClientAddExt(&stream, NULL, U_CELL_AT_BUFFER_LENGTH_BYTES); + if (atHandle != NULL) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX: AT client added.\n"); + uPortLog("U_CELL_CMUX: AT client added.\n"); #endif - errorCode = uCellMuxPrivateCopyAtClient(pContext->savedAtHandle, - atHandle); - if (errorCode == 0) { + errorCode = uCellMuxPrivateCopyAtClient(pContext->savedAtHandle, + atHandle); + if (errorCode == 0) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX: existing AT client copied, CMUX is running.\n"); + uPortLog("U_CELL_CMUX: existing AT client copied, CMUX is running.\n"); #endif - // Now that we have everything, we set the AT handle - // of our instance to the new AT handle, leaving the - // old AT handle locked - pInstance->atHandle = atHandle; - // The setting of echo-off and AT+CMEE is port-specific, - // so we need to set those here for the new port + // Now that we have everything, we set the AT handle + // of our instance to the new AT handle, leaving the + // old AT handle locked + pInstance->atHandle = atHandle; + // The setting of echo-off and AT+CMEE is port-specific, + // so we need to set those here for the new port #ifdef U_CFG_CELL_ENABLE_NUMERIC_ERROR - cmeeMode = 1; + cmeeMode = 1; #endif - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "ATE0"); - uAtClientCommandStopReadResponse(atHandle); - uAtClientCommandStart(atHandle, "AT+CMEE="); - uAtClientWriteInt(atHandle, cmeeMode); - uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); - } else { - // Recover on error - uAtClientRemove(atHandle); - atHandle = pContext->savedAtHandle; + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "ATE0"); + uAtClientCommandStopReadResponse(atHandle); + uAtClientCommandStart(atHandle, "AT+CMEE="); + uAtClientWriteInt(atHandle, cmeeMode); + uAtClientCommandStopReadResponse(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + // Let GNSS update any AT handles it may hold + uGnssUpdateAtHandle(pContext->savedAtHandle, atHandle); } } else { // Recover on error + uAtClientRemove(atHandle); atHandle = pContext->savedAtHandle; } + } else { + // Recover on error + atHandle = pContext->savedAtHandle; } } } - if (errorCode < 0) { - // Clean up and unlock the AT client on error - uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); - // Closing the control channel will take us out of CMUX mode - uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL); - uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); - pContext->savedAtHandle = NULL; - uAtClientUnlock(atHandle); - } + } + if (errorCode < 0) { + // Clean up and unlock the AT client on error + uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); + // Closing the control channel will take us out of CMUX mode + uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL); + uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); + pContext->savedAtHandle = NULL; + uAtClientUnlock(atHandle); } } } } + } + + return errorCode; +} + +// Determine if the multiplexer is currently enabled. +bool uCellMuxPrivateIsEnabled(uCellPrivateInstance_t *pInstance) +{ + bool isEnabled = false; + uCellMuxPrivateContext_t *pContext; + + if ((pInstance != NULL) && (pInstance->pMuxContext != NULL)) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + isEnabled = (pContext->savedAtHandle != NULL); + } + + return isEnabled; +} + +// Add a multiplexer channel. +int32_t uCellMuxPrivateAddChannel(uCellPrivateInstance_t *pInstance, + int32_t channel, + uDeviceSerial_t **ppDeviceSerial) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uCellMuxPrivateContext_t *pContext; + + if ((pInstance != NULL) && (ppDeviceSerial != NULL) && + (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL) && + (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_AT) && + ((channel <= U_CELL_MUX_PRIVATE_ADDRESS_MAX) || + (channel == U_CELL_MUX_CHANNEL_ID_GNSS))) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + if (pContext->savedAtHandle != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (channel == U_CELL_MUX_CHANNEL_ID_GNSS) { + channel = pContext->channelGnss; + } + if (channel >= 0) { + errorCode = openChannel(pContext, (uint8_t) channel, + U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); + if (errorCode == 0) { +#ifdef U_CELL_MUX_ENABLE_DEBUG + uPortLog("U_CELL_CMUX_%d: channel added.\n", channel); +#endif + *ppDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, (uint8_t) channel); + } + } + } + } + } + + return errorCode; +} + +// Disable multiplexer mode. This involves a few steps: +// +// 1. Send DISC on the virtual serial interface of any currently +// open channels and close the virtual serial interfaces; +// do channel 0, the control interface, last and it will +// end CMUX mode. +// 2. Move AT client operations back to the original AT client. +// 3. DO NOT free memory; only uCellMuxPrivateRemoveContext() does +// that, to ensure thread-safety. +int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uAtClientHandle_t atHandle; + uCellMuxPrivateContext_t *pContext; + uCellMuxPrivateChannelContext_t *pChannelContext; + + if (pInstance != NULL) { + atHandle = pInstance->atHandle; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + // Start from the top, so that we do channel 0, which + // will always be at index 0, last + for (int32_t x = (sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) - 1; + x >= 0; x--) { + pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( + pContext->pDeviceSerial[x]); + if (pChannelContext != NULL) { + uCellMuxPrivateCloseChannel(pContext, pChannelContext->channel); + } + } + if (pContext->savedAtHandle != NULL) { + // Copy the settings of the AT handler on channel 1 + // back into the original one, in case they have changed + errorCode = uCellMuxPrivateCopyAtClient(atHandle, pContext->savedAtHandle); + // While we set the error code above, there's not a whole lot + // we can do if this fails, so continue anyway; close the + // AT handler that was on channel 1 + uAtClientIgnoreAsync(atHandle); + uAtClientRemove(atHandle); + // Unhijack the old AT handler and unlock it + atHandle = pContext->savedAtHandle; + uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); + uAtClientUnlock(atHandle); + // Let GNSS update any AT handles it may hold + uGnssUpdateAtHandle(pInstance->atHandle, atHandle); + pInstance->atHandle = atHandle; + pContext->savedAtHandle = NULL; +#ifdef U_CELL_MUX_ENABLE_DEBUG + uPortLog("U_CELL_CMUX: closed.\n"); +#endif + } + // Give the module a moment for the MUX switcheroo + uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); + } + } + + return (int32_t) errorCode; +} + +// Get the serial device for the given channel. +uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, + uint8_t channel) +{ + uDeviceSerial_t *pDeviceSerial = NULL; + uCellMuxPrivateChannelContext_t *pChannelContext; + + if ((pContext != NULL) && (channel <= U_CELL_MUX_PRIVATE_CHANNEL_ID_MAX)) { + for (size_t x = 0; + (x < sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) && + (pDeviceSerial == NULL); x++) { + if (pContext->pDeviceSerial[x] != NULL) { + pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( + pContext->pDeviceSerial[x]); + if ((pChannelContext != NULL) && !pChannelContext->markedForDeletion && + (pChannelContext->channel == channel)) { + pDeviceSerial = pContext->pDeviceSerial[x]; + } + } + } + } + + return pDeviceSerial; +} + +// Close a CMUX channel. +void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel) +{ + uDeviceSerial_t *pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, channel); + + if (pDeviceSerial != NULL) { + pDeviceSerial->close(pDeviceSerial); +#ifdef U_CELL_MUX_ENABLE_DEBUG + uPortLog("U_CELL_CMUX_%d: channel closed.\n", channel); +#endif + } +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: WORKAROUND FOR LINKER ISSUE + * -------------------------------------------------------------- */ + +void uCellMuxPrivateLink() +{ + //dummy +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Enable multiplexer mode. +int32_t uCellMuxEnable(uDeviceHandle_t cellHandle) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCode = uCellMuxPrivateEnable(pInstance); + } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); } @@ -1728,16 +1926,14 @@ bool uCellMuxIsEnabled(uDeviceHandle_t cellHandle) { bool isEnabled = false; uCellPrivateInstance_t *pInstance; - uCellMuxPrivateContext_t *pContext; if (gUCellPrivateMutex != NULL) { U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pMuxContext != NULL)) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - isEnabled = (pContext->savedAtHandle != NULL); + if (pInstance != NULL) { + isEnabled = uCellMuxPrivateIsEnabled(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -1753,7 +1949,6 @@ int32_t uCellMuxAddChannel(uDeviceHandle_t cellHandle, { int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; uCellPrivateInstance_t *pInstance; - uCellMuxPrivateContext_t *pContext; if (gUCellPrivateMutex != NULL) { @@ -1761,31 +1956,8 @@ int32_t uCellMuxAddChannel(uDeviceHandle_t cellHandle, errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (ppDeviceSerial != NULL) && - (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL) && - (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_AT) && - ((channel <= U_CELL_MUX_PRIVATE_ADDRESS_MAX) || - (channel == U_CELL_MUX_CHANNEL_ID_GNSS))) { - errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - if (pContext->savedAtHandle != NULL) { - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - if (channel == U_CELL_MUX_CHANNEL_ID_GNSS) { - channel = pContext->channelGnss; - } - if (channel >= 0) { - errorCode = openChannel(pContext, (uint8_t) channel, - U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); - if (errorCode == 0) { -#ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_%d: channel added.\n", channel); -#endif - *ppDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, (uint8_t) channel); - } - } - } - } + if (pInstance != NULL) { + errorCode = uCellMuxPrivateAddChannel(pInstance, channel, ppDeviceSerial); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); diff --git a/cell/src/u_cell_mux_private.c b/cell/src/u_cell_mux_private.c index f9da49cbc..9c74e9e74 100644 --- a/cell/src/u_cell_mux_private.c +++ b/cell/src/u_cell_mux_private.c @@ -388,31 +388,6 @@ int32_t uCellMuxPrivateParseCmux(uParseHandle_t parseHandle, void *pUserParam) * PUBLIC FUNCTIONS: MISC * -------------------------------------------------------------- */ -// Get the serial device for the given channel. -uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, - uint8_t channel) -{ - uDeviceSerial_t *pDeviceSerial = NULL; - uCellMuxPrivateChannelContext_t *pChannelContext; - - if ((pContext != NULL) && (channel <= U_CELL_MUX_PRIVATE_CHANNEL_ID_MAX)) { - for (size_t x = 0; - (x < sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) && - (pDeviceSerial == NULL); x++) { - if (pContext->pDeviceSerial[x] != NULL) { - pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( - pContext->pDeviceSerial[x]); - if ((pChannelContext != NULL) && !pChannelContext->markedForDeletion && - (pChannelContext->channel == channel)) { - pDeviceSerial = pContext->pDeviceSerial[x]; - } - } - } - } - - return pDeviceSerial; -} - // Copy the settings of one AT client into another AT client. int32_t uCellMuxPrivateCopyAtClient(uAtClientHandle_t atHandleSource, uAtClientHandle_t atHandleDestination) @@ -503,77 +478,6 @@ int32_t uCellMuxPrivateCopyAtClient(uAtClientHandle_t atHandleSource, return errorCode; } -// Close a CMUX channel. -void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel) -{ - uDeviceSerial_t *pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, channel); - - if (pDeviceSerial != NULL) { - pDeviceSerial->close(pDeviceSerial); -#ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_%d: channel closed.\n", channel); -#endif - } -} - -// Disable multiplexer mode. This involves a few steps: -// -// 1. Send DISC on the virtual serial interface of any currently -// open channels and close the virtual serial interfaces; -// do channel 0, the control interface, last and it will -// end CMUX mode. -// 2. Move AT client operations back to the original AT client. -// 3. DO NOT fee memory; only uCellMuxPrivateRemoveContext() does -// that, to ensure thread-safety. -int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance) -{ - int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; - uAtClientHandle_t atHandle; - uCellMuxPrivateContext_t *pContext; - uCellMuxPrivateChannelContext_t *pChannelContext; - - if (pInstance != NULL) { - atHandle = pInstance->atHandle; - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - // Start from the top, so that we do channel 0, which - // will always be at index 0, last - for (int32_t x = (sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) - 1; - x >= 0; x--) { - pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( - pContext->pDeviceSerial[x]); - if (pChannelContext != NULL) { - uCellMuxPrivateCloseChannel(pContext, pChannelContext->channel); - } - } - if (pContext->savedAtHandle != NULL) { - // Copy the settings of the AT handler on channel 1 - // back into the original one, in case they have changed - errorCode = uCellMuxPrivateCopyAtClient(atHandle, pContext->savedAtHandle); - // While we set the error code above, there's not a whole lot - // we can do if this fails, so continue anyway; close the - // AT handler that was on channel 1 - uAtClientIgnoreAsync(atHandle); - uAtClientRemove(atHandle); - // Unhijack the old AT handler and unlock it - atHandle = pContext->savedAtHandle; - uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); - uAtClientUnlock(atHandle); - pInstance->atHandle = atHandle; - pContext->savedAtHandle = NULL; -#ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX: closed.\n"); -#endif - } - // Give the module a moment for the MUX switcheroo - uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); - } - } - - return (int32_t) errorCode; -} - // Remove the CMUX context for the given cellular instance. void uCellMuxPrivateRemoveContext(uCellPrivateInstance_t *pInstance) { @@ -605,4 +509,8 @@ void uCellMuxPrivateRemoveContext(uCellPrivateInstance_t *pInstance) } } +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: THERE ARE MORE uCellMuxPrivateXxx() FUNCTIONS IN U_CELL_MUX.C + * -------------------------------------------------------------- */ + // End of file diff --git a/cell/src/u_cell_mux_private.h b/cell/src/u_cell_mux_private.h index 463e0f4b4..fb128586d 100644 --- a/cell/src/u_cell_mux_private.h +++ b/cell/src/u_cell_mux_private.h @@ -203,6 +203,7 @@ typedef enum { typedef enum { U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL = 0, /**< fixed by 3GPP 27.010. */ U_CELL_MUX_PRIVATE_CHANNEL_ID_AT = 1, /**< AT channel, common to all u-blox cellular modules. */ + U_CELL_MUX_PRIVATE_CHANNEL_ID_PPP = 2, /**< PPP data channel, common to all u-blox cellular modules. */ U_CELL_MUX_PRIVATE_CHANNEL_ID_MAX = U_CELL_MUX_PRIVATE_ADDRESS_MAX } uCellMuxPrivateChannelId_t; @@ -308,7 +309,170 @@ typedef struct { } uCellMuxPrivateChannelContext_t; /* ---------------------------------------------------------------- - * FUNCTIONS: 3GPP 27.010 CMUX ENCODE/DECODE + * FUNCTIONS: PRIVATE TO CELLULAR (SEE U_CELL_MUX.C) + * -------------------------------------------------------------- */ + +/** Enable multiplexer mode. Puts the cellular module's AT + * interface into multiplexer (3GPP 27.010 CMUX) mode. This + * is useful when you want to access a GNSS module that is + * connected via, or embedded inside, a cellular module as if it + * were connected directly to this MCU via a serial interface (see + * uCellMuxAddChannel()). Note that this function _internally_ + * opens and uses a CMUX channel for the AT interface, you do not + * have to do that. The AT handle that was originally passed to + * uCellAdd() will remain locked, the handle of the new one that is + * created for use internally can be obtained by calling + * uCellAtClientHandleGet(); uCellAtClientHandleGet() will always + * return the AT handle currently in use. + * + * Whether multiplexer mode is supported or not depends on the cellular + * module and the interface in use: for instance a USB interface to + * a module does not support multiplexer mode. + * + * The module must be powered on for this to work. Returns success + * without doing anything if multiplexer mode is already enabled. + * Multiplexer mode does not survive a power-cycle, either deliberate + * (with uCellPwrOff(), uCellPwrReboot(), etc.) or accidental, and + * cannot be used with 3GPP power saving (since it will also be + * reset during module deep sleep). + * + * Note: if you have passed the AT handle to a GNSS instance (e.g. + * via uGnssAdd()) it will stop working when multiplexer mode is + * enabled (because the AT handle will have been changed), hence you + * should enable multiplexer mode _before_ calling uGnssAdd() + * (and, likewise, remove any GNSS instance before disabling + * multiplexer mode). However, if you have enabled multiplexer + * mode it is much better to call uCellMuxAddChannel() with + * #U_CELL_MUX_CHANNEL_ID_GNSS and then you can pass the + * #uDeviceSerial_t handle that returns to uGnssAdd() (with the + * transport type #U_GNSS_TRANSPORT_VIRTUAL_SERIAL) and you will + * have streamed position. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + * @return zero on success or negative error code + * on failure. + */ +int32_t uCellMuxPrivateEnable(uCellPrivateInstance_t *pInstance); + +/** Determine if the multiplexer is currently enabled. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + * @return true if the multiplexer is enabled, + * else false. + */ +bool uCellMuxPrivateIsEnabled(uCellPrivateInstance_t *pInstance); + +/** Add a multiplexer channel; may be called after uCellMuxEnable() + * has returned success in order to, for instance, create a virtual + * serial port to a GNSS chip inside a SARA-R422M8S or SARA-R510M8S + * module. The virtual serial port handle returned in *ppDeviceSerial + * can be used in #uDeviceCfg_t to open the GNSS device using the + * uDevice API, or it can be passed to uGnssAdd() (with the transport + * type #U_GNSS_TRANSPORT_VIRTUAL_SERIAL) if you prefer to use the + * uGnss API the hard way. + * + * If the channel is already open, this function returns success + * without doing anything. An error is returned if uCellMuxEnable() + * has not been called. + * + * Note: there is a known issue with SARA-R5 modules where, if a GNSS + * multiplexer channel is opened, closed, and then re-opened the GNSS + * chip will be unresponsive. For that case, please open the GNSS + * multiplexer channel once at start of day. + * + * UART POWER SAVING: when UART power saving is enabled in the module + * any constraints arising will also apply to a multiplexer channel; + * specifically, if a DTR pin is not used to wake-up the module, i.e. + * the module supports and is using the "wake up on TX activity" mode + * of UART power saving then, though the AT interface will continue + * to work correctly (as it knows to expect loss of the first few + * characters of an AT string), the other multiplexer channels have + * the same restriction and have no such automated protection. Hence + * if you (a) expect to use a multiplexer channel to communicate with + * a GNSS chip in a cellular module and (b) are not able to use a DTR + * pin to wake the module up from power-saving, then you should call + * uCellPwrDisableUartSleep() to disable UART sleep while you run the + * multiplexer channel (and uCellPwrEnableUartSleep() to re-enable it + * afterwards). + * + * NOTES ON DEVICE SERIAL OPERATION: the operation of *pDeviceSerial + * is constrained in certain ways, since what you have is not a real + * serial port, it is a virtual serial port which has hijacked some + * of the functionality of the physical serial port that was + * previously running, see notes below, but particularly flow control, + * or not taking data out of one or more multiplexed serial ports fast + * enough, can have an adverse effect on other multiplexed serial ports. + * This is difficult to avoid since they are on the same transport. Hence + * it is important to service your multiplexed serial ports often or, + * alternatively, you may call serialDiscardOnFlowControl() with true + * on any serial port where you are happy for any overruns to be + * discarded (e.g. the GNSS one), so that it cannot possibly interfere + * with others (e.g. the AT command one). + * + * The stack size and priority of any event serial callbacks are not + * respected: what you end up with is #U_CELL_MUX_CALLBACK_TASK_PRIORITY + * and #U_CELL_MUX_CALLBACK_TASK_STACK_SIZE_BYTES since a common + * event queue is used for all serial devices. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + * @param channel the channel number to open; channel + * numbers are module-specific, however + * the value #U_CELL_MUX_CHANNEL_ID_GNSS + * can be used, in all cases, to open a + * channel to an embedded GNSS chip. + * Note that channel zero is reserved + * for management operations and channel + * one is the existing AT interface; + * neither value can be used here. + * @param[out] ppDeviceSerial a pointer to a place to put the + * handle of the virtual serial port + * that is the multiplexer channel. + * @return zero on success or negative error + * code on failure. + */ +int32_t uCellMuxPrivateAddChannel(uCellPrivateInstance_t *pInstance, + int32_t channel, + uDeviceSerial_t **ppDeviceSerial); + +/** Disable CMUX on the given cellular instance. This does NOT free + * memory to ensure thread safety; only uCellMuxPrivateRemoveContext() + * frees memory. Note that this may cause the atHandle in pInstance to + * change, so if you have a local copy of it you will need to refresh + * it once this function returns. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + */ +int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance); + +/** Get the serial device for the given channel. + * + * @param[in] pContext the mux context. + * @param channel the channel number. + * @return the serial device. + */ +uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, + uint8_t channel); + +/** Close a CMUX channel. This does NOT free memory to ensure + * thread safety; only uCellMuxPrivateRemoveContext() frees memory. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pContext the mux context. + * @param channel the channel number to close. + */ +void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel); + +/* ---------------------------------------------------------------- + * FUNCTIONS: 3GPP 27.010 CMUX ENCODE/DECODE (SEE U_CELL_MUX_PRIVATE.C) * -------------------------------------------------------------- */ /** Encode a 3GPP 27.010 mux frame. @@ -364,18 +528,9 @@ int32_t uCellMuxPrivateEncode(uint8_t address, uCellMuxPrivateFrameType_t type, int32_t uCellMuxPrivateParseCmux(uParseHandle_t parseHandle, void *pUserParam); /* ---------------------------------------------------------------- - * FUNCTIONS: MISC + * FUNCTIONS: MISC (SEE U_CELL_MUX_PRIVATE.C) * -------------------------------------------------------------- */ -/** Get the serial device for the given channel. - * - * @param[in] pContext the mux context. - * @param channel the channel number. - * @return the serial device. - */ -uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, - uint8_t channel); - /** Copy the settings of one AT client into another AT client. * * @param atHandleSource the source AT client. @@ -385,28 +540,6 @@ uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pCont int32_t uCellMuxPrivateCopyAtClient(uAtClientHandle_t atHandleSource, uAtClientHandle_t atHandleDestination); -/** Close a CMUX channel. This does NOT free memory to ensure - * thread safety; only uCellMuxPrivateRemoveContext() frees memory. - * - * Note: gUCellPrivateMutex should be locked before this is called. - * - * @param[in] pContext the mux context. - * @param channel the channel number to close. - */ -void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel); - -/** Disable CMUX on the given cellular instance. This does NOT free - * memory to ensure thread safety; only uCellMuxPrivateRemoveContext() - * frees memory. Note that this may cause the atHandle in pInstance to - * change, so if you have a local copy of it you will need to refresh - * it once this function returns. - * - * Note: gUCellPrivateMutex should be locked before this is called. - * - * @param[in] pInstance a pointer to the cellular instance. - */ -int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance); - /** Remove the CMUX context for the given cellular instance. If CMUX * is active it will be disabled first. Note that this may cause the * atHandle in pInstance to change, so if you have a local copy of it diff --git a/cell/src/u_cell_net.c b/cell/src/u_cell_net.c index 44ac35f3e..0b2053f16 100644 --- a/cell/src/u_cell_net.c +++ b/cell/src/u_cell_net.c @@ -53,6 +53,8 @@ #include "u_at_client.h" +#include "u_sock.h" + #include "u_cell_module_type.h" #include "u_cell_file.h" #include "u_cell.h" // Order is @@ -61,6 +63,7 @@ #include "u_cell_info.h" #include "u_cell_apn_db.h" #include "u_cell_mno_db.h" +#include "u_cell_ppp_shared.h" #include "u_cell_pwr_private.h" @@ -262,10 +265,22 @@ static void activateContextCallback(uAtClientHandle_t atHandle, void *pParameter) { uCellPrivateInstance_t *pInstance = (uCellPrivateInstance_t *) pParameter; + uDeviceHandle_t cellHandle = pInstance->cellHandle; + char buffer[U_CELL_NET_IP_ADDRESS_SIZE]; + uSockAddress_t address; + uSockIpAddress_t *pIpAddress = NULL; (void) atHandle; activateContext(pInstance, U_CELL_NET_CONTEXT_ID, U_CELL_NET_PROFILE_ID); + + if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + if ((uCellNetGetIpAddressStr(cellHandle, buffer) > 0) && + (uSockStringToAddress(buffer, &address) > 0)) { + pIpAddress = &address.ipAddress; + } + uPortPppReconnect(cellHandle, pIpAddress); + } } // Set the current network status. @@ -1405,7 +1420,7 @@ static int32_t setAuthenticationMode(const uCellPrivateInstance_t *pInstance, const char *pPassword, uCellNetAuthenticationMode_t overrideAuthenticationMode) { - int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + int32_t errorCodeOrAuthenticationMode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; uAtClientHandle_t atHandle = pInstance->atHandle; uCellNetAuthenticationMode_t authenticationMode = pInstance->authenticationMode; @@ -1457,10 +1472,14 @@ static int32_t setAuthenticationMode(const uCellPrivateInstance_t *pInstance, } } uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); + errorCodeOrAuthenticationMode = uAtClientUnlock(atHandle); } - return errorCode; + if (errorCodeOrAuthenticationMode == 0) { + errorCodeOrAuthenticationMode = authenticationMode; + } + + return errorCodeOrAuthenticationMode; } // Get the APN currently in use 3GPP commands, required @@ -2190,6 +2209,40 @@ static int32_t parseDeepScanLine(uAtClientHandle_t atHandle, return errorCodeOrNumber; } +// Make the PPP connection +static void connectPpp(uDeviceHandle_t cellHandle, + const char *pUsername, + const char *pPassword, + uCellNetAuthenticationMode_t authenticationMode) +{ + char buffer1[U_CELL_NET_IP_ADDRESS_SIZE]; + char buffer2[U_CELL_NET_IP_ADDRESS_SIZE]; + uSockAddress_t address; + uSockIpAddress_t *pIpAddress = NULL; + uSockAddress_t dnsAddressPrimary; + uSockIpAddress_t *pDnsIpAddressPrimary = NULL; + uSockAddress_t dnsAddressSecondary; + uSockIpAddress_t *pDnsIpAddressSecondary = NULL; + + if ((uCellNetGetIpAddressStr(cellHandle, buffer1) > 0) && + (uSockStringToAddress(buffer1, &address) > 0)) { + pIpAddress = &address.ipAddress; + } + if (uCellNetGetDnsStr(cellHandle, false, buffer1, buffer2) > 0) { + if (uSockStringToAddress(buffer1, &dnsAddressPrimary) > 0) { + pDnsIpAddressPrimary = &dnsAddressPrimary.ipAddress; + } + if (uSockStringToAddress(buffer2, &dnsAddressSecondary) > 0) { + pDnsIpAddressSecondary = &dnsAddressSecondary.ipAddress; + } + } + // uPortPppAuthenticationMode_t matches uCellNetAuthenticationMode_t + // so this is fine + uPortPppConnect(cellHandle, pIpAddress, pDnsIpAddressPrimary, + pDnsIpAddressSecondary, pUsername, pPassword, + (uPortPppAuthenticationMode_t) authenticationMode); +} + /* ---------------------------------------------------------------- * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ @@ -2206,15 +2259,26 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, char buffer[15]; // At least 15 characters for the IMSI const char *pApnConfig = NULL; uCellNetAuthenticationMode_t overrideAuthenticationMode = U_CELL_NET_AUTHENTICATION_MODE_NOT_SET; + uCellNetAuthenticationMode_t authenticationModeUsed = overrideAuthenticationMode; + bool hasPpp = false; if (gUCellPrivateMutex != NULL) { + // It is possible that the user already has a PDP context + // up and wants to change it, in which case + // handleExistingContext() will close it. However, the + // PPP connection _must_ be taken down before that happens + // and we can't do so while the cellular API mutex is + // locked, so we alays take the PPP connection down first + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; if ((pInstance != NULL) && ((pUsername == NULL) || (pPassword != NULL))) { + hasPpp = U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_PPP); errorCode = (int32_t) U_CELL_ERROR_NOT_CONNECTED; if (uCellPrivateIsRegistered(pInstance)) { @@ -2301,6 +2365,10 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, pUsername, pPassword, overrideAuthenticationMode); + if (errorCode >= 0) { + authenticationModeUsed = errorCode; + errorCode = 0; + } } } if (errorCode == 0) { @@ -2410,6 +2478,11 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + + if ((errorCode == 0) && hasPpp) { + // Any PPP connection the platform may have attached is now up + connectPpp(cellHandle, pUsername, pPassword, authenticationModeUsed); + } } return errorCode; @@ -2507,15 +2580,26 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, char imsi[15]; const char *pApnConfig = NULL; uCellNetAuthenticationMode_t overrideAuthenticationMode = U_CELL_NET_AUTHENTICATION_MODE_NOT_SET; + uCellNetAuthenticationMode_t authenticationModeUsed = overrideAuthenticationMode; + bool hasPpp = false; if (gUCellPrivateMutex != NULL) { + // It is possible that the user already has a PDP context + // up and wants to change it, in which case + // handleExistingContext() will close it. However, the + // PPP connection _must_ be taken down before that happens + // and we can't do so while the cellular API mutex is + // locked, so we alays take the PPP connection down first + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; if ((pInstance != NULL) && ((pUsername == NULL) || (pPassword != NULL))) { + hasPpp = U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_PPP); errorCode = (int32_t) U_CELL_ERROR_NOT_REGISTERED; if (uCellPrivateIsRegistered(pInstance)) { @@ -2579,6 +2663,10 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, pUsername, pPassword, overrideAuthenticationMode); + if (errorCode >= 0) { + authenticationModeUsed = errorCode; + errorCode = 0; + } } if (errorCode == 0) { if (!uCellPrivateIsRegistered(pInstance)) { @@ -2637,6 +2725,11 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + + if ((errorCode == 0) && hasPpp) { + // Any PPP connection the platform may have attached is now up + connectPpp(cellHandle, pUsername, pPassword, authenticationModeUsed); + } } return errorCode; @@ -2652,6 +2745,10 @@ int32_t uCellNetDeactivate(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Let the platform, know that the PPP connection, + // is going down + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -2674,7 +2771,8 @@ int32_t uCellNetDeactivate(uDeviceHandle_t cellHandle, errorCode = deactivate(pInstance, U_CELL_NET_CONTEXT_ID); } } - if (errorCode != 0) { + if (errorCode == 0) { + } else { uPortLog("U_CELL_NET: unable to deactivate context.\n"); } } @@ -2697,6 +2795,10 @@ int32_t uCellNetDisconnect(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Let the platform, know that the PPP connection, + // is going down + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); diff --git a/cell/src/u_cell_ppp.c b/cell/src/u_cell_ppp.c new file mode 100644 index 000000000..9337b7161 --- /dev/null +++ b/cell/src/u_cell_ppp.c @@ -0,0 +1,586 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/* Only #includes of u_* and the C standard library are allowed here, + * no platform stuff and no OS stuff. Anything required from + * the platform/OS must be brought in through u_port* to maintain + * portability. + */ + +/** @file + * @brief Implementation of the PPP interface for cellular. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // memset(), strlen() +#include "stdio.h" // snprintf() +#include "ctype.h" // isprint() + +#include "u_cfg_sw.h" +#include "u_error_common.h" + +#include "u_port_clib_platform_specific.h" /* integer stdio, must be included + before the other port files if + any print or scan function is used. */ +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_debug.h" +#include "u_port_uart.h" +#include "u_port_event_queue.h" +#include "u_port_ppp.h" + +#include "u_interface.h" +#include "u_ringbuffer.h" + +#include "u_at_client.h" + +#include "u_device_shared.h" + +#include "u_cell_module_type.h" +#include "u_cell_file.h" +#include "u_cell.h" // Order is +#include "u_cell_net.h" // important here +#include "u_cell_private.h" // don't change it + +#include "u_cell_pwr.h" +#include "u_cell_pwr_private.h" +#include "u_cell_mux.h" +#include "u_cell_mux_private.h" + +#include "u_cell_ppp_shared.h" + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_CELL_PPP_DIAL_STRING +/** The string that performs a PPP dialed-up, intended to have the. + * %d and %s replaced with the PDP context ID and the AT command + * send delimiter respectively. + */ +# define U_CELL_PPP_DIAL_STRING "ATD*99***%d#%s" +#endif + +#ifndef U_CELL_PPP_DIAL_RESPONSE_STRING +/** The string that indicates PPP has connected, sent by the module + * in response to #U_CELL_PPP_DIAL_STRING. + * Note: deliberately omits the end as some modules (e.g. SARA-R4) + * respond with things like "\r\n CONNECT 150000000\r\n". + */ +# define U_CELL_PPP_DIAL_RESPONSE_STRING "\r\nCONNECT" +#endif + +#ifndef U_CELL_PPP_HANG_UP_STRING +/** The string to send to hang-up the PPP interface. + */ +# define U_CELL_PPP_HANG_UP_STRING "~+++" +#endif + +#ifndef U_CELL_PPP_HANG_UP_RESPONSE_STRING +/** The string that indicates a successful hang-up of the PPP + * interface, sent by the module in response to + * #U_CELL_PPP_HANG_UP_STRING. + */ +# define U_CELL_PPP_HANG_UP_RESPONSE_STRING "\r\nNO CARRIER\r\n" +#endif + +#ifndef U_CELL_PPP_D2_STRING +/** The &D2 string, to be sent to set the hang-up action to really + * mean hang-up, rather than return to AT command mode, where the + * %s is intended to be replaced by the AT command delimiter. + */ +# define U_CELL_PPP_D2_STRING "AT&D2%s" +#endif + +#ifndef U_CELL_PPP_OK_STRING +/** The "OK string on the PPP interface when operated in command + * mode (i.e. an AT interface), sent in response to + * #U_CELL_PPP_D2_STRING. + */ +# define U_CELL_PPP_OK_STRING "\r\nOK\r\n" +#endif + +#ifndef U_CELL_PPP_ERROR_STRING +/** The "ERROR" string on the PPP interface when operated in + * command mode (i.e. an AT interface), may be sent at any time. + */ +# define U_CELL_PPP_ERROR_STRING "\r\nERROR\r\n" +#endif + +#ifndef U_CELL_PPP_ERROR_STRING_LENGTH +/** The length of #U_CELL_PPP_ERROR_STRING. + */ +# define U_CELL_PPP_ERROR_STRING_LENGTH 9 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** The context data for PPP operation. + */ +typedef struct { + uDeviceHandle_t cellHandle; + uDeviceSerial_t *pDeviceSerial; + uPortPppReceiveCallback_t *pReceiveCallback; + void *pReceiveCallbackParam; + char *pReceiveBuffer; + size_t receiveBufferSize; + bool receiveBufferIsMalloced; + bool muxAlreadyEnabled; + bool uartSleepWakeOnDataWasEnabled; +} uCellPppContext_t; + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Check if the given buffer contains the given string; returns +// the number of characters matched: if this is non-zero but less +// than the length of pStr then the caller should keep that many +// characters in the buffer and call this again with any additions. +static int32_t containsString(const char *pBuffer, size_t size, const char *pStr) +{ + size_t count = 0; + size_t y = 0; + + if (pStr != NULL) { + y = strlen(pStr); + for (size_t x = 0; (x < size) && (count < y); x++) { + if (*pBuffer == *(pStr + count)) { + count++; + } else { + count = 0; + if (*pBuffer == *pStr) { + count = 1; + } + } + pBuffer++; + } + } + + return count; +} + +// Print out a buffer of sent or received characters nicely. +static void printBuffer(const char *pBuffer, size_t size) +{ + for (size_t x = 0; x < size; x++) { + if (!isprint((int32_t) *pBuffer)) { + // Print the hex + uPortLog("[%02x]", (unsigned char) *pBuffer); + } else { + // Print the ASCII character + uPortLog("%c", *pBuffer); + } + pBuffer++; + } +} + +// A very minimal AT send/receive function to avoid having +// to use the full AT parser on the PPP channel. +static int32_t sendExpect(uCellPppContext_t *pContext, + const char *pSendString, + const char *pExpectedResponseString, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle), + size_t timeoutSeconds) +{ + int32_t errorCode = 0; + uDeviceSerial_t *pDeviceSerial = pContext->pDeviceSerial; + uDeviceHandle_t cellHandle = pContext->cellHandle; + char buffer[64]; + int32_t timeoutMs = timeoutSeconds * 1000; + int32_t startTimeMs; + int32_t expectedResponseLength; + int32_t x = 0; + int32_t y = 0; + + if (pSendString != NULL) { + x = strlen(pSendString); + errorCode = pDeviceSerial->write(pDeviceSerial, pSendString, x); + } + if (errorCode == x) { + if (x > 0) { + uPortLog("U_CELL_PPP: sent "); + printBuffer(pSendString, x); + uPortLog("\n"); + } + if (pExpectedResponseString != NULL) { + expectedResponseLength = strlen(pExpectedResponseString); + // Wait for a response to come back + errorCode = (int32_t) U_ERROR_COMMON_TIMEOUT; + startTimeMs = uPortGetTickTimeMs(); + while ((errorCode == (int32_t) U_ERROR_COMMON_TIMEOUT) && + (uPortGetTickTimeMs() - startTimeMs < timeoutMs) && + ((pKeepGoingCallback == NULL) || (pKeepGoingCallback(cellHandle)))) { + x = pDeviceSerial->read(pDeviceSerial, buffer + y, sizeof(buffer) - y); + if (x > 0) { + x += y; + uPortLog("U_CELL_PPP: received "); + printBuffer(buffer, x); + uPortLog("\n"); + y = containsString(buffer, x, pExpectedResponseString); + if (y == expectedResponseLength) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else if (y == 0) { + y = containsString(buffer, x, U_CELL_PPP_ERROR_STRING); + if (y == U_CELL_PPP_ERROR_STRING_LENGTH) { + errorCode = (int32_t) U_ERROR_COMMON_DEVICE_ERROR; + } + } + // Keep y characters, in case there was a partial match, + // moved down to the start of the buffer + memmove(buffer, buffer + y, sizeof(buffer) - y); + } else { + // Wait a little while for more to arrive + uPortTaskBlock(100); + } + } + } + } else { + errorCode = (int32_t) U_ERROR_COMMON_DEVICE_ERROR; + } + + return errorCode; +} + +// Make the PPP connection over the AT interface. +static int32_t connectPpp(uCellPppContext_t *pContext, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle)) +{ + int32_t errorCode; + char buffer[16]; // Enough room for "ATD*99***1#\r" + + // Before we do anything else, send AT&D2: this is necessary + // to ensure that sending "~+++" deactivates PPP, rather than + // just returning to command mode, and it HAS to be done on + // this channel as the one that was done at start of day + // on the original AT channel does not apply here (i.e. all + // the AT&x commmands are AT-channel-specific) + snprintf(buffer, sizeof(buffer), U_CELL_PPP_D2_STRING, U_AT_CLIENT_COMMAND_DELIMITER); + errorCode = sendExpect(pContext, buffer, U_CELL_PPP_OK_STRING, + pKeepGoingCallback, U_AT_CLIENT_DEFAULT_TIMEOUT_MS / 1000); + if (errorCode == 0) { + snprintf(buffer, sizeof(buffer), U_CELL_PPP_DIAL_STRING, + U_CELL_NET_CONTEXT_ID, U_AT_CLIENT_COMMAND_DELIMITER); + errorCode = sendExpect(pContext, buffer, U_CELL_PPP_DIAL_RESPONSE_STRING, + pKeepGoingCallback, U_CELL_PPP_DIAL_TIMEOUT_SECONDS); + } + + return errorCode; +} + +// Close the PPP interface. +void closePpp(uCellPrivateInstance_t *pInstance, + bool pppTerminateRequired) +{ + uCellPppContext_t *pContext; + uDeviceSerial_t *pDeviceSerial; + + if (pInstance != NULL) { + pContext = (uCellPppContext_t *) pInstance->pPppContext; + // Note: we don't free the context or any allocated receive + // buffer to ensure thread-safety of the callback + if (pContext != NULL) { + if (pContext->pDeviceSerial != NULL) { + pDeviceSerial = pContext->pDeviceSerial; + if (pppTerminateRequired) { + // Send the sequence hang-up sequence, "~+++", + // preceded by a two second delay (so that the + // module can distinguish it from data) on the + // channel, to ensure that it is hung-up, and + // wait for the "NO CARRIER" response + // First, remove the callback so that we get the + // response + pDeviceSerial->eventCallbackRemove(pDeviceSerial); + uPortTaskBlock(2000); + sendExpect(pContext, U_CELL_PPP_HANG_UP_STRING, + U_CELL_PPP_HANG_UP_RESPONSE_STRING, NULL, + U_CELL_PPP_HANG_UP_TIMEOUT_SECONDS); + } + // Remove the multiplexer channel + uCellMuxPrivateCloseChannel((uCellMuxPrivateContext_t *) pInstance->pMuxContext, + U_CELL_MUX_PRIVATE_CHANNEL_ID_PPP); + pContext->pDeviceSerial = NULL; + } + if (!pContext->muxAlreadyEnabled) { + // Disable the multiplexer if one was in use + // and it was us who started it + uCellMuxPrivateDisable(pInstance); + } + // Re-enable UART sleep if we had switched it off + if (pContext->uartSleepWakeOnDataWasEnabled) { + uCellPwrPrivateEnableUartSleep(pInstance); + } + } + } +} + +// Callback for data received over the PPP CMUX channel. +static void callback(uDeviceSerial_t *pDeviceSerial, + uint32_t eventBitmask, void *pParameters) +{ + uCellPppContext_t *pContext = (uCellPppContext_t *) pParameters; + int32_t bytesRead; + + if ((eventBitmask & U_PORT_UART_EVENT_BITMASK_DATA_RECEIVED) && + (pContext != NULL) && (pContext->pReceiveBuffer != NULL)) { + bytesRead = pDeviceSerial->read(pDeviceSerial, + pContext->pReceiveBuffer, + pContext->receiveBufferSize); + if ((bytesRead > 0) && (pContext->pReceiveCallback != NULL)) { + pContext->pReceiveCallback(pContext->cellHandle, + pContext->pReceiveBuffer, bytesRead, + pContext->pReceiveCallbackParam); + } + } +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO CELLULAR + * -------------------------------------------------------------- */ + +// Free context. +void uCellPppPrivateRemoveContext(uCellPrivateInstance_t *pInstance) +{ + uCellPppContext_t *pContext; + + if ((pInstance != NULL) && (pInstance->pPppContext != NULL)) { + closePpp(pInstance, false); + pContext = (uCellPppContext_t *) pInstance->pPppContext; + if (pContext->receiveBufferIsMalloced) { + // The receive buffer was malloc'ed, free it now + uPortFree(pContext->pReceiveBuffer); + } + uPortFree(pContext); + pInstance->pPppContext = NULL; + } +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Open the PPP interface of a cellular module. +int32_t uCellPppOpen(uDeviceHandle_t cellHandle, + uPortPppReceiveCallback_t *pReceiveCallback, + void *pReceiveCallbackParam, + char *pReceiveData, size_t receiveDataSize, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle)) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + uCellPppContext_t *pContext; + uDeviceSerial_t *pDeviceSerial; + bool pppTerminateRequired = false; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + // No point even trying if we're not on the network + errorCode = (int32_t) U_CELL_ERROR_NOT_REGISTERED; + if (uCellPrivateIsRegistered(pInstance)) { + pContext = (uCellPppContext_t *) pInstance->pPppContext; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pContext == NULL) { + // Allocate memory for the context + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pContext = (uCellPppContext_t *) pUPortMalloc(sizeof(*pContext)); + if (pContext != NULL) { + memset(pContext, 0, sizeof(*pContext)); + pContext->cellHandle = cellHandle; + } + } + if ((pContext != NULL) && (pContext->pDeviceSerial == NULL)) { + // Have a context and the serial device for PPP is not set up + if (pContext->receiveBufferIsMalloced) { + // There is a previously malloc'ed buffer: free it now + uPortFree(pContext->pReceiveBuffer); + pContext->receiveBufferIsMalloced = false; + } + pContext->pReceiveBuffer = pReceiveData; + pContext->receiveBufferSize = receiveDataSize; + if ((pReceiveCallback != NULL) && (pContext->pReceiveBuffer == NULL)) { + // Allocate memory for the receive data buffer + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pContext->pReceiveBuffer = (char *) pUPortMalloc(receiveDataSize); + if (pContext->pReceiveBuffer != NULL) { + pContext->receiveBufferIsMalloced = true; + } + } + if ((pContext != NULL) && + ((pReceiveCallback == NULL) || (pContext->pReceiveBuffer != NULL))) { + // Have a context and either don't need a receive buffer or we have one + pInstance->pPppContext = pContext; + pContext->pReceiveCallback = pReceiveCallback; + pContext->pReceiveCallbackParam = pReceiveCallbackParam; + // Determine if CMUX and "wake-up on UART data line" UART power saving + // are already enabled + pContext->muxAlreadyEnabled = uCellMuxPrivateIsEnabled(pInstance); + pContext->uartSleepWakeOnDataWasEnabled = uCellPwrPrivateUartSleepIsEnabled(pInstance); + if (uCellPwrPrivateGetDtrPowerSavingPin(pInstance) >= 0) { + pContext->uartSleepWakeOnDataWasEnabled = false; + } + // Enable CMUX + errorCode = uCellMuxPrivateEnable(pInstance); + if (errorCode == 0) { + // Add the PPP channel + errorCode = uCellMuxPrivateAddChannel(pInstance, + U_CELL_MUX_PRIVATE_CHANNEL_ID_PPP, + &(pContext->pDeviceSerial)); + } + if (errorCode == 0) { + // If we're on wake-up-on-data UART power saving and CMUX, switch + // UART power saving off, just in case + errorCode = uCellPwrPrivateDisableUartSleep(pInstance); + } + if (errorCode == 0) { + pDeviceSerial = pContext->pDeviceSerial; + // We now have a second serial interface to + // the module: do a PPP dial-up on it. Could + // attach an AT handler to it but that would be + // an overhead in terms of RAM that we can do + // without, instead just send the dial-up string + // and wait for the response + uPortTaskBlock(1000); + errorCode = connectPpp(pContext, pKeepGoingCallback); + if ((errorCode == 0) && (pReceiveCallback != NULL)) { + pppTerminateRequired = (errorCode == 0); + // Note: the priority and stack size parameters + // to eventCallbackSet() are ignored, hence use of -1 + errorCode = pDeviceSerial->eventCallbackSet(pDeviceSerial, + U_DEVICE_SERIAL_EVENT_BITMASK_DATA_RECEIVED, + callback, pContext, + -1, -1); + } + } + if (errorCode < 0) { + // Tidy up on error + closePpp(pInstance, pppTerminateRequired); + } + } + } + } + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCode; +} + +// Close the PPP interface of a cellular module. +int32_t uCellPppClose(uDeviceHandle_t cellHandle, + bool pppTerminateRequired) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + closePpp(pInstance, pppTerminateRequired); + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCode; +} + +// Transmit a buffer of data over the PPP interface. +int32_t uCellPppTransmit(uDeviceHandle_t cellHandle, + const char *pData, size_t dataSize) +{ + int32_t errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + uCellPppContext_t *pContext; + uDeviceSerial_t *pDeviceSerial; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pContext = (uCellPppContext_t *) pInstance->pPppContext; + if ((pContext != NULL) && (pContext->pDeviceSerial != NULL)) { + pDeviceSerial = pContext->pDeviceSerial; + errorCodeOrBytesSent = pDeviceSerial->write(pDeviceSerial, + pData, dataSize); + } + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCodeOrBytesSent; +} + +// Free memory. +void uCellPppFree(uDeviceHandle_t cellHandle) +{ + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + pInstance = pUCellPrivateGetInstance(cellHandle); + if ((pInstance != NULL) && (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP))) { + uCellPppPrivateRemoveContext(pInstance); + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } +} + +// End of file diff --git a/cell/src/u_cell_ppp_private.h b/cell/src/u_cell_ppp_private.h new file mode 100644 index 000000000..45a21f3de --- /dev/null +++ b/cell/src/u_cell_ppp_private.h @@ -0,0 +1,66 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +#ifndef _U_CELL_PPP_PRIVATE_H_ +#define _U_CELL_PPP_PRIVATE_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +/** @file + * @brief This header file defines functions that are private to + * cellular and associated with PPP. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Remove the PPP context for the given cellular instance. If PPP + * is active it will be disabled first. Note that this may cause the + * atHandle in pInstance to change, so if you have a local copy of it + * you will need to refresh it once this function returns. + * + * This should be called _before_ uCellMuxPrivateRemoveContext(), + * since it cleans up CMUX stuff of its own. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + */ +void uCellPppPrivateRemoveContext(uCellPrivateInstance_t *pInstance); + +#ifdef __cplusplus +} +#endif + +#endif // _U_CELL_PPP_PRIVATE_H_ + +// End of file diff --git a/cell/src/u_cell_ppp_shared.h b/cell/src/u_cell_ppp_shared.h new file mode 100644 index 000000000..30baf0242 --- /dev/null +++ b/cell/src/u_cell_ppp_shared.h @@ -0,0 +1,200 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +#ifndef _U_CELL_PPP_SHARED_H_ +#define _U_CELL_PPP_SHARED_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +#include "u_port_ppp.h" + +/** @file + * @brief This header file defines functions that expose the PPP + * transport for cellular. They are not intended for direct customer + * use, they are shared internally with the port layer which then + * integrates with the bottom-end of the IP stack of a platform. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_CELL_PPP_DIAL_TIMEOUT_SECONDS +/** The time in seconds to wait for a PPP dial-up to succeed. + */ +# define U_CELL_PPP_DIAL_TIMEOUT_SECONDS 240 +#endif + +#ifndef U_CELL_PPP_HANG_UP_TIMEOUT_SECONDS +/** How long to wait for PPP to disconnect, that is to return + * "NO CARRIER" after the hang-up sequence "~+++" has been sent. + */ +# define U_CELL_PPP_HANG_UP_TIMEOUT_SECONDS 30 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Open the PPP interface of a cellular module; only works with + * modules where CMUX is supported (so, for example, does not work + * on LARA-R6). The cellular network connection should already have + * been brought up using uCellNetConnect() or uCellNetActivate(); all + * this does is open the PPP data interface. If the PPP interface is + * already open this function will do nothing and return success; + * please call uCellPppClose() first if you would like to change + * the buffering arrangements, the callback or its parameter. + * + * Note: this will invoke multiplexer mode in the cellular device + * and hence will only work on interfaces that support multiplexer + * mode (for example the USB interface of a cellular device does not + * support multiplexer mode). Also, since multiplexer mode is a + * frame-oriented protocol it will be broken if there a character + * is lost on the interface and hence, on a UART interface, it is + * HIGHLY RECOMMENDED that the UART flow control lines are + * connected. + * + * Note: this function will allocate memory that is not released, + * for thread-safety reasons, until the cellular device is closed. + * If you need the heap memory back before then, see uCellPppFree(). + * + * Implementation note: follows the function signature of + * #uPortPppConnectCallback_t. + * + * @param cellHandle the handle of the cellular instance. + * @param[in] pReceiveCallback the data reception callback; may be + * NULL if only data transmission is + * required. + * @param[in,out] pReceiveCallbackParam a parameter that will be passed to + * pReceiveCallback as its last parameter; + * may be NULL, ignored if pReceiveCallback + * is NULL. + * @param[in] pReceiveData a pointer to a buffer for received + * data; may be NULL, in which case, if + * pReceiveCallback is non-NULL, this code + * will provide a receive buffer. + * @param receiveDataSize the amount of space at pReceiveData in + * bytes or, if pReceiveData is NULL, + * the receive buffer size that should + * be allocated by this function; + * #U_PORT_PPP_RECEIVE_BUFFER_BYTES is + * a sensible value. + * @param[in] pKeepGoingCallback a callback function that governs how + * long to wait for the PPP connection to + * open. This function is called once a + * second while waiting for the "CONNECT" + * response; the PPP open attempt will + * only continue while it returns true. + * This allows the caller to terminate + * the connection attempt at their + * convenience. May be NULL, in which case + * the connection attempt will eventually + * time out on failure. + * @return zero on success, else negative error + * code. + */ +int32_t uCellPppOpen(uDeviceHandle_t cellHandle, + uPortPppReceiveCallback_t *pReceiveCallback, + void *pReceiveCallbackParam, + char *pReceiveData, size_t receiveDataSize, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle)); + +/** Close the PPP interface of a cellular module. This does not + * deactivate the cellular connection, the caller must do that + * afterwards with a call to uCellNetDisconnect() or + * uCellNetDeactivate(). When this function has returned the + * pReceiveCallback function passed to uCellPppOpen() will no longer be + * called and any pReceiveData buffer passed to uCellPppOpen() + * will no longer be written-to. If no PPP connection is open this + * function will do nothing and return success. + * + * Note: in the case of SARA-R5, and potentially other modules, + * it is vital that the PPP session is closed cleanly by the + * application before this function is called; not doing so may + * lead to the PPP entity being unresponsive in subsequent calls + * to uCellPppOpen(). Should that occur, the sure way out is + * to reboot the module, e.g. with uCellPwrReboot(), before + * calling uCellPppOpen() again. + * + * Implementation note: follows the function signature of + * #uPortPppDisconnectCallback_t. + * + * @param cellHandle the handle of the cellular instance. + * @param pppTerminateRequired set this to true if the PPP connection + * should be terminated first or leave + * as false if the PPP connection + * has already been terminated by + * the peer; be sure to get this right + * for the SARA-R5 case. + * @return zero on success, else negative error + * code. + */ +int32_t uCellPppClose(uDeviceHandle_t cellHandle, + bool pppTerminateRequired); + +/** Transmit data over the PPP interface of the cellular module. + * This may be integrated into a higher layer, e.g. the PPP + * interface at the bottom of an IP stack, to permit it to send + * PPP frames over a cellular transport. uCellPppOpen() must have + * been called for transmission to succeed. + * + * Implementation note: follows the function signature of + * #uPortPppTransmitCallback_t. + * + * @param cellHandle the handle of the cellular instance. + * @param[in] pData a pointer to the data to transmit; can only + * be NULL if dataSize is zero. + * @param dataSize the number of bytes of data at pData. + * @return on success the number bytes transmitted, + * which may be less than dataSize, else + * negative error code. + */ +int32_t uCellPppTransmit(uDeviceHandle_t cellHandle, + const char *pData, size_t dataSize); + +/** uCellPppClose() does not free memory in order to ensure + * thread-safety; memory is only free'ed when the cellular instance + * is closed. However, if you can't wait, you really need that + * memory back, and you are absolutely sure that there is no chance + * of an asynchronous receive occurring, you may call this function + * to regain heap. Note that this only does the memory-freeing part, + * not the closing down part, i.e. you must have called + * uCellPppClose() and, to really ensure thread-safety, also called + * uCellNetDisconnect() or uCellNetDeactivate(), for it to have + * any effect. + * + * @param cellHandle the handle of the cellular instance. + */ +void uCellPppFree(uDeviceHandle_t cellHandle); + +#ifdef __cplusplus +} +#endif + +#endif // _U_CELL_PPP_SHARED_H_ + +// End of file diff --git a/cell/src/u_cell_private.c b/cell/src/u_cell_private.c index d36681467..70882538a 100644 --- a/cell/src/u_cell_private.c +++ b/cell/src/u_cell_private.c @@ -116,6 +116,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_AUTHENTICATION_MODE_AUTOMATIC) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 6, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -145,6 +147,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -179,6 +183,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -203,6 +209,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_CMUX) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -251,7 +259,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_AUTHENTICATION_MODE_AUTOMATIC) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | - (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) | + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_PPP) /* features */ ), 4, /* Default CMUX channel for GNSS */ 16 /* AT+CFUN reboot command */ @@ -279,6 +288,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -322,7 +333,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_SNR_REPORTED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | - (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) | + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_PPP) /* features */ ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -356,6 +368,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -387,6 +401,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { // save the response to file (in fact it leaves any previous response file there, // unchanged) and returns error rather than success. This makes it impossible to // support proper HTTP operation. + // Similarly, LENA-R8 supports PPP but it fails LCP negotiation at start of day + // and hence it is not marked as supported ), -1, /* Default CMUX channel for GNSS */ 16 /* AT+CFUN reboot command */ diff --git a/cell/src/u_cell_private.h b/cell/src/u_cell_private.h index 702c89add..a5f279580 100644 --- a/cell/src/u_cell_private.h +++ b/cell/src/u_cell_private.h @@ -242,7 +242,8 @@ typedef enum { U_CELL_PRIVATE_FEATURE_AUTHENTICATION_MODE_AUTOMATIC, U_CELL_PRIVATE_FEATURE_LWM2M, U_CELL_PRIVATE_FEATURE_UCGED, - U_CELL_PRIVATE_FEATURE_HTTP + U_CELL_PRIVATE_FEATURE_HTTP, + U_CELL_PRIVATE_FEATURE_PPP } uCellPrivateFeature_t; /** The characteristics that may differ between cellular modules. @@ -485,6 +486,7 @@ typedef struct uCellPrivateInstance_t { void *pCellTimeContext; /**< Hook for CellTime context. */ void *pCellTimeCellSyncContext; /**< Hook for CellTime cell synchronisation context. */ void *pFenceContext; /**< Storage for a uGeofenceContext_t. */ + void *pPppContext; /**< Hook for a PPP connection context. */ struct uCellPrivateInstance_t *pNext; } uCellPrivateInstance_t; diff --git a/cell/src/u_cell_pwr.c b/cell/src/u_cell_pwr.c index d8f7a102b..3eb3efcde 100644 --- a/cell/src/u_cell_pwr.c +++ b/cell/src/u_cell_pwr.c @@ -58,6 +58,7 @@ #include "u_cell_cfg.h" #include "u_cell_mux.h" #include "u_cell_mux_private.h" +#include "u_cell_ppp_shared.h" #include "u_cell_pwr.h" #include "u_cell_pwr_private.h" @@ -1356,6 +1357,12 @@ int32_t uCellPwrPrivateOn(uCellPrivateInstance_t *pInstance, quickPowerOff(pInstance, pKeepGoingCallback); } + if ((errorCode == 0) && U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + // A PPP connection may now be opened by a platform + uPortPppAttach(pInstance->cellHandle, uCellPppOpen, uCellPppClose, uCellPppTransmit); + } + // If we were successful, were asleep at the start and there is // a wake-up callback then call it if (asleepAtStart && (errorCode == 0) && (pSleepContext != NULL) && @@ -1708,6 +1715,134 @@ int32_t uCellPwrPrivateGetEDrx(const uCellPrivateInstance_t *pInstance, return errorCode; } +// Get the DTR power-saving pin. +int32_t uCellPwrPrivateGetDtrPowerSavingPin(const uCellPrivateInstance_t *pInstance) +{ + int32_t errorCodeOrPin = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + + if (pInstance != NULL) { + errorCodeOrPin = (int32_t) U_ERROR_COMMON_NOT_FOUND; + if (pInstance->pinDtrPowerSaving >= 0) { + errorCodeOrPin = pInstance->pinDtrPowerSaving; + } + } + + return errorCodeOrPin; +} + +// Disable 32 kHz sleep. +int32_t uCellPwrPrivateDisableUartSleep(uCellPrivateInstance_t *pInstance) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uCellPrivateUartSleepCache_t *pUartSleepCache; + uAtClientHandle_t atHandle; + + if (pInstance != NULL) { + pUartSleepCache = &(pInstance->uartSleepCache); + // If a wake-up handler has been set then the module supports + // UART sleep, if it has not then it doesn't and we can say so + atHandle = pInstance->atHandle; + // If a sleep handler is not set then sleep is already + // disabled, so that's fine + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (uAtClientWakeUpHandlerIsSet(atHandle)) { + // Read and stash the current UART sleep parameters + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+UPSV?"); + uAtClientCommandStop(atHandle); + uAtClientResponseStart(atHandle, "+UPSV:"); + pUartSleepCache->mode = uAtClientReadInt(atHandle); + if ((pUartSleepCache->mode == 1) || + ((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LENA_R8) && + (pUartSleepCache->mode == 4))) { + // Mode 1 has a time attached, as does mode 4 but only if this + // is LENA-R8 + pUartSleepCache->sleepTime = uAtClientReadInt(atHandle); + } + uAtClientResponseStop(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + // Now switch off sleep and remove the handler, + // so that everyone knows sleep is gone + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+UPSV="); + uAtClientWriteInt(atHandle, 0); + uAtClientCommandStopReadResponse(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + uAtClientSetWakeUpHandler(atHandle, NULL, NULL, 0); + } + } + } + } + + return errorCode; +} + +// Enable 32 kHz sleep. +int32_t uCellPwrPrivateEnableUartSleep(uCellPrivateInstance_t *pInstance) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uCellPrivateUartSleepCache_t *pUartSleepCache; + uAtClientHandle_t atHandle; + + if (pInstance != NULL) { + pUartSleepCache = &(pInstance->uartSleepCache); + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + atHandle = pInstance->atHandle; + if (uAtClientWakeUpHandlerIsSet(atHandle)) { + // If the sleep handler is set the sleep is already + // enabled, there is nothing to do + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + // If no sleep handler is set then either sleep + // is not supported or it has been disabled: + // if it has been disabled then the cache + // will contain the previous mode so check it + if (pUartSleepCache->mode > 0) { + // There is a cached mode, put it back again +#ifndef U_CFG_CELL_DISABLE_UART_POWER_SAVING + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+UPSV="); + uAtClientWriteInt(atHandle, pUartSleepCache->mode); + if (pUartSleepCache->mode == 1) { + // Mode 1 has a time + uAtClientWriteInt(atHandle, pUartSleepCache->sleepTime); + } + uAtClientCommandStopReadResponse(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + // Empty the cache so that we know sleep + // has been re-enabled + pUartSleepCache->mode = 0; + pUartSleepCache->sleepTime = 0; + uAtClientSetWakeUpHandler(atHandle, uCellPrivateWakeUpCallback, pInstance, + (U_CELL_POWER_SAVING_UART_INACTIVITY_TIMEOUT_SECONDS * 1000) - + U_CELL_POWER_SAVING_UART_WAKEUP_MARGIN_MILLISECONDS); + } else { + // Return a clearer error code than "AT error" + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + } +#endif + } + } + } + + return errorCode; +} + +// Determine whether UART, AKA 32 kHz, sleep is enabled or not. +bool uCellPwrPrivateUartSleepIsEnabled(const uCellPrivateInstance_t *pInstance) +{ + bool isEnabled = false; + + if (pInstance != NULL) { + isEnabled = uAtClientWakeUpHandlerIsSet(pInstance->atHandle); + } + + return isEnabled; +} + /* ---------------------------------------------------------------- * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ @@ -1797,6 +1932,9 @@ int32_t uCellPwrOff(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -1822,6 +1960,9 @@ int32_t uCellPwrOffHard(uDeviceHandle_t cellHandle, bool trulyHard, if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -1913,6 +2054,9 @@ int32_t uCellPwrReboot(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -2053,6 +2197,9 @@ int32_t uCellPwrResetHard(uDeviceHandle_t cellHandle, int32_t pinReset) if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -2793,51 +2940,14 @@ int32_t uCellPwrDisableUartSleep(uDeviceHandle_t cellHandle) { int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; uCellPrivateInstance_t *pInstance; - uCellPrivateUartSleepCache_t *pUartSleepCache; - uAtClientHandle_t atHandle; if (gUCellPrivateMutex != NULL) { U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pModule != NULL)) { - pUartSleepCache = &(pInstance->uartSleepCache); - // If a wake-up handler has been set then the module supports - // UART sleep, if it has not then it doesn't and we can say so - atHandle = pInstance->atHandle; - // If a sleep handler is not set then sleep is already - // disabled, so that's fine - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (uAtClientWakeUpHandlerIsSet(atHandle)) { - // Read and stash the current UART sleep parameters - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "AT+UPSV?"); - uAtClientCommandStop(atHandle); - uAtClientResponseStart(atHandle, "+UPSV:"); - pUartSleepCache->mode = uAtClientReadInt(atHandle); - if ((pUartSleepCache->mode == 1) || - ((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LENA_R8) && - (pUartSleepCache->mode == 4))) { - // Mode 1 has a time attached, as does mode 4 but only if this - // is LENA-R8 - pUartSleepCache->sleepTime = uAtClientReadInt(atHandle); - } - uAtClientResponseStop(atHandle); - errorCode = uAtClientUnlock(atHandle); - if (errorCode == 0) { - // Now switch off sleep and remove the handler, - // so that everyone knows sleep is gone - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "AT+UPSV="); - uAtClientWriteInt(atHandle, 0); - uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); - if (errorCode == 0) { - uAtClientSetWakeUpHandler(atHandle, NULL, NULL, 0); - } - } - } + if (pInstance != NULL) { + errorCode = uCellPwrPrivateDisableUartSleep(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -2851,54 +2961,14 @@ int32_t uCellPwrEnableUartSleep(uDeviceHandle_t cellHandle) { int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; uCellPrivateInstance_t *pInstance; - uCellPrivateUartSleepCache_t *pUartSleepCache; - uAtClientHandle_t atHandle; if (gUCellPrivateMutex != NULL) { U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pModule != NULL)) { - pUartSleepCache = &(pInstance->uartSleepCache); - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - atHandle = pInstance->atHandle; - if (uAtClientWakeUpHandlerIsSet(atHandle)) { - // If the sleep handler is set the sleep is already - // enabled, there is nothing to do - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - } else { - // If no sleep handler is set then either sleep - // is not supported or it has been disabled: - // if it has been disabled then the cache - // will contain the previous mode so check it - if (pUartSleepCache->mode > 0) { - // There is a cached mode, put it back again -#ifndef U_CFG_CELL_DISABLE_UART_POWER_SAVING - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "AT+UPSV="); - uAtClientWriteInt(atHandle, pUartSleepCache->mode); - if (pUartSleepCache->mode == 1) { - // Mode 1 has a time - uAtClientWriteInt(atHandle, pUartSleepCache->sleepTime); - } - uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); - if (errorCode == 0) { - // Empty the cache so that we know sleep - // has been re-enabled - pUartSleepCache->mode = 0; - pUartSleepCache->sleepTime = 0; - uAtClientSetWakeUpHandler(atHandle, uCellPrivateWakeUpCallback, pInstance, - (U_CELL_POWER_SAVING_UART_INACTIVITY_TIMEOUT_SECONDS * 1000) - - U_CELL_POWER_SAVING_UART_WAKEUP_MARGIN_MILLISECONDS); - } else { - // Return a clearer error code than "AT error" - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - } -#endif - } - } + if (pInstance != NULL) { + errorCode = uCellPwrPrivateEnableUartSleep(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -2918,8 +2988,8 @@ bool uCellPwrUartSleepIsEnabled(uDeviceHandle_t cellHandle) U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pModule != NULL)) { - isEnabled = uAtClientWakeUpHandlerIsSet(pInstance->atHandle); + if (pInstance != NULL) { + isEnabled = uCellPwrPrivateUartSleepIsEnabled(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); diff --git a/cell/src/u_cell_pwr_private.h b/cell/src/u_cell_pwr_private.h index 7015a508c..af2114f1b 100644 --- a/cell/src/u_cell_pwr_private.h +++ b/cell/src/u_cell_pwr_private.h @@ -114,6 +114,7 @@ int32_t uCellPwrPrivatePeriodicWakeupStrToSeconds(const char *pStr, int32_t *pSeconds); /** Get the 3GPP power saving settings. + * * Note: gUCellPrivateMutex should be locked before this is called. * * @param pInstance a pointer to the cellular instance. @@ -142,6 +143,7 @@ int32_t uCellPwrPrivateGet3gppPowerSaving(uCellPrivateInstance_t *pInstance, int32_t *pPeriodicWakeupSeconds); /** Get the E-DRX settings for the given RAT. + * * Note: gUCellPrivateMutex should be locked before this is called. * * @param pInstance a pointer to the cellular instance. @@ -172,6 +174,51 @@ int32_t uCellPwrPrivateGetEDrx(const uCellPrivateInstance_t *pInstance, int32_t *pEDrxSeconds, int32_t *pPagingWindowSeconds); +/** Get the DTR power-saving pin. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return the pin of this MCU that is connected to + * the DTR line of the cellular module, as + * set by uCellPwrSetDtrPowerSavingPin(), + * or negative error code. + */ +int32_t uCellPwrPrivateGetDtrPowerSavingPin(const uCellPrivateInstance_t *pInstance); + +/** Disable UART, AKA 32 kHz, sleep. 32 kHz sleep is always + * enabled where supported by the module; call this function + * to disable 32 kHz sleep. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return zero on success or negative error code on + * failure. + */ +int32_t uCellPwrPrivateDisableUartSleep(uCellPrivateInstance_t *pInstance); + +/** Enable UART, AKA 32 kHz sleep. 32 kHz sleep is always enabled + * where supported - you only need to call this if you have + * previously called uCellPwrDisableUartSleep(). + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return zero on success or negative error code on + * failure. + */ +int32_t uCellPwrPrivateEnableUartSleep(uCellPrivateInstance_t *pInstance); + +/** Determine whether UART, AKA 32 kHz, sleep is enabled or not. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return true if UART sleep is enabled, else false. + */ +bool uCellPwrPrivateUartSleepIsEnabled(const uCellPrivateInstance_t *pInstance); + #ifdef __cplusplus } #endif diff --git a/cell/src/u_cell_stub_gnss.c b/cell/src/u_cell_stub_gnss.c new file mode 100644 index 000000000..fa1aedf30 --- /dev/null +++ b/cell/src/u_cell_stub_gnss.c @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/* Only #includes of u_* and the C standard library are allowed here, + * no platform stuff and no OS stuff. Anything required from + * the platform/OS must be brought in through u_port* to maintain + * portability. + */ + +/** @file + * @brief Stubs to allow the cellular API to be compiled without GNSS; + * if you call a GNSS API function from the source code here you must + * also include a weak stub for it which will return an error when + * GNSS is not included in the build. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. + +#include "u_compiler.h" // U_WEAK + +#include "u_gnss_shared.h" + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +U_WEAK void uGnssUpdateAtHandle(void *pAtOld, void *pAtNew) +{ + (void) pAtOld; + (void) pAtNew; +} + +// End of file diff --git a/cell/test/u_cell_ppp_test.c b/cell/test/u_cell_ppp_test.c new file mode 100644 index 000000000..efb72a5a3 --- /dev/null +++ b/cell/test/u_cell_ppp_test.c @@ -0,0 +1,505 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/* Only #includes of u_* and the C standard library are allowed here, + * no platform stuff and no OS stuff. Anything required from the + * platform/OS must be brought in through u_port* to maintain + * portability. + */ + +/** @file + * @brief Tests for the cellular PPP API: these should pass on all + * platforms where CMUX is also supported. They are only compiled + * if U_CFG_TEST_CELL_MODULE_TYPE is defined and U_CFG_TEST_DISABLE_MUX + * is NOT defined. + */ + +#if defined(U_CFG_TEST_CELL_MODULE_TYPE) && !defined(U_CFG_TEST_DISABLE_MUX) + +# ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +# endif + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // memset() + +#include "u_cfg_sw.h" +#include "u_cfg_os_platform_specific.h" +#include "u_cfg_app_platform_specific.h" +#include "u_cfg_test_platform_specific.h" + +#include "u_error_common.h" + +#include "u_port.h" +#include "u_port_os.h" // Required by u_cell_private.h +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE +# include "u_port_i2c.h" +# include "u_port_spi.h" +#endif +#include "u_port_heap.h" +#include "u_port_event_queue.h" +#include "u_port_debug.h" + +#include "u_test_util_resource_check.h" + +#include "u_at_client.h" + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE +# include "u_network.h" +# include "u_network_test_shared_cfg.h" +# include "u_location.h" +#endif + +#include "u_cell_module_type.h" +#include "u_cell.h" +#include "u_cell_file.h" +#include "u_cell_net.h" // Required by u_cell_private.h +#include "u_cell_private.h" // So that we can get at some innards +#include "u_cell_info.h" // For uCellInfoGetModelStr() +#if U_CFG_APP_PIN_CELL_PWR_ON < 0 +# include "u_cell_pwr.h" +#endif +#ifdef U_CELL_TEST_MUX_ALWAYS +# include "u_cell_mux.h" +#endif + +#include "u_cell_ppp_shared.h" + +#include "u_cell_test_cfg.h" +#include "u_cell_test_private.h" + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE +# include "u_gnss_module_type.h" +# include "u_gnss_type.h" +# include "u_gnss.h" // uGnssSetUbxMessagePrint() +# include "u_gnss_pos.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_CELL_PPP_TEST: " + +/** Print a whole line, with terminator, prefixed for this test file. + */ +#define U_TEST_PRINT_LINE(format, ...) uPortLog(U_TEST_PREFIX format "\n", ##__VA_ARGS__) + +#ifndef U_CELL_PPP_TEST_TIMEOUT_SECONDS +/** How long to wait for uCellPppOpen() to connect. + */ +# define U_CELL_PPP_TEST_TIMEOUT_SECONDS 60 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/** Used for keepGoingCallback() timeout. + */ +static int32_t gStopTimeMs; + +/** Handle. + */ +static uCellTestPrivate_t gHandles = U_CELL_TEST_PRIVATE_DEFAULTS; + +/** A small buffer to check that static buffers don't blow things + * up. + */ +static char gBuffer[16]; + +/* ---------------------------------------------------------------- +* STATIC FUNCTIONS +* -------------------------------------------------------------- */ + +// Callback function for the cellular connection process. +static bool keepGoingCallback(uDeviceHandle_t unused) +{ + bool keepGoing = true; + + (void) unused; + + if (uPortGetTickTimeMs() > gStopTimeMs) { + keepGoing = false; + } + + return keepGoing; +} + +// Callback for received data; doesn't do much. +static void receiveDataCallback(uDeviceHandle_t cellHandle, + const char *pData, + size_t dataSize, + void *pCallbackParam) +{ + (void) cellHandle; + (void) pData; + (void) dataSize; + (void) pCallbackParam; +} + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +// Network-API level bring up, used when addressing the GNSS chip inside a cellular module +static uNetworkTestList_t *pStdPreamble() +{ + uNetworkTestList_t *pList; + + // Add the device for each network configuration + // if not already added + pList = pUNetworkTestListAlloc(uNetworkTestIsDeviceCell); + if (pList == NULL) { + U_TEST_PRINT_LINE("*** WARNING *** nothing to do."); + } + // Open the devices that are not already open + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle == NULL) { + U_TEST_PRINT_LINE("adding device %s for network %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType], + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uDeviceOpen(pTmp->pDeviceCfg, pTmp->pDevHandle) == 0); + } + } + + // Bring up each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("bringing up %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceUp(*pTmp->pDevHandle, + pTmp->networkType, + pTmp->pNetworkCfg) == 0); + } + + return pList; +} + +// Convert a lat/long into a whole number and a bit-after-the-decimal-point +// that can be printed by a version of printf() that does not support +// floating point operations, returning the prefix (either "+" or "-"). +// The result should be printed with printf() format specifiers +// %c%d.%07d, e.g. something like: +// +// int32_t whole; +// int32_t fraction; +// +// printf("%c%d.%07d/%c%d.%07d", latLongToBits(latitudeX1e7, &whole, &fraction), +// whole, fraction, +// latLongToBits(longitudeX1e7, &whole, &fraction), +// whole, fraction); +static char latLongToBits(int32_t thingX1e7, + int32_t *pWhole, + int32_t *pFraction) +{ + char prefix = '+'; + + // Deal with the sign + if (thingX1e7 < 0) { + thingX1e7 = -thingX1e7; + prefix = '-'; + } + *pWhole = thingX1e7 / 10000000; + *pFraction = thingX1e7 % 10000000; + + return prefix; +} + +// Print lat/long location as a clickable link. +static void printLocation(int32_t latitudeX1e7, int32_t longitudeX1e7) +{ + char prefixLat; + char prefixLong; + int32_t wholeLat; + int32_t wholeLong; + int32_t fractionLat; + int32_t fractionLong; + + prefixLat = latLongToBits(latitudeX1e7, &wholeLat, &fractionLat); + prefixLong = latLongToBits(longitudeX1e7, &wholeLong, &fractionLong); + uPortLog("I am here: https://maps.google.com/?q=%c%d.%07d,%c%d.%07d\n", + prefixLat, wholeLat, fractionLat, prefixLong, wholeLong, + fractionLong); +} + +#endif // #ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +/* ---------------------------------------------------------------- +* PUBLIC FUNCTIONS +* -------------------------------------------------------------- */ + +/** A very basic test of PPP operation indeed; most of the real + * testing is done in the platform tests. + */ +U_PORT_TEST_FUNCTION("[cellPpp]", "cellPppBasic") +{ + int32_t resourceCount; + uDeviceHandle_t cellHandle; + const uCellPrivateModule_t *pModule; + char buffer[64]; + int32_t x; + + // In case a previous test failed + uCellTestPrivateCleanup(&gHandles); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + // Do the standard preamble + U_PORT_TEST_ASSERT(uCellTestPrivatePreamble(U_CFG_TEST_CELL_MODULE_TYPE, + &gHandles, true) == 0); + cellHandle = gHandles.cellHandle; + + // Get the private module data as we need it for testing + pModule = pUCellPrivateGetModule(cellHandle); + U_PORT_TEST_ASSERT(pModule != NULL); + //lint -esym(613, pModule) Suppress possible use of NULL pointer + // for pModule from now on + + // Only run if PPP operation is supported + if (U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + U_TEST_PRINT_LINE("testing PPP, first with no connection."); + // First check before having connected: should return error + gStopTimeMs = uPortGetTickTimeMs() + (U_CELL_PPP_TEST_TIMEOUT_SECONDS * 1000); + x = uCellPppOpen(cellHandle, NULL, NULL, gBuffer, + sizeof(gBuffer), keepGoingCallback); + U_TEST_PRINT_LINE("uCellPppOpen() returned %d.", x); + U_PORT_TEST_ASSERT(x < 0); + x = uCellPppTransmit(cellHandle, "dummy", 5); + U_TEST_PRINT_LINE("uCellPppTransmit() returned %d.", x); + U_PORT_TEST_ASSERT(x < 0); + + U_TEST_PRINT_LINE("now with a connection."); + // Now connect + gStopTimeMs = uPortGetTickTimeMs() + + (U_CELL_TEST_CFG_CONNECT_TIMEOUT_SECONDS * 1000); + x = uCellNetConnect(cellHandle, NULL, +#ifdef U_CELL_TEST_CFG_APN + U_PORT_STRINGIFY_QUOTED(U_CELL_TEST_CFG_APN), +#else + NULL, +#endif +#ifdef U_CELL_TEST_CFG_USERNAME + U_PORT_STRINGIFY_QUOTED(U_CELL_TEST_CFG_USERNAME), +#else + NULL, +#endif +#ifdef U_CELL_TEST_CFG_PASSWORD + U_PORT_STRINGIFY_QUOTED(U_CELL_TEST_CFG_PASSWORD), +#else + NULL, +#endif + keepGoingCallback); + U_PORT_TEST_ASSERT (x == 0); + + gStopTimeMs = uPortGetTickTimeMs() + (U_CELL_PPP_TEST_TIMEOUT_SECONDS * 1000); + x = uCellPppOpen(cellHandle, NULL, NULL, gBuffer, + sizeof(gBuffer), keepGoingCallback); + U_TEST_PRINT_LINE("uCellPppOpen() returned %d.", x); + U_PORT_TEST_ASSERT(x == 0); + + x = uCellPppTransmit(cellHandle, "dummy", 5); + U_TEST_PRINT_LINE("uCellPppTransmit() returned %d.", x); + U_PORT_TEST_ASSERT(x == 5); + + // Check that we can still do normal AT things + memset(buffer, 0, sizeof(buffer)); + x = uCellInfoGetModelStr(cellHandle, buffer, sizeof(buffer)); + U_PORT_TEST_ASSERT((x > 0) && (x < sizeof(buffer) - 1) && + (x == strlen(buffer))); + + U_TEST_PRINT_LINE("closing PPP (there will be a delay)..."); + U_PORT_TEST_ASSERT(uCellPppClose(cellHandle, true) == 0); + + // Disconnect + U_PORT_TEST_ASSERT(uCellNetDisconnect(cellHandle, NULL) == 0); + + } else { + U_TEST_PRINT_LINE("PPP is not supported, not testing it."); + U_PORT_TEST_ASSERT(uCellPppOpen(cellHandle, receiveDataCallback, + NULL, NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + keepGoingCallback) < 0); + U_PORT_TEST_ASSERT(uCellPppTransmit(cellHandle, "dummy", 5) < 0); + U_PORT_TEST_ASSERT(uCellPppClose(cellHandle, false) < 0); + uCellPppFree(cellHandle); + } + + // Do the standard postamble, also powering the module down + // as otherwise SARA-R5 can get upset since CMUX was running + uCellTestPrivatePostamble(&gHandles, true); + + // Check for resource leaks + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); + resourceCount = uTestUtilGetDynamicResourceCount() - resourceCount; + U_TEST_PRINT_LINE("we have leaked %d resources(s).", resourceCount); + U_PORT_TEST_ASSERT(resourceCount <= 0); +} + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +/** Test that GNSS access can run at the same time as PPP. + */ +U_PORT_TEST_FUNCTION("[cellPpp]", "cellPppWithGnss") +{ + uNetworkTestList_t *pList; + const uCellPrivateModule_t *pModule; + uDeviceHandle_t cellHandle = NULL; + uDeviceHandle_t gnssHandle = NULL; + int32_t resourceCount; + int32_t x; + uLocation_t location; + + // In case a previous test failed + uCellTestPrivateCleanup(&gHandles); + uNetworkTestCleanUp(); + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial heap size + uPortDeinit(); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + U_PORT_TEST_ASSERT(uPortInit() == 0); + // Don't check these for success as not all platforms support I2C or SPI + uPortI2cInit(); + uPortSpiInit(); + U_PORT_TEST_ASSERT(uDeviceInit() == 0); + + // Do the preamble to get all the networks up + pList = pStdPreamble(); + + // Find the cellular device and the GNSS network in the list + for (uNetworkTestList_t *pTmp = pList; (pTmp != NULL) && (gnssHandle == NULL); pTmp = pTmp->pNext) { + if (pTmp->pDeviceCfg->deviceType == U_DEVICE_TYPE_CELL) { + cellHandle = *pTmp->pDevHandle; + if (pTmp->networkType == U_NETWORK_TYPE_GNSS) { + gnssHandle = *pTmp->pDevHandle; + U_TEST_PRINT_LINE("selected GNSS network via cellular device."); + } + } + } + + if (gnssHandle != NULL) { + U_PORT_TEST_ASSERT(cellHandle != NULL); + + // So that we can see what we're doing + uGnssSetUbxMessagePrint(gnssHandle, true); + + // Get the private module data as we need it for testing + pModule = pUCellPrivateGetModule(cellHandle); + U_PORT_TEST_ASSERT(pModule != NULL); + //lint -esym(613, pModule) Suppress possible use of NULL pointer + // for pModule from now on + + // Only run if PPP operation is supported + if (U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + U_TEST_PRINT_LINE("testing PPP and GNSS at the same time."); + x = uCellPppOpen(cellHandle, NULL, NULL, gBuffer, sizeof(gBuffer), NULL); + U_TEST_PRINT_LINE("uCellPppOpen() returned %d.", x); + U_PORT_TEST_ASSERT(x == 0); + + // Now get location + x = uLocationGet(gnssHandle, U_LOCATION_TYPE_GNSS, + NULL, NULL, &location, NULL); + U_TEST_PRINT_LINE("uLocationGet() returned %d.", x); + U_PORT_TEST_ASSERT(x == 0); + printLocation(location.latitudeX1e7, location.longitudeX1e7); + + x = uCellPppTransmit(cellHandle, "dummy", 5); + U_TEST_PRINT_LINE("uCellPppTransmit() returned %d.", x); + U_PORT_TEST_ASSERT(x == 5); + + U_TEST_PRINT_LINE("closing PPP (there will be a delay)..."); + U_PORT_TEST_ASSERT(uCellPppClose(cellHandle, true) == 0); + } + + // Call PPP free this time + uCellPppFree(cellHandle); + + } else { + U_TEST_PRINT_LINE("*** WARNING *** not testing GNSS at the same time" + " as PPP since no GNSS device is attached via cellular."); + } + + // Close the devices once more and free the list + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); + U_TEST_PRINT_LINE("closing and powering off device %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); + x = uDeviceClose(*pTmp->pDevHandle, true); + if (x != 0) { + // Device has not responded to power off request, just + // release resources + x = uDeviceClose(*pTmp->pDevHandle, false); + } + U_PORT_TEST_ASSERT(x == 0); + *pTmp->pDevHandle = NULL; + } + } + uNetworkTestListFree(); + + uPortEventQueueCleanUp(); + + uDeviceDeinit(); + uPortSpiDeinit(); + uPortI2cDeinit(); + uPortDeinit(); + + // Check for resource leaks + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); + resourceCount = uTestUtilGetDynamicResourceCount() - resourceCount; + U_TEST_PRINT_LINE("we have leaked %d resources(s).", resourceCount); + U_PORT_TEST_ASSERT(resourceCount <= 0); +} + +#endif // #ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +/** Clean-up to be run at the end of this round of tests, just + * in case there were test failures which would have resulted + * in the deinitialisation being skipped. + */ +U_PORT_TEST_FUNCTION("[cellPpp]", "cellPppCleanUp") +{ + uPortEventQueueCleanUp(); + uCellTestPrivateCleanup(&gHandles); +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE + // The network test configuration is shared between + // the network, sockets, security and location tests + // so must reset the handles here in case the + // tests of one of the other APIs are coming next. + uNetworkTestCleanUp(); +#endif + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +#endif // #if defined(U_CFG_TEST_CELL_MODULE_TYPE) && !defined(U_CFG_TEST_DISABLE_MUX) + +// End of file diff --git a/cell/test/u_cell_test_private.c b/cell/test/u_cell_test_private.c index 3a2ecd23d..429cbe517 100644 --- a/cell/test/u_cell_test_private.c +++ b/cell/test/u_cell_test_private.c @@ -53,9 +53,7 @@ #include "u_cell_pwr.h" #include "u_cell_cfg.h" #include "u_cell_info.h" -#ifdef U_CELL_TEST_MUX_ALWAYS -# include "u_cell_mux.h" -#endif +#include "u_cell_mux.h" #include "u_cell_test_cfg.h" #include "u_cell_test_private.h" @@ -264,6 +262,12 @@ int32_t uCellTestPrivatePreamble(uCellModuleType_t moduleType, // Power up U_TEST_PRINT_LINE("powering on..."); errorCode = uCellPwrOn(cellHandle, U_CELL_TEST_CFG_SIM_PIN, NULL); + if (errorCode < 0) { + // If powering-on fails, try sending the CMUX abort sequence in + // case the module is stuck in CMUX mode, and powering-on again + uCellMuxModuleAbort(cellHandle); + errorCode = uCellPwrOn(cellHandle, U_CELL_TEST_CFG_SIM_PIN, NULL); + } if (errorCode == 0) { // Note: if this is a SARA-R422 module, which supports only // 1.8V SIMs, the SIM cards we happen to use in the ubxlib test farm diff --git a/common/at_client/api/u_at_client.h b/common/at_client/api/u_at_client.h index 8bba54236..30d24c504 100644 --- a/common/at_client/api/u_at_client.h +++ b/common/at_client/api/u_at_client.h @@ -362,7 +362,7 @@ extern "C" { * or STM32F4 and more again in the version pre-built for * Arduino. */ -# define U_AT_CLIENT_URC_TASK_STACK_SIZE_BYTES 2304 +# define U_AT_CLIENT_URC_TASK_STACK_SIZE_BYTES 2816 #endif #ifndef U_AT_CLIENT_URC_TASK_PRIORITY diff --git a/common/network/test/u_network_test_shared_cfg.c b/common/network/test/u_network_test_shared_cfg.c index ba9e3fe46..325eab78e 100644 --- a/common/network/test/u_network_test_shared_cfg.c +++ b/common/network/test/u_network_test_shared_cfg.c @@ -675,4 +675,14 @@ bool uNetworkTestHasStatusCallback(uDeviceType_t deviceType, (networkType == U_NETWORK_TYPE_CELL); } +// Return true if the configuration supports a PPP connection. +bool uNetworkTestHasPpp(uDeviceType_t deviceType, + uNetworkType_t networkType, + int32_t moduleType) +{ + (void) deviceType; + (void) moduleType; + return (networkType == U_NETWORK_TYPE_CELL); +} + // End of file diff --git a/common/network/test/u_network_test_shared_cfg.h b/common/network/test/u_network_test_shared_cfg.h index b9735ca05..e63a60b3e 100644 --- a/common/network/test/u_network_test_shared_cfg.h +++ b/common/network/test/u_network_test_shared_cfg.h @@ -266,6 +266,18 @@ bool uNetworkTestHasStatusCallback(uDeviceType_t deviceType, uNetworkType_t networkType, int32_t moduleType); +/** Return true if the combination supports a PPP connection. + * + * @param deviceType the device type. + * @param networkType the network type. + * @param moduleType the module type. + * @return true if a PPP connection is supported, + * else false. + */ +bool uNetworkTestHasPpp(uDeviceType_t deviceType, + uNetworkType_t networkType, + int32_t moduleType); + #endif // _U_NETWORK_TEST_CFG_H_ // End of file diff --git a/common/sock/test/u_sock_test.c b/common/sock/test/u_sock_test.c index b937f8e78..5ac399be6 100644 --- a/common/sock/test/u_sock_test.c +++ b/common/sock/test/u_sock_test.c @@ -129,20 +129,6 @@ # define U_SOCK_TEST_MAX_UDP_PACKET_SIZE 500 #endif -#ifndef U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE -/** The maximum TCP read/write size to use during testing. - */ -# define U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE 1024 -#endif - -#ifndef U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE -/** Sending just one byte doesn't always cause all - * modules to actually send the data in a reasonable - * time so set a sensible minimum here for testing. - */ -# define U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE 128 -#endif - #ifndef U_SOCK_TEST_NON_BLOCKING_TIME_MS /** Expected return time for non-blocking operation *in ms during testing. diff --git a/common/sock/test/u_sock_test_shared_cfg.h b/common/sock/test/u_sock_test_shared_cfg.h index 503b0263f..bcfd060d9 100644 --- a/common/sock/test/u_sock_test_shared_cfg.h +++ b/common/sock/test/u_sock_test_shared_cfg.h @@ -119,6 +119,20 @@ # define U_SOCK_TEST_UDP_RETRIES 10 #endif +#ifndef U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE +/** The maximum TCP read/write size to use during testing. + */ +# define U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE 1024 +#endif + +#ifndef U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE +/** Sending just one byte doesn't always cause all + * modules to actually send the data in a reasonable + * time so set a sensible minimum here for testing. + */ +# define U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE 128 +#endif + #ifndef U_SOCK_TEST_TCP_CLOSE_SECONDS /** Time to wait for a TCP socket to close, necessary * in the case where the underlying implementation diff --git a/example/sockets/README.md b/example/sockets/README.md index dc7ab26d7..fcfc8d968 100644 --- a/example/sockets/README.md +++ b/example/sockets/README.md @@ -37,3 +37,16 @@ Obviously you will need a SIM in your board, an antenna connected and you may ne `U_CFG_APP_PIN_SHORT_RANGE_xxx`: the default values for the MCU pins connecting your short range module to your MCU are \#defined in the file [port/platform](/port/platform)`//mcu//cfg/cfg_app_platform_specific.h`. You should check if these are correct for your board and, if not, override the values of the \#defines (where -1 means "not connected"). `U_CFG_APP_SHORT_RANGE_UART`: this sets the internal HW UART block that your chosen MCU will use to talk to the Wi-Fi module. The default is usually acceptable but if you wish to change it then consult the file [port/platform](/port/platform)`/mcu//cfg/cfg_hw_platform_specific.h` for other options. + +# Using PPP +On the following platforms: + +- [ESP-IDF](/port/platform/esp-idf) +- [Zephyr](/port/platform/zephyr) + +...and with following \[cellular\] modules: + +- SARA-R5 +- SARA-R422 + +...it is possible to make a PPP connection between the module and the bottom of the platform's own IP stack, allowing the native applications of that platform (e.g. MQTT) to connect through the module. Set-up for `ubxlib` is as above, plus you must define `U_CFG_PPP_ENABLE` when building `ubxlib`. Unfortunately there will _always_ be additional platform-specific setup: for this, refer to the `README.md` of the relevant platform directory or also look at the top of the example `.c` file. \ No newline at end of file diff --git a/example/sockets/main_ppp_espidf.c b/example/sockets/main_ppp_espidf.c new file mode 100644 index 000000000..9b14ce156 --- /dev/null +++ b/example/sockets/main_ppp_espidf.c @@ -0,0 +1,312 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/** @brief This example demonstrates how to bring up a network + * connection and then perform sockets operations using the native + * IP stack of the ESP-IDF platform. + * + * For this example to run you must define U_CFG_PPP_ENABLE when + * building ubxlib and you must switch on the following in your + * sdkconfig file: + * + * CONFIG_LWIP_PPP_SUPPORT + * CONFIG_ESP_NETIF_TCPIP_LWIP + * CONFIG_LWIP_PPP_PAP_SUPPORT + * + * If your network operator requires a user name and password + * along with the APN **AND** requires CHAP authentication, then + * you must also switch on CONFIG_LWIP_PPP_CHAP_SUPPORT. + * + * If you are minimising the components built into your main + * application then you may need to add the ESP-IDF component + * "esp_netif" to your component list. + * + * The choice of [cellular] module is made at build time, see the + * README.md for instructions. + */ + +// This example is only for the ESP-IDF platform +#ifdef __XTENSA__ + +// Bring in all of the ubxlib public header files +#include "ubxlib.h" + +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// The BSD sockets interface of ESP-IDF +#include "unistd.h" +#include "sys/socket.h" +#include "netdb.h" +#include "arpa/inet.h" +#include "errno.h" + +#ifndef U_PORT_TEST_FUNCTION +// The ESP-IDF network interface that allows us to connect PPP +# include "esp_event.h" +# include "esp_netif.h" +#endif + +// Bring in the application settings +#include "u_cfg_app_platform_specific.h" + +#ifndef U_CFG_DISABLE_TEST_AUTOMATION +// This purely for internal u-blox testing +# include "u_cfg_test_platform_specific.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +// Echo server URL and port number +#define MY_SERVER_NAME "ubxlib.com" +#define MY_SERVER_PORT 5055 + +// For u-blox internal testing only +#ifdef U_PORT_TEST_ASSERT +# define EXAMPLE_FINAL_STATE(x) U_PORT_TEST_ASSERT(x); +#else +# define EXAMPLE_FINAL_STATE(x) +#endif + +#ifndef U_PORT_TEST_FUNCTION +# error if you are not using the unit test framework to run this code you must ensure that the platform clocks/RTOS are set up and either define U_PORT_TEST_FUNCTION yourself or replace it as necessary. +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#ifdef U_CFG_TEST_CELL_MODULE_TYPE +// Set U_CFG_TEST_CELL_MODULE_TYPE to your module type, +// chosen from the values in cell/api/u_cell_module_type.h +// +// Note that the pin numbers are those of the MCU: if you +// are using an MCU inside a u-blox module the IO pin numbering +// for the module is likely different that from the MCU: check +// the data sheet for the module to determine the mapping. + +// DEVICE i.e. module/chip configuration: in this case a cellular +// module connected via UART +static const uDeviceCfg_t gDeviceCfg = { + .deviceType = U_DEVICE_TYPE_CELL, + .deviceCfg = { + .cfgCell = { + .moduleType = U_CFG_TEST_CELL_MODULE_TYPE, + .pSimPinCode = NULL, /* SIM pin */ + .pinEnablePower = U_CFG_APP_PIN_CELL_ENABLE_POWER, + .pinPwrOn = U_CFG_APP_PIN_CELL_PWR_ON, + .pinVInt = U_CFG_APP_PIN_CELL_VINT, + .pinDtrPowerSaving = U_CFG_APP_PIN_CELL_DTR + }, + }, + .transportType = U_DEVICE_TRANSPORT_TYPE_UART, + .transportCfg = { + .cfgUart = { + .uart = U_CFG_APP_CELL_UART, + .baudRate = U_CELL_UART_BAUD_RATE, + .pinTxd = U_CFG_APP_PIN_CELL_TXD, + .pinRxd = U_CFG_APP_PIN_CELL_RXD, + .pinCts = U_CFG_APP_PIN_CELL_CTS, + .pinRts = U_CFG_APP_PIN_CELL_RTS, +#ifdef U_CFG_APP_UART_PREFIX + .pPrefix = U_PORT_STRINGIFY_QUOTED(U_CFG_APP_UART_PREFIX) // Relevant for Linux only +#else + .pPrefix = NULL +#endif + }, + }, +}; +// NETWORK configuration +static const uNetworkCfgCell_t gNetworkCfg = { + .type = U_NETWORK_TYPE_CELL, + .pApn = NULL, /* APN: NULL to accept default. If using a Thingstream SIM enter "tsiot" here */ + .timeoutSeconds = 240 /* Connection timeout in seconds */ + // There are four additional fields here which we do NOT set, + // we allow the compiler to set them to 0 and all will be fine. + // The fields are: + // + // - "pKeepGoingCallback": you may set this field to a function + // of the form "bool keepGoingCallback(uDeviceHandle_t devHandle)", + // e.g.: + // + // .pKeepGoingCallback = keepGoingCallback; + // + // ...and your function will be called periodically during an + // abortable network operation such as connect/disconnect; + // if it returns true the operation will continue else it + // will be aborted, allowing you immediate control. If this + // field is set, timeoutSeconds will be ignored. + // + // - "pUsername" and "pPassword": if you are required to set a + // user name and password to go with the APN value that you + // were given by your service provider, set them here. + // + // - "authenticationMode": if you MUST give a user name and + // password then you must populate this field with the + // authentication mode that should be used, see + // #uCellNetAuthenticationMode_t in u_cell_net.h, and noting + // that automatic authentication mode will NOT work with PPP. + // You ONLY NEED TO WORRY ABOUT THIS if you were given a user name + // name and password with the APN (which is thankfully not usual). + // + // - "pMccMnc": ONLY required if you wish to connect to a specific + // MCC/MNC rather than to the best available network; should point + // to the null-terminated string giving the MCC and MNC of the PLMN + // to use (for example "23410").}; +}; +#else +// No module available - set some dummy values to make test system happy +static const uDeviceCfg_t gDeviceCfg = {.deviceType = U_DEVICE_TYPE_NONE}; +static const uNetworkCfgCell_t gNetworkCfg = {.type = U_NETWORK_TYPE_NONE}; +#endif + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: THE EXAMPLE + * -------------------------------------------------------------- */ + +// The entry point, main(): before this is called the system +// clocks must have been started and the RTOS must be running; +// we are in task space. +U_PORT_TEST_FUNCTION("[example]", "examplePppEspIdfSockets") +{ + uDeviceHandle_t devHandle = NULL; + struct hostent *pHostEnt; + struct sockaddr_in destinationAddress; + int32_t sock; + const char message[] = "The quick brown espidf-fox jumps over the lazy dog."; + size_t txSize = sizeof(message); + char buffer[128]; + size_t rxSize = 0; + int32_t returnCode; + +#ifndef U_PORT_TEST_FUNCTION + // ESP-IDF requires the application to initialise + // its network interface and default event loop + // This is under the switch #ifndef U_PORT_TEST_FUNCTION + // only because, when we run this for internal + // testing, the initialisation is done elsewhere + esp_netif_init(); + esp_event_loop_create_default(); +#endif + + // Initialise the APIs we will need + uPortInit(); + uDeviceInit(); + + // Open the device + returnCode = uDeviceOpen(&gDeviceCfg, &devHandle); + uPortLog("Opened device with return code %d.\n", returnCode); + + if (returnCode == 0) { + // Bring up the network interface + uPortLog("Bringing up the network...\n"); + if (uNetworkInterfaceUp(devHandle, U_NETWORK_TYPE_CELL, + &gNetworkCfg) == 0) { + + // ESP-IDF's IP stack is now connected to the + // internet via the cellular module + + // Look up the IP address of the echo server + // using the ESP-IDF platform's gethostbyname() + errno = 0; + pHostEnt = gethostbyname(MY_SERVER_NAME); + if (pHostEnt != NULL) { + uPortLog("Found %s at %s.\n", MY_SERVER_NAME, + inet_ntoa(*(struct in_addr *) pHostEnt->h_addr)); + + // Call the native BSD sockets APIs of the + // ESP-IDF platform to send data + + // You could equally use any of the ESP-IDF + // native protocol entities (MQTT, HTTP, etc.) + + destinationAddress.sin_addr = *(struct in_addr *) pHostEnt->h_addr; + destinationAddress.sin_family = pHostEnt->h_addrtype; + destinationAddress.sin_port = htons(MY_SERVER_PORT); + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (sock >= 0) { + if (connect(sock, (struct sockaddr *) &destinationAddress, sizeof(destinationAddress)) == 0) { + if (send(sock, message, txSize, 0) == txSize) { + uPortLog("Sent %d byte(s) to echo server.\n", txSize); + rxSize = recv(sock, buffer, sizeof(buffer), 0); + if (rxSize > 0) { + uPortLog("\nReceived echo back (%d byte(s)): %s\n", rxSize, buffer); + } else { + uPortLog("\nNo reply received!\n"); + } + } else { + uPortLog("Unable to send to server (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to connect to server (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to create socket (errno %d)!\n", errno); + } + + // Close the socket + uPortLog("Closing socket...\n"); + shutdown(sock, 0); + close(sock); + } else { + uPortLog("Unable to find %s (errno %d)!\n", MY_SERVER_NAME, errno); + } + + // When finished with the network layer + uPortLog("Taking down network...\n"); + uNetworkInterfaceDown(devHandle, U_NETWORK_TYPE_CELL); + } else { + uPortLog("Unable to bring up the network!\n"); + } + + // Close the device + // Note: we don't power the device down here in order + // to speed up testing; you may prefer to power it off + // by setting the second parameter to true. + uDeviceClose(devHandle, false); + + } else { + uPortLog("Unable to bring up the device!\n"); + } + + // Tidy up + uDeviceDeinit(); + uPortDeinit(); + + uPortLog("Done.\n"); + +#ifdef U_CFG_TEST_CELL_MODULE_TYPE + // For u-blox internal testing only + EXAMPLE_FINAL_STATE(rxSize == sizeof(message)); +#endif +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) +#endif // #ifdef __XTENSA__ + +// End of file diff --git a/example/sockets/main_ppp_zephyr.c b/example/sockets/main_ppp_zephyr.c new file mode 100644 index 000000000..59a06251c --- /dev/null +++ b/example/sockets/main_ppp_zephyr.c @@ -0,0 +1,343 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/** @brief This example demonstrates how to bring up a network + * connection and then perform sockets operations using the native + * IP stack of the Zephyr platform. + * + * For this example to run you must define U_CFG_PPP_ENABLE when + * building ubxlib and you must set the following in your prj.conf file: + * + * CONFIG_NETWORKING=y + * CONFIG_NET_DRIVERS=y + * CONFIG_NET_IPV6=n + * CONFIG_NET_IPV4=y + * CONFIG_PPP_NET_IF_NO_AUTO_START=y + * CONFIG_NET_PPP=y + * CONFIG_NET_PPP_ASYNC_UART=y + * CONFIG_NET_L2_PPP=y + * CONFIG_NET_L2_PPP_PAP=y + * CONFIG_NET_L2_PPP_TIMEOUT=10000 + * CONFIG_NET_PPP_UART_BUF_LEN=512 (suggested buffer size) + * CONFIG_NET_PPP_ASYNC_UART_TX_BUF_LEN=512 (suggested buffer size) + * + * Depending on how much data you expect to receive, you may want to increase + * CONFIG_NET_PPP_RINGBUF_SIZE from the default of 256 (during testing we + * use 1024). + * + * For this example to work you must also enable sockets and TCP with: + * + * CONFIG_NET_TCP=y + * CONFIG_NET_TCP_MAX_SEND_WINDOW_SIZE=256 (since the PPP link is relatively slow, keep the window size small) + * CONFIG_NET_TCP_MAX_RECV_WINDOW_SIZE=256 + * CONFIG_NET_SOCKETS=y + * + * In addition to all of the above, you must add the following to + * your `.dts` or `.overlay` file: + * + * + * / { + * chosen { + * zephyr,ppp-uart = &uart99; + * }; + * + * uart99: uart-ppp@8000 { + * compatible = "u-blox,uart-ppp"; + * reg = <0x8000 0x100>; + * status = "okay"; + * }; + *}; + * + * Note that if your network operator requires a user name and password + * along with the APN then you must edit the hard-coded username/ + * password that is hard-coded in Zephyr ppp.c; Zephyr does not + * offer a way to set this at run-time. Also note that Zephyr does + * not support CHAP authentication. + * + * The choice of [cellular] module is made at build time, see the + * README.md for instructions. + */ + +// This example is only for the Zephyr platform +#ifdef __ZEPHYR__ + +// Bring in all of the ubxlib public header files +#include "ubxlib.h" + +#include + +#if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(3,1,0) +#include +#include +#else +#include +#include +#endif + +#if defined(CONFIG_NET_PPP) && defined(CONFIG_NET_TCP) && \ + defined(CONFIG_NET_SOCKETS) && defined(U_CFG_PPP_ENABLE) + +#include "errno.h" + +// Bring in the application settings +#include "u_cfg_app_platform_specific.h" + +#ifndef U_CFG_DISABLE_TEST_AUTOMATION +// This purely for internal u-blox testing +# include "u_cfg_test_platform_specific.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +// Echo server IP address and port number; see the body of the code +// for why we use the IP address rather than the domain name +#define MY_SERVER_IP_ADDRESS "18.133.144.142" +#define MY_SERVER_PORT 5055 + +// For u-blox internal testing only +#ifdef U_PORT_TEST_ASSERT +# define EXAMPLE_FINAL_STATE(x) U_PORT_TEST_ASSERT(x); +#else +# define EXAMPLE_FINAL_STATE(x) +#endif + +#ifndef U_PORT_TEST_FUNCTION +# error if you are not using the unit test framework to run this code you must ensure that the platform clocks/RTOS are set up and either define U_PORT_TEST_FUNCTION yourself or replace it as necessary. +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#ifdef U_CFG_TEST_CELL_MODULE_TYPE +// Set U_CFG_TEST_CELL_MODULE_TYPE to your module type, +// chosen from the values in cell/api/u_cell_module_type.h +// +// Note that the pin numbers are those of the MCU: if you +// are using an MCU inside a u-blox module the IO pin numbering +// for the module is likely different that from the MCU: check +// the data sheet for the module to determine the mapping. + +// DEVICE i.e. module/chip configuration: in this case a cellular +// module connected via UART +static const uDeviceCfg_t gDeviceCfg = { + .deviceType = U_DEVICE_TYPE_CELL, + .deviceCfg = { + .cfgCell = { + .moduleType = U_CFG_TEST_CELL_MODULE_TYPE, + .pSimPinCode = NULL, /* SIM pin */ + .pinEnablePower = U_CFG_APP_PIN_CELL_ENABLE_POWER, + .pinPwrOn = U_CFG_APP_PIN_CELL_PWR_ON, + .pinVInt = U_CFG_APP_PIN_CELL_VINT, + .pinDtrPowerSaving = U_CFG_APP_PIN_CELL_DTR + }, + }, + .transportType = U_DEVICE_TRANSPORT_TYPE_UART, + .transportCfg = { + .cfgUart = { + .uart = U_CFG_APP_CELL_UART, + .baudRate = U_CELL_UART_BAUD_RATE, + .pinTxd = U_CFG_APP_PIN_CELL_TXD, + .pinRxd = U_CFG_APP_PIN_CELL_RXD, + .pinCts = U_CFG_APP_PIN_CELL_CTS, + .pinRts = U_CFG_APP_PIN_CELL_RTS, +#ifdef U_CFG_APP_UART_PREFIX + .pPrefix = U_PORT_STRINGIFY_QUOTED(U_CFG_APP_UART_PREFIX) // Relevant for Linux only +#else + .pPrefix = NULL +#endif + }, + }, +}; +// NETWORK configuration +static const uNetworkCfgCell_t gNetworkCfg = { + .type = U_NETWORK_TYPE_CELL, + .pApn = NULL, /* APN: NULL to accept default. If using a Thingstream SIM enter "tsiot" here */ + .timeoutSeconds = 240 /* Connection timeout in seconds */ + // There are four additional fields here which we do NOT set, + // we allow the compiler to set them to 0 and all will be fine. + // The fields are: + // + // - "pKeepGoingCallback": you may set this field to a function + // of the form "bool keepGoingCallback(uDeviceHandle_t devHandle)", + // e.g.: + // + // .pKeepGoingCallback = keepGoingCallback; + // + // ...and your function will be called periodically during an + // abortable network operation such as connect/disconnect; + // if it returns true the operation will continue else it + // will be aborted, allowing you immediate control. If this + // field is set, timeoutSeconds will be ignored. + // + // - "pUsername" and "pPassword": if you are required to set a + // user name and password to go with the APN value that you + // were given by your service provider, set them here. + // + // - "authenticationMode": if you MUST give a user name and + // password then you must populate this field with the + // authentication mode that should be used, see + // #uCellNetAuthenticationMode_t in u_cell_net.h, and noting + // that automatic authentication mode will NOT work with PPP. + // You ONLY NEED TO WORRY ABOUT THIS if you were given a user name + // name and password with the APN (which is thankfully not usual). + // + // - "pMccMnc": ONLY required if you wish to connect to a specific + // MCC/MNC rather than to the best available network; should point + // to the null-terminated string giving the MCC and MNC of the PLMN + // to use (for example "23410").}; +}; +#else +// No module available - set some dummy values to make test system happy +static const uDeviceCfg_t gDeviceCfg = {.deviceType = U_DEVICE_TYPE_NONE}; +static const uNetworkCfgCell_t gNetworkCfg = {.type = U_NETWORK_TYPE_NONE}; +#endif + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: THE EXAMPLE + * -------------------------------------------------------------- */ + +// The entry point, main(): before this is called the system +// clocks must have been started and the RTOS must be running; +// we are in task space. +U_PORT_TEST_FUNCTION("[example]", "examplePppZephyrSockets") +{ + uDeviceHandle_t devHandle = NULL; + struct sockaddr_in destinationAddress; + int32_t sock; + const char message[] = "The quick brown zephyr-fox jumps over the lazy dog."; + size_t txSize = sizeof(message); + char buffer[128]; + size_t rxSize = 0; + int32_t returnCode; + + // Initialise the APIs we will need + uPortInit(); + uDeviceInit(); + + // Open the device + returnCode = uDeviceOpen(&gDeviceCfg, &devHandle); + uPortLog("Opened device with return code %d.\n", returnCode); + + if (returnCode == 0) { + // Bring up the network interface + uPortLog("Bringing up the network...\n"); + if (uNetworkInterfaceUp(devHandle, U_NETWORK_TYPE_CELL, + &gNetworkCfg) == 0) { + + // Zephyr's IP stack is now connected to the + // internet via the cellular module + + // Call the native sockets APIs of the + // Zephyr platform to send data; you could + // equally use any of the Zephyr native protocol + // entities (MQTT, HTTP, etc.) + + // Note: normally you would do something like: + // + // struct addrinfo hints = {0}; + // struct addrinfo *pAddrs = NULL; + // hints.ai_family = AF_INET; + // hints.ai_socktype = SOCK_STREAM; + // if (zsock_getaddrinfo(MY_SERVER_NAME, NULL, &hints, &pAddrs) == 0) { + // ... + // + // ...here to get the IP address of MY_SERVER_NAME (e.g. + // "ubxlib.com") into the pointer pAddrs, calling + // zsock_freeaddrinfo(pAddrs) to free memory again at the end. + // + // However, zsock_getaddrinfo() does not work with the Zephyr + // minimal lib C (it requires an implementation of calloc(), + // which we don't bring in), hence in this example we use the + // known IP address of the server instead. + zsock_inet_pton(AF_INET, MY_SERVER_IP_ADDRESS, &destinationAddress.sin_addr); + destinationAddress.sin_family = AF_INET; + destinationAddress.sin_port = htons(MY_SERVER_PORT); + errno = 0; + sock = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (sock >= 0) { + if (zsock_connect(sock, (struct sockaddr *) &destinationAddress, sizeof(destinationAddress)) == 0) { + if (zsock_send(sock, message, txSize, 0) == txSize) { + uPortLog("Sent %d byte(s) to echo server.\n", txSize); + rxSize = zsock_recv(sock, buffer, sizeof(buffer), 0); + if (rxSize > 0) { + uPortLog("\nReceived echo back (%d byte(s)): %s\n", rxSize, buffer); + } else { + uPortLog("\nNo reply received!\n"); + } + } else { + uPortLog("Unable to send to server (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to connect to server (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to create socket (errno %d)!\n", errno); + } + + // Close the socket + uPortLog("Closing socket...\n"); + zsock_shutdown(sock, 0); + zsock_close(sock); + + // When finished with the network layer + uPortLog("Taking down network...\n"); + uNetworkInterfaceDown(devHandle, U_NETWORK_TYPE_CELL); + } else { + uPortLog("Unable to bring up the network!\n"); + } + + // Close the device + // Note: usually in these examples we don't power the + // device down, in order to speed up testing, but we + // do in this case as there is a bug in some versions + // of SARA-R5 which causes a CMUX'ed AT interface (used + // when PPP is running) to become unresponsive when it + // is taken down and brought back up again without + // power-cycling the module in-between. + uDeviceClose(devHandle, true); + + } else { + uPortLog("Unable to bring up the device!\n"); + } + + // Tidy up + uDeviceDeinit(); + uPortDeinit(); + + uPortLog("Done.\n"); + +#ifdef U_CFG_TEST_CELL_MODULE_TYPE + // For u-blox internal testing only + EXAMPLE_FINAL_STATE(rxSize == sizeof(message)); +#endif +} + +#endif // #if defined(CONFIG_NET_PPP) && defined(CONFIG_NET_TCP) && +// defined(CONFIG_NET_SOCKETS) && defined(U_CFG_PPP_ENABLE) +#endif // #ifdef __ZEPHYR__ + +// End of file diff --git a/gnss/api/u_gnss.h b/gnss/api/u_gnss.h index e64edaca2..042d56610 100644 --- a/gnss/api/u_gnss.h +++ b/gnss/api/u_gnss.h @@ -180,6 +180,16 @@ void uGnssRemove(uDeviceHandle_t gnssHandle); /** Get the type and handle of the transport used by the given * GNSS instance. * + * Note: where the transport is over AT (i.e. the case where AT+UGUBX + * messages are being used to talk to a GNSS chip that is inside or + * connected via a GNSS chip, e.g. if U_NETWORK_GNSS_CFG_CELL_USE_AT_ONLY + * is defined, or CMUX is not supported, not the normal case) it is + * possible for the AT handle to change underneath, so an AT handle + * returned by this function will be locked and therefore unusable. + * This will occur if a PPP session is opened to the cellular device. + * Should a PPP session be opened this function should be called again + * to obtain the correct AT handle. + * * @param gnssHandle the handle of the GNSS instance. * @param[out] pTransportType a place to put the transport type, * may be NULL. diff --git a/gnss/src/u_gnss.c b/gnss/src/u_gnss.c index 3315498c7..5ce24c949 100644 --- a/gnss/src/u_gnss.c +++ b/gnss/src/u_gnss.c @@ -197,6 +197,32 @@ static void deleteGnssInstance(uGnssPrivateInstance_t *pInstance) } } +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE SHARED WITHIN UBXLIB ONLY + * -------------------------------------------------------------- */ + +// Update an AT handle that any GNSS instance may be using. +void uGnssUpdateAtHandle(void *pAtOld, void *pAtNew) +{ + uGnssPrivateInstance_t *pInstance; + + if (gUGnssPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUGnssPrivateMutex); + + pInstance = gpUGnssPrivateInstanceList; + while (pInstance != NULL) { + if ((pInstance->transportType == U_GNSS_TRANSPORT_AT) && + (pInstance->transportHandle.pAt == pAtOld)) { + pInstance->transportHandle.pAt = pAtNew; + } + pInstance = pInstance->pNext; + } + + U_PORT_MUTEX_UNLOCK(gUGnssPrivateMutex); + } +} + /* ---------------------------------------------------------------- * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ diff --git a/gnss/src/u_gnss_shared.h b/gnss/src/u_gnss_shared.h new file mode 100644 index 000000000..eefc512d0 --- /dev/null +++ b/gnss/src/u_gnss_shared.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +#ifndef _U_GNSS_SHARED_H_ +#define _U_GNSS_SHARED_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +/** @file + * @brief This header file defines a few functions that are not public + * but are shared with the rest of ubxlib. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Update the AT handles of any GNSS devices that were using pAtOld + * to be pAtNew; useful if the AT handle is changed dynamically, + * for example when CMUX is invoked with a cellular module via which + * a GNSS device is connected. + * + * @param[in] pAtOld the old AT handle. + * @param[in] pAtNew the new AT handle. + */ +void uGnssUpdateAtHandle(void *pAtOld, void *pAtNew); + +#ifdef __cplusplus +} +#endif + +#endif // _U_GNSS_SHARED_H_ + +// End of file diff --git a/port/api/u_port_event_queue.h b/port/api/u_port_event_queue.h index ee9b547ee..a51322245 100644 --- a/port/api/u_port_event_queue.h +++ b/port/api/u_port_event_queue.h @@ -107,7 +107,7 @@ extern "C" { /** The maximum length of parameter block that can be sent on an * event queue. */ -# define U_PORT_EVENT_QUEUE_MAX_PARAM_LENGTH_BYTES 128 +# define U_PORT_EVENT_QUEUE_MAX_PARAM_LENGTH_BYTES 512 #endif /** The length of uEventQueueControlOrSize_t (see implementation). diff --git a/port/api/u_port_ppp.h b/port/api/u_port_ppp.h new file mode 100644 index 000000000..28876e5c7 --- /dev/null +++ b/port/api/u_port_ppp.h @@ -0,0 +1,439 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +#ifndef _U_PORT_PPP_H_ +#define _U_PORT_PPP_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +#include "u_sock.h" + +/** \addtogroup __port __Port + * @{ + */ + +/** @file + * @brief This header file defines functions that allow a PPP + * interface of ubxlib to be connected into the IP stack of + * a platform. + * + * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT + * + * It is HIGHLY LIKELY that there are many settings you need + * to get right in your platform configuration files for PPP + * to work: please see the README.md in the relevant platform + * directory for details. + * + * It is ALSO HIGHLY LIKELY that there are limitations as to + * what each platform actually supports; these limitations + * are documented in the same place. + * + * Please also note that the application NEVER needs to call + * any of the functions defined here; they are purely called + * from within ubxlib to connect a platform's PPP interface. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** Suggested size of receive buffer to request if pReceiveData + * passed to #uPortPppConnectCallback_t is NULL. + */ +#define U_PORT_PPP_RECEIVE_BUFFER_BYTES 1024 + +#ifndef U_PORT_PPP_SHUTDOWN_TIMEOUT_SECONDS +/** How long to wait for the IP stack that PPP is attached to to + * shut down any connections that may be running over PPP down. + */ +# define U_PORT_PPP_SHUTDOWN_TIMEOUT_SECONDS 10 +#endif + +#ifndef U_PORT_PPP_DNS_PRIMARY_DEFAULT_STR +/** The primary DNS address to use if it is not possible to + * read the primary DNS address from the module. Use NULL + * to provide no default. + */ +# define U_PORT_PPP_DNS_PRIMARY_DEFAULT_STR "8.8.8.8" +#endif + +#ifndef U_PORT_PPP_DNS_SECONDARY_DEFAULT_STR +/** The secondary DNS address to use if it is not possible to + * read the secondary DNS address from the module. Use NULL + * to provide no default. + */ +# define U_PORT_PPP_DNS_SECONDARY_DEFAULT_STR "8.8.4.4" +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** The possible authentication modes for the PPP connection. + * + * Note: there is also a #uCellNetAuthenticationMode_t enumeration + * which is set to match this one. If you make a change here you + * may need to make a change there also. + */ +typedef enum { + U_PORT_PPP_AUTHENTICATION_MODE_NONE = 0, + U_PORT_PPP_AUTHENTICATION_MODE_PAP = 1, + U_PORT_PPP_AUTHENTICATION_MODE_CHAP = 2, + U_PORT_PPP_AUTHENTICATION_MODE_MAX_NUM +} uPortPppAuthenticationMode_t; + +/** Callback to receive a buffer of data from the PPP interface of + * a module. This function may be hooked into the PPP API at the + * bottom-end of a platform's IP stack to permit it to receive the + * contents of PPP frames arriving from a module. Any data at pData + * should be handled by this function before it returns as it may be + * overwritten afterwards. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the [for + * example celular] instance that called + * the callback; this is a void * + * rather than a #uDeviceHandle_t here + * in order to avoid dragging in all + * of the uDevice types into the port + * layer. + * @param[in] pData a pointer to the received data; + * will not be NULL. + * @param dataSize the number of bytes of data at pData. + * @param[in,out] pCallbackParam the callback parameter that was + * passed to uPortPppAttach(). + */ +typedef void (uPortPppReceiveCallback_t)(void *pDevHandle, + const char *pData, + size_t dataSize, + void *pCallbackParam); + +/** Callback that opens the PPP interface of a module. If the PPP + * interface is already open this function should do nothing and + * return success; uPortPppDetach() should be called first if you + * would like to change the buffering arrangements, the callback + * or its parameter. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * on which the PPP channel is to be + * opened; this is a void * + * rather than a #uDeviceHandle_t here + * in order to avoid dragging in all + * of the uDevice types into the port + * layer. + * @param[in] pReceiveCallback the data reception callback; may be + * NULL if only data transmission is + * required. + * @param[in,out] pReceiveCallbackParam a parameter that will be passed to + * pReceiveCallback as its last parameter; + * may be NULL, ignored if pReceiveCallback + * is NULL. + * @param[in] pReceiveData a pointer to a buffer for received + * data; may be NULL, in which case, if + * pReceiveCallback is non-NULL, this code + * will provide a receive buffer. + * @param receiveDataSize the amount of space at pReceiveData in + * bytes or, if pReceiveData is NULL, the + * receive buffer size that should be + * allocated by this function; + * #U_PORT_PPP_RECEIVE_BUFFER_BYTES is + * a sensible value. + * @param[in] pKeepGoingCallback a callback function that governs how + * long to wait for the PPP connection to + * open. This function will be called + * once a second while waiting for the + * PPP connection to complete; the PPP + * open attempt will only continue while + * it returns true. This allows the caller + * to terminate the connection attempt at + * their convenience. May be NULL, in + * which case the connection attempt will + * eventually time out on failure. + * @return zero on success, else negative error + * code. + */ +typedef int32_t (uPortPppConnectCallback_t) (void *pDevHandle, + uPortPppReceiveCallback_t *pReceiveCallback, + void *pReceiveCallbackParam, + char *pReceiveData, size_t receiveDataSize, + bool (*pKeepGoingCallback) (void *pDevHandle)); + +/** Callback that closes the PPP interface of a module. When this + * function has returned the pReceiveCallback function passed to + * #uPortPppConnectCallback_t will no longer be called and any + * pReceiveData buffer passed to #uPortPppConnectCallback_t will + * no longer be written-to. If no PPP connection is open this + * function will do nothing and return success. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device on which + * the PPP channel is to be closed; this is a + * void * rather than a #uDeviceHandle_t here + * in order to avoid dragging in all of the + * uDevice types into the port layer. + * @param pppTerminateRequired set this to true if the PPP connection + * should be terminated first or leave + * as false if the PPP connection + * has already been terminated by + * the peer. + * @return zero on success, else negative error code. + */ +typedef int32_t (uPortPppDisconnectCallback_t) (void *pDevHandle, + bool pppTerminateRequired); + +/** Callback to transmit data over the PPP interface of a module. + * This may be integrated into a higher layer, e.g. the PPP + * interface at the bottom of an IP stack of a platform, to permit + * it to send PPP frames over a module. #uPortPppConnectCallback_t must + * have returned successfully for transmission to succeed. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device on which + * the PPP channel is to be transmitted; this + * is a void * rather than a #uDeviceHandle_t + * here in order to avoid dragging in all of + * the uDevice types into the port layer. + * @param[in] pData a pointer to the data to transmit; can only + * be NULL if dataSize is zero. + * @param dataSize the number of bytes of data at pData. + * @return on success the number bytes transmitted, + * which may be less than dataSize, else + * negative error code. + */ +typedef int32_t (uPortPppTransmitCallback_t) (void *pDevHandle, + const char *pData, + size_t dataSize); + +/* ---------------------------------------------------------------- + * FUNCTIONS: WORKAROUND FOR LINKER ISSUE + * -------------------------------------------------------------- */ + +/** Workaround for Espressif linker missing out files that + * only contain functions which also have weak alternatives + * (see https://www.esp32.com/viewtopic.php?f=13&t=8418&p=35899). + * + * You can ignore this function. + */ +void uPortPppDefaultPrivateLink(void); + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Attach a PPP interface to the bottom of the IP stack of a + * platform. This is called by a ubxlib layer (e.g. cellular) + * when a device is powered-up that is able to support PPP. This + * function performs all of the logical connection with the platform + * but it does NOT call any of the callback functions passed in, the + * ones that interact with the [e.g. cellular] device; those are + * simply stored for use when uPortPppConnect(), uPortPppReconnect(), + * uPortPppDisconnect() or uPortPppDetach() are called. + * + * The application NEVER NEEDS to call this function; it is purely + * for internal use within ubxlib. + * + * If the PPP interface is already attached this function will do + * nothing and return success; to ensure that any new parameters + * are adopted, uPortPppDetach() should be called first. + * + * If a PPP interface is not supported by the platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * Note: this only attaches the PPP interface logically, the + * interface cannot be used until uPortPppConnect() is called. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * that is offering the PPP interface; + * this is a void * rather than a + * #uDeviceHandle_t here in order to + * avoid dragging in all of the uDevice + * types into the port layer. + * @param[in] pConnectCallback a callback that will open the PPP + * interface on the device; may be + * NULL if the PPP interface is transmit + * only and is always open. + * @param[in] pDisconnectCallback a callback that will close the PPP + * interface on the device; may be + * NULL if the PPP interface cannot + * be closed. + * @param[in] pTransmitCallback a callback that the platform may call + * to send PPP data over the PPP + * interface; may be NULL if is it not + * possible to transmit data over the + * PPP interface. + * @return zero on success, else negative error code. + */ +int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback); + +/** Indicate that a PPP interface that was previously attached with + * a call to uPortPppAttach() is now connected. Internally + * #uPortPppConnectCallback_t will be called. + * + * The application NEVER NEEDS to call this function; it is purely + * for internal use within ubxlib. + * + * If a PPP interface is not supported by the platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * that is offering the PPP interface; + * this is a void * rather than a + * #uDeviceHandle_t here in order to + * avoid dragging in all of the uDevice + * types into the port layer. + * @param[in] pIpAddress the IP address, if already assigned, + * NULL if not; you do not need to provide + * this if you are sure that the PPP + * negotiation process will do so. + * @param[in] pDnsIpAddressPrimary the primary DNS address, if already + * known, NULL if not; currently only + * IPV4 addresses are supported. You + * do not need to provide this if you + * are sure that the PPP negotiation + * process will do so. + * @param[in] pDnsIpAddressSecondary the secondary DNS address, if already + * known, NULL if not; currently only + * IPV4 addresses are supported. You do + * not need to provide this if you are + * sure that the PPP negotiation process + * will do so. + * @param[in] pUsername pointer to a string giving the user + * name for PPP authentication; should + * be set to NULL if no user name or + * password is required. This value + * is currently IGNORED in the Zephyr + * case since the user name is hard-coded + * by Zephyr (see pap.c inside Zephyr). + * @param[in] pPassword pointer to a string giving the + * password for PPP authentication; must + * be non-NULL if pUsername is non-NULL, + * ignored if pUsername is NULL. This + * value is currently IGNORED in the Zephyr + * case since the password is hard-coded + * by Zephyr (see pap.c inside Zephyr). + * @param authenticationMode the authentication mode, ignored if + * pUsername is NULL; ignored by Zephyr + * (PAP will be used if authentication is + * required). + * @return zero on success, else negative error + * code. + */ +int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode); + +/** Reconnect a PPP interface after it was lost due to, for instance, + * a radio interface service loss. Internally #uPortPppConnectCallback_t + * will be called. + * + * The application NEVER NEEDS to call this function; it is purely + * for internal use within ubxlib. + * + * If a PPP interface is not supported by the platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * that is offering the PPP interface; + * this is a void * rather than a + * #uDeviceHandle_t here in order to + * avoid dragging in all of the uDevice + * types into the port layer. + * @param[in] pIpAddress the IP address, if already assigned, + * NULL if not; you do not need to provide + * this if you are sure that the PPP + * negotiation process has done so. + * @return zero on success, else negative error + * code. + */ +int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress); + +/** Indicate that a PPP interface that was previously attached with + * a call to uPortPppAttach() is going to be disconnected. This + * must be called by a ubxlib layer (e.g. cellular) that previous + * called uPortPppConnect() _before_ that connection is brought down. + * Internally it will cause #uPortPppDisconnectCallback_t to be called. + * + * The application NEVER NEEDS to call this function; it is purely + * for internal use within ubxlib. + * + * When this function has returned, pReceiveCallback passed + * to #uPortPppConnectCallback_t will no longer be called and any + * pReceiveData buffer passed to #uPortPppConnectCallback_t will no + * longer be written-to. + * + * If no PPP connection is open this function will do nothing. + * + * If a PPP interface is not supported by the platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * that offered the PPP interface; this + * is a void * rather than a #uDeviceHandle_t + * in order to avoid dragging in all of the + * uDevice types into the port layer. + * @return zero on success, else negative error code. + */ +int32_t uPortPppDisconnect(void *pDevHandle); + +/** Detach a PPP interface from the bottom of a platform's IP stack. + * #uPortPppDisconnectCallback_t will be called first. + * + * The application NEVER NEEDS to call this function; it is purely + * for internal use within ubxlib. + * + * When this function has returned none of the callbacks passed to + * uPortPppAttach() will be called any more. + * + * If no PPP connection is open this function will do nothing. + * + * If a PPP interface is not supported by the platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device that + * originally called uPortPppAttach(); this + * is a void * rather than a #uDeviceHandle_t + * here in order to avoid dragging in all of + * the uDevice types into the port layer. + * @return zero on success, else negative error code. + */ +int32_t uPortPppDetach(void *pDevHandle); + +#ifdef __cplusplus +} +#endif + +/** @}*/ + +#endif // _U_PORT_PPP_H_ + +// End of file diff --git a/port/platform/arduino/source.txt b/port/platform/arduino/source.txt index c0df33743..867f384ef 100644 --- a/port/platform/arduino/source.txt +++ b/port/platform/arduino/source.txt @@ -32,6 +32,8 @@ cell/src/u_cell_geofence.c cell/src/u_cell_private.c cell/src/u_cell_mux_private.c cell/src/u_cell_mno_db.c +cell/src/u_cell_ppp.c +cell/src/u_cell_stub_gnss.c gnss/src/u_gnss.c gnss/src/u_gnss_pwr.c gnss/src/u_gnss_cfg.c @@ -121,6 +123,7 @@ common/geofence/src/u_geofence.c common/geofence/src/dummy/u_geofence_geodesic.c port/u_port_heap.c port/u_port_resource.c +port/u_port_ppp_default.c port/platform/common/event_queue/u_port_event_queue.c port/clib/u_port_clib_mktime64.c port/u_port_timezone.c @@ -131,6 +134,7 @@ port/platform/esp-idf/src/u_port_gpio.c port/platform/esp-idf/src/u_port_uart.c port/platform/esp-idf/src/u_port_i2c.c port/platform/esp-idf/src/u_port_spi.c +port/platform/esp-idf/src/u_port_ppp.c port/platform/esp-idf/src/u_port_private.c port/platform/common/mutex_debug/u_mutex_debug.c port/platform/common/log_ram/u_log_ram.c diff --git a/port/platform/arduino/source_test.txt b/port/platform/arduino/source_test.txt index ed9a38893..a6d5e09ea 100644 --- a/port/platform/arduino/source_test.txt +++ b/port/platform/arduino/source_test.txt @@ -43,6 +43,7 @@ cell/test/u_cell_gpio_test.c cell/test/u_cell_fota_test.c cell/test/u_cell_mux_test.c cell/test/u_cell_geofence_test.c +cell/test/u_cell_ppp_test.c cell/test/u_cell_test_preamble.c cell/test/u_cell_test_private.c cell/test/u_cell_mux_private_test.c @@ -97,6 +98,7 @@ port/platform/common/test/u_preamble_test.c port/platform/common/test/u_postamble_test.c port/platform/common/test/u_cleanup_test.c port/platform/common/test_util/u_test_util_resource_check.c +port/platform/esp-idf/test/u_espidf_ppp_test.c # Note: it is deliberate that u_runner.c is here but # port/platform/common/runner is in "include.txt" # and NOT just in "include_test.txt": the header file diff --git a/port/platform/common/automation/DATABASE.md b/port/platform/common/automation/DATABASE.md index ae0cd7870..35afd8ae9 100644 --- a/port/platform/common/automation/DATABASE.md +++ b/port/platform/common/automation/DATABASE.md @@ -29,7 +29,7 @@ The table below defines the instances of test hardware available on the `ubxlib` | 5.2 | Check build without logging | | | | | | | | U_CFG_ENABLE_LOGGING=0 U_CFG_GEOFENCE | | 5.3 | Check public headers not in ubxlib.h | | | | | | | | U_CFG_GEOFENCE | | 5.4 | Check malloc()/free() being called | | | | | | | | U_CFG_GEOFENCE | -| 6.1 | CodeChecker (Zephyr) | NRF5340 | nrf5340dk_nrf5340_cpuapp | CodeChecker:Zephyr | | SARA_R5 M8 NINA_W15 | | | U_CFG_BLE_MODULE_INTERNAL U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_BLE_TEST_CFG_REMOTE_SPS_PERIPHERAL=2462ABB6EAC6p U_CFG_APP_SHORT_RANGE_ROLE=1 U_CFG_TEST_NET_STATUS_CELL U_CFG_TEST_NET_STATUS_SHORT_RANGE U_DEBUG_UTILS_DUMP_THREADS U_CFG_HEAP_MONITOR | +| 6.1 | CodeChecker (Zephyr) | NRF5340 | nrf5340dk_nrf5340_cpuapp | CodeChecker:Zephyr | | SARA_R5 M8 NINA_W15 | | | U_CFG_PPP_ENABLE U_CFG_BLE_MODULE_INTERNAL U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_BLE_TEST_CFG_REMOTE_SPS_PERIPHERAL=2462ABB6EAC6p U_CFG_APP_SHORT_RANGE_ROLE=1 U_CFG_TEST_NET_STATUS_CELL U_CFG_TEST_NET_STATUS_SHORT_RANGE U_DEBUG_UTILS_DUMP_THREADS U_CFG_HEAP_MONITOR | | 6.2.0 | CodeChecker (STM32Cube) | STM32F4 | | CodeChecker:STM32Cube || SARA_R5 NINA_W15 M8 | | | U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=0x42 U_CFG_SARA_R5_M8_WORKAROUND U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_CFG_TEST_NET_STATUS_CELL U_CFG_TEST_NET_STATUS_SHORT_RANGE U_DEBUG_UTILS_DUMP_THREADS U_CFG_HEAP_MONITOR | | 6.2.1 | CodeChecker (STM32Cube) with geofence | STM32F4 | | CodeChecker:STM32Cube || SARA_R5 NINA_W15 M8 | | | U_CFG_GEOFENCE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=0x42 U_CFG_SARA_R5_M8_WORKAROUND U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_CFG_TEST_NET_STATUS_CELL U_CFG_TEST_NET_STATUS_SHORT_RANGE U_DEBUG_UTILS_DUMP_THREADS U_CFG_HEAP_MONITOR | | 7 | Build PIO examples | | | | | | | | | @@ -40,23 +40,24 @@ The table below defines the instances of test hardware available on the `ubxlib` | 11.0 | ESP32-DevKitC | ESP32 | | ESP-IDF | | M9 | port at_client ubx_protocol gnss spartn | | U_CFG_APP_GNSS_SPI=2 U_CFG_TEST_PIN_GNSS_RESET_N=25 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_MUTEX_DEBUG U_CFG_TEST_UART_B=1 U_CFG_TEST_PIN_UART_A_CTS=-1 U_CFG_TEST_PIN_UART_A_RTS=-1 U_CFG_TEST_PIN_UART_A_RXD=26 U_CFG_TEST_PIN_UART_B_TXD=27 U_CFG_TEST_PIN_UART_B_RXD=14 U_DEBUG_UTILS_DUMP_THREADS | | 11.1 | ESP32-DevKitC | ESP32 | esp32:esp32:esp32doit-devkit-v1 | Arduino | ESP-IDF | M9 | port at_client ubx_protocol gnss spartn | | U_CFG_APP_GNSS_SPI=2 U_CFG_TEST_PIN_GNSS_RESET_N=25 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_TEST_UART_B=1 U_CFG_TEST_PIN_UART_A_CTS=-1 U_CFG_TEST_PIN_UART_A_RTS=-1 U_CFG_TEST_PIN_UART_A_RXD=26 U_CFG_TEST_PIN_UART_B_TXD=27 U_CFG_TEST_PIN_UART_B_RXD=14 | | 11.2 | ESP32-DevKitC | ESP32 | esp-wrover-kit | platformio | arduino | M9 | port device network | | U_CFG_APP_FILTER=port.example U_CFG_APP_GNSS_SPI=2 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_TEST_PIN_GNSS_RESET_N=25 | -| 12.0 | ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | | ESP-IDF | | SARA_R5 M8 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client gnss location || U_CELL_TEST_MUX_ALWAYS U_CFG_TEST_SECURITY_DISABLE U_CFG_TEST_CELL_PWR_DISABLE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | +| 12.0 | ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | | ESP-IDF | | SARA_R5 M8 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client gnss location || U_CFG_PPP_ENABLE U_CELL_TEST_MUX_ALWAYS U_CFG_TEST_SECURITY_DISABLE U_CFG_TEST_CELL_PWR_DISABLE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | | 12.2.0| ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | esp-wrover-kit | platformio | espidf | SARA_R5 M8 NINA_W15 | port device network | | U_CFG_APP_FILTER=port.example.gnssInfo U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p | | 12.2.1| ESP32-DevKitC + EVK, Cat M1 | ESP32 | esp-wrover-kit | platformio | espidf | SARA_R5 | port device network | cell | U_CFG_APP_FILTER=cellInfo U_CFG_TEST_SECURITY_DISABLE U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 | | 12.2.2| ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | esp-wrover-kit | platformio | espidf | NINA_W15 | port device network | short_range | U_CFG_APP_FILTER=wifiStation U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p | | 13.0.0| Nordic DK board (NRF52840) + EVK, Cat M1 | NRF52840 | | nRF5SDK | GCC | SARA_R5 M10 | port at_client cell sock network mqtt_client ubx_protocol gnss spartn || U_CFG_TEST_CELL_PWR_DISABLE U_CFG_TEST_MQTT_CLIENT_SN_DISABLE_CONNECTIVITY_TEST U_CELL_CFG_SARA_R5_00B U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_APP_GNSS_I2C=1 U_CFG_TEST_PIN_GNSS_RESET_N=29 U_CFG_TEST_UART_B=0 U_CFG_TEST_PIN_UART_A_CTS=-1 U_CFG_TEST_PIN_UART_A_RTS=-1 U_CFG_TEST_PIN_UART_B_TXD=44 U_CFG_TEST_PIN_UART_B_RXD=43 U_CFG_TEST_PIN_UART_A_RXD=45 U_DEBUG_UTILS_DUMP_THREADS | -| 13.1 | Nordic DK board (NRF52840) + EVK | NRF52840 | nrf52840dk_nrf52840 | Zephyr | | | port at_client ubx_protocol spartn | short_range | U_CFG_TEST_UART_B=0 U_DEBUG_UTILS_DUMP_THREADS | +| 13.1 | Nordic DK board (NRF52840) + EVK | NRF52840 | nrf52840dk_nrf52840 | Zephyr | | | port at_client ubx_protocol spartn | short_range | U_CFG_PPP_ENABLE U_CFG_TEST_UART_B=0 U_DEBUG_UTILS_DUMP_THREADS | | 14 | STM32F407 Discovery + EVK, Cat M1 | STM32F4 | | STM32Cube | | SARA_R422 M9 | port device network sock security cell mqtt_client http_client gnss location || CMSIS_V2 U_CFG_TEST_CELL_PWR_DISABLE U_CFG_TEST_GNSS_ASSIST_NOW U_GNSS_MGA_TEST_HAS_FLASH U_LOCATION_TEST_DISABLE U_CFG_1V8_SIM_WORKAROUND HSE_VALUE=8000000U U_CFG_APP_GNSS_SPI=2 U_CFG_APP_PIN_GNSS_SPI_MOSI=0x1F U_CFG_APP_PIN_GNSS_SPI_MISO=0x1E U_CFG_APP_PIN_GNSS_SPI_CLK=0x1D U_CFG_APP_PIN_GNSS_SPI_SELECT=0x1C U_CFG_TEST_PIN_GNSS_RESET_N=0x40 U_CFG_TEST_PIN_C=0x3F U_CFG_APP_GNSS_UART=-1 U_CFG_APP_PIN_GNSS_ENABLE_POWER=-1 U_CFG_TEST_UART_A=-1 U_CFG_APP_PIN_C030_ENABLE_3V3=-1 U_CFG_APP_PIN_CELL_RESET=-1 U_CFG_APP_CELL_UART=3 U_CFG_APP_PIN_CELL_TXD=0x38 U_CFG_APP_PIN_CELL_RXD=0x39 U_CFG_APP_PIN_CELL_RTS=-1 U_CFG_APP_PIN_CELL_CTS=-1 U_DEBUG_UTILS_DUMP_THREADS | | 15.0.0| Nordic DK board (NRF52840) + EVK | NRF52840 | | nRF5SDK | GCC | M9 | port gnss device | gnss | U_CFG_APP_GNSS_SPI=3 U_CFG_TEST_PIN_GNSS_RESET_N=37 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_MUTEX_DEBUG U_DEBUG_UTILS_DUMP_THREADS | | 15.1 | Nordic DK board (NRF52840) + EVK | NRF52840 | nrf52840dk_nrf52840 | Zephyr | | M9 | port device network ble short_range gnss | | U_CFG_TEST_UART_A=-1 U_CFG_APP_GNSS_SPI=3 U_CFG_APP_PIN_GNSS_SPI_SELECT=29 U_CFG_TEST_PIN_GNSS_RESET_N=37 U_GNSS_MGA_TEST_HAS_FLASH U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | | 16 | STM32F407 Discovery | STM32F4 | | STM32Cube | | M10 | port ubx_protocol gnss spartn geofence | gnss | U_CFG_GEOFENCE HSE_VALUE=8000000U U_PORT_TEST_DISABLE_I2C U_GNSS_MGA_TEST_DISABLE_DATABASE U_CFG_APP_GNSS_I2C=1 U_CFG_TEST_PIN_GNSS_RESET_N=0x40 U_CFG_APP_GNSS_UART=-1 U_CFG_APP_PIN_GNSS_ENABLE_POWER=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_CFG_APP_PIN_C030_ENABLE_3V3=-1 U_CFG_APP_PIN_CELL_RESET=-1 U_DEBUG_UTILS_DUMP_THREADS | | 17.1.0| Nordic NRF5340 DK board | NRF5340 | nrf5340dk_nrf5340_cpuapp | Zephyr | | M9 | port device network ble short_range lib_common ubx_protocol gnss spartn || U_CFG_APP_GNSS_SPI=2 U_CFG_APP_PIN_GNSS_SPI_SELECT=46 U_CFG_TEST_PIN_GNSS_RESET_N=37 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_BLE_MODULE_INTERNAL U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_BLE_TEST_CFG_REMOTE_SPS_PERIPHERAL=2462ABB6EAC6p U_CFG_APP_SHORT_RANGE_ROLE=1 U_DEBUG_UTILS_DUMP_THREADS | | 17.1.1| Nordic NRF5340 DK board | NRF5340 | nrf5340dk_nrf5340_cpuapp | Zephyr | | M9 | port | gnss | U_CFG_APP_FILTER=port U_CFG_APP_GNSS_SPI=2 U_CFG_TEST_GNSS_SPI_SELECT_INDEX=1 U_CFG_APP_PIN_GNSS_SPI_SELECT=46 U_CFG_TEST_PIN_GNSS_RESET_N=37 U_GNSS_MGA_TEST_HAS_FLASH U_DEBUG_UTILS_DUMP_THREADS | -| 18 | NORA-B1 NRF5340 DK board + EVK, Cat M1 | NRF5340 | nrf5340dk_nrf5340_cpuapp | Zephyr | | SARA_R5 M8 | port device network sock security cell lib_common mqtt_client http_client gnss location || U_CELL_TEST_MUX_ALWAYS U_CFG_TEST_CELL_PWR_DISABLE U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_TEST_CLOUD_LOCATE U_CFG_TEST_CELL_LOCATE U_CFG_TEST_GNSS_ASSIST_NOW U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_PIN_CELL_DTR=37 U_CFG_APP_PIN_CELL_PWR_ON=36 U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | +| 18 | NORA-B1 NRF5340 DK board + EVK, Cat M1 | NRF5340 | nrf5340dk_nrf5340_cpuapp | Zephyr | | SARA_R5 M8 | port device network sock security cell lib_common mqtt_client http_client gnss location || U_CFG_PPP_ENABLE U_CELL_TEST_MUX_ALWAYS U_CFG_TEST_CELL_PWR_DISABLE U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_TEST_CLOUD_LOCATE U_CFG_TEST_CELL_LOCATE U_CFG_TEST_GNSS_ASSIST_NOW U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_PIN_CELL_DTR=37 U_CFG_APP_PIN_CELL_PWR_ON=36 U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | | 19 | C030-R5 board (STM32F437), Cat M1, uConnect| STM32F4 | | STM32Cube | | SARA_R5 NINA_W15 M8 | port device network sock ble wifi cell short_range security mqtt_client http_client ubx_protocol spartn gnss location || U_CFG_TEST_CELL_PWR_DISABLE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=0x42 U_CFG_SARA_R5_M8_WORKAROUND U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | | 20 | WHRE board (NINA-W1), Cat M1 | ESP32 | | ESP-IDF | | SARA_R412M_02B | cell mqtt_client | | U_CFG_APP_FILTER=cellMqtt.mqttClient.exampleMqtt.cellMuxMqtt.cellCfgGreeting.cellInfo.cellCfgTime U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | | 21 | WHRE board (NINA-W1), Cat M1 | ESP32 | | ESP-IDF | | SARA_R410M_02B | cell mqtt_client | | U_CFG_APP_FILTER=cellMqtt.mqttClient.exampleMqtt.cellMuxMqtt.cellCfgGreeting.cellInfo.cellCfgTime U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | -| 22 | NINA-W1 + EVK, Cat M1 | ESP32 | esp32:esp32:nina_w10 | Arduino | ESP-IDF | SARA_R422 M8 | port device network sock security cell mqtt_client http_client gnss location || U_CFG_TEST_CELL_PWR_DISABLE U_GNSS_TEST_DISABLE_ACTIVE_ANTENNA_DISABLE U_CFG_MONITOR_DTR_RTS_OFF U_CFG_1V8_SIM_WORKAROUND U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_PWR_ON=5 U_CFG_APP_PIN_CELL_TXD=14 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | +| 22.0 | NINA-W1 + EVK, Cat M1 | ESP32 | esp32:esp32:nina_w10 | Arduino | ESP-IDF | SARA_R422 M8 | port device network sock security cell mqtt_client http_client gnss location || U_CFG_PPP_ENABLE U_CELL_MUX_ENABLE_DEBUG U_CELL_MUX_ENABLE_USER_TX_DEBUG U_CELL_MUX_ENABLE_USER_RX_DEBUG U_CFG_TEST_CELL_PWR_DISABLE U_GNSS_TEST_DISABLE_ACTIVE_ANTENNA_DISABLE U_CFG_MONITOR_DTR_RTS_OFF U_CFG_1V8_SIM_WORKAROUND U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_PWR_ON=5 U_CFG_APP_PIN_CELL_TXD=14 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | +| 22.1 | EVK, Cat M1 | ESP32 | | ESP-IDF | | SARA_R422 | port | | U_CFG_APP_FILTER=espidf U_CFG_PPP_ENABLE U_CFG_TEST_CELL_PWR_DISABLE U_CFG_MONITOR_DTR_RTS_OFF U_CFG_1V8_SIM_WORKAROUND U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_PWR_ON=5 U_CFG_APP_PIN_CELL_TXD=14 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | | 23 | Windows + EVK, Cat M1, uConnect | WIN32 | | WINDOWS | MSVC | SARA_R5 M8 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client ubx_protocol gnss spartn location geofence |cell short_range gnss geodesic| U_CFG_GEOFENCE U_AT_CLIENT_PRINT_WITH_TIMESTAMP U_CFG_HEAP_MONITOR U_ASSERT_HOOK_FUNCTION_TEST_RETURN U_CFG_TEST_DISABLE_GREETING_CALLBACK U_CFG_MUTEX_DEBUG U_NETWORK_GNSS_CFG_CELL_USE_AT_ONLY U_GNSS_MSG_TEST_MESSAGE_RECEIVE_NON_BLOCKING_PRINT U_CFG_TEST_NET_STATUS_CELL=RF_SWITCH_A U_CFG_TEST_NET_STATUS_SHORT_RANGE=PWR_SWITCH_A U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=6009C390E4DAp U_CFG_TEST_UART_A=100 U_CFG_APP_SHORT_RANGE_UART=101 U_CFG_APP_CELL_UART=102 U_CFG_QUEUE_DEBUG | | 24 | Linux/Posix under Zephyr | LINUX32 | native_posix | Zephyr | | | port gnss geofence | gnss geodesic | U_CFG_APP_FILTER=port.gnssFenceStandalone U_CFG_GEOFENCE U_CFG_GNSS_FENCE_USE_GEODESIC U_CFG_HEAP_MONITOR U_ASSERT_HOOK_FUNCTION_TEST_RETURN U_CFG_MUTEX_DEBUG U_CFG_TEST_UART_A=0 U_CFG_TEST_UART_B=1 | | 25 | HPG Solution board (NINA-W1), live network | ESP32 | | ESP-IDF | | LARA_R6 M9 | port device network sock cell security mqtt_client gnss location geofence || U_CFG_GEOFENCE U_CFG_TEST_GNSS_POWER_SAVING_NOT_SUPPORTED U_CFG_TEST_DISABLE_MUX U_GNSS_MGA_TEST_ASSIST_NOW_AUTONOMOUS_NOT_SUPPORTED U_NETWORK_GNSS_CFG_CELL_USE_AT_ONLY U_HTTP_CLIENT_DISABLE_TEST U_CELL_CFG_TEST_USE_FIXED_TIME_SECONDS U_CFG_TEST_CELL_GEOFENCE U_CFG_MONITOR_DTR_RTS_OFF U_CELL_TEST_NO_INVALID_APN U_CELL_TEST_CFG_BANDMASK1=0x0000000000080084ULL U_CELL_NET_TEST_RAT=U_CELL_NET_RAT_LTE U_CELL_TEST_CFG_MNO_PROFILE=90 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_PWR_ON=0x800c U_CFG_APP_PIN_CELL_RESET=13 U_CELL_RESET_PIN_DRIVE_MODE=U_PORT_GPIO_DRIVE_MODE_NORMAL U_CFG_APP_PIN_CELL_VINT=0x8025 U_CFG_APP_PIN_CELL_DTR=15 U_CFG_APP_PIN_CELL_TXD=25 U_CFG_APP_PIN_CELL_RXD=26 U_CFG_APP_PIN_CELL_RTS=27 U_CFG_APP_PIN_CELL_CTS=36 U_CFG_APP_GNSS_I2C=0 U_GNSS_TEST_I2C_ADDRESS_EXTRA=0x43 U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | diff --git a/port/platform/common/automation/ubxlib_h_excludes.txt b/port/platform/common/automation/ubxlib_h_excludes.txt index ddda49e77..ce9a3011f 100644 --- a/port/platform/common/automation/ubxlib_h_excludes.txt +++ b/port/platform/common/automation/ubxlib_h_excludes.txt @@ -10,4 +10,7 @@ u_lib_internal.h lib_fibonacci.h # Contains no user-callable functions -u_geofence_geodesic.h \ No newline at end of file +u_geofence_geodesic.h + +# Called only internally within ubxlib +u_port_ppp.h \ No newline at end of file diff --git a/port/platform/common/test/u_preamble_test.c b/port/platform/common/test/u_preamble_test.c index 243cceaa0..e243e6d00 100644 --- a/port/platform/common/test/u_preamble_test.c +++ b/port/platform/common/test/u_preamble_test.c @@ -51,6 +51,7 @@ #include "u_port.h" #include "u_port_debug.h" #include "u_port_os.h" +#include "u_port_event_queue.h" #if defined(U_CFG_TEST_PIN_GNSS_RESET_N) && (U_CFG_TEST_PIN_GNSS_RESET_N >= 0) #include "u_port_gpio.h" #endif @@ -201,6 +202,7 @@ U_PORT_TEST_FUNCTION("[preamble]", "preambleHeapDefence") uPortTaskBlock(2000); #endif + uPortEventQueueCleanUp(); uPortDeinit(); #ifdef PRE_ALLOCATE_FILE_COUNT diff --git a/port/platform/esp-idf/README.md b/port/platform/esp-idf/README.md index bdb02fcf0..37b87bf6e 100644 --- a/port/platform/esp-idf/README.md +++ b/port/platform/esp-idf/README.md @@ -4,4 +4,17 @@ These directories provide the implementation of the porting layer on the Espress - [app](app): contains the code that runs the application (both examples and unit tests) on the ESP-IDF platform. - [src](src): contains the implementation of the porting layers for ESP-IDF. - [mcu](mcu): contains the configuration and build files for the MCUs supported on the ESP-IDF platform. -- [u_cfg_os_platform_specific.h](u_cfg_os_platform_specific.h): task priorities and stack sizes for the platform, built into this code. \ No newline at end of file +- [u_cfg_os_platform_specific.h](u_cfg_os_platform_specific.h): task priorities and stack sizes for the platform, built into this code. +- [test](test): contains tests that use ESP-IDF application APIs to check out the integration of `ubxlib` into ESP-IDF, e.g. at PPP level beneath LWIP. + +# PPP-Level Integration With Cellular +`ubxlib` depends on very few ESP-IDF components (see the directories beneath this one for how to perform a build) but note that, if you wish to include a PPP-level integration at the base of the ESP-IDF LWIP stack, allowing use of native ESP-IDF clients (e.g. MQTT) with a cellular connection, then you must define `U_CFG_PPP_ENABLE` when building `ubxlib` and you must switch on the following in your `sdkconfig` file: + +- `CONFIG_LWIP_PPP_SUPPORT` +- `CONFIG_ESP_NETIF_TCPIP_LWIP` +- `CONFIG_LWIP_PPP_PAP_SUPPORT` +- if your network operator requires a user name and password along with the APN **AND** requires CHAP authentication, then also switch on `CONFIG_LWIP_PPP_CHAP_SUPPORT` + +If you are minimising the components built into your main application then you should add the ESP-IDF component `esp_netif` to the list. If you fail to do this your code will either fail to link because `_g_esp_netif_netstack_default_ppp` could not be found, or the initial PPP LCP negotiation phase with the module will fail. + +You must also call `esp_netif_init()` and `esp_event_loop_create_default()` at start of day from your application code, otherwise ESP-IDF will not work correctly or will go "bang" somewhere in the IP stack at the point that a cellular connection is made. \ No newline at end of file diff --git a/port/platform/esp-idf/app/u_main.c b/port/platform/esp-idf/app/u_main.c index efc4da5a8..9243e043c 100644 --- a/port/platform/esp-idf/app/u_main.c +++ b/port/platform/esp-idf/app/u_main.c @@ -40,6 +40,15 @@ #include "u_debug_utils.h" +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#ifdef CONFIG_LWIP_PPP_SUPPORT +# include "esp_log.h" +# include "esp_event.h" +# include "esp_netif.h" +#endif + /* ---------------------------------------------------------------- * COMPILE-TIME MACROS * -------------------------------------------------------------- */ @@ -77,6 +86,12 @@ static void appTask(void *pParam) U_MUTEX_DEBUG_WATCHDOG_TIMEOUT_SECONDS); #endif +#ifdef CONFIG_LWIP_PPP_SUPPORT + esp_netif_init(); + esp_event_loop_create_default(); + esp_log_level_set("*", ESP_LOG_VERBOSE); +#endif + #ifdef U_RUNNER_TOP_STR // If U_RUNNER_TOP_STR is defined we must be running inside the // test automation system (since the definition is added by @@ -88,7 +103,7 @@ static void appTask(void *pParam) uPortTaskBlock(5000); uPortInit(); -#if U_CFG_APP_PIN_CELL_RESET >= 0 +# if U_CFG_APP_PIN_CELL_RESET >= 0 // Set reset high (i.e. not reset) if it is connected (this for the // HPG Solution board) we use in the ubxlib test farm gpioConfig.pin = U_CFG_APP_PIN_CELL_RESET; @@ -96,7 +111,7 @@ static void appTask(void *pParam) gpioConfig.direction = U_PORT_GPIO_DIRECTION_OUTPUT; uPortGpioConfig(&gpioConfig); uPortGpioSet(U_CFG_APP_PIN_CELL_RESET, 1); -#endif +# endif UNITY_BEGIN(); @@ -120,6 +135,10 @@ static void appTask(void *pParam) uPortDeinit(); +# ifdef CONFIG_LWIP_PPP_SUPPORT + esp_netif_deinit(); +# endif + while (1) {} #else // If U_RUNNER_TOP_STR is not defined we must be running outside diff --git a/port/platform/esp-idf/mcu/esp32/README.md b/port/platform/esp-idf/mcu/esp32/README.md index 1e9f3dd3c..a7986398c 100644 --- a/port/platform/esp-idf/mcu/esp32/README.md +++ b/port/platform/esp-idf/mcu/esp32/README.md @@ -26,7 +26,7 @@ set U_FLAGS=-DMY_FLAG -DU_CFG_APP_PIN_CELL_ENABLE_POWER=-1 With this done, `cd` to your chosen build directory beneath this one to build and download your code. # Integration With Your Application -To use this port in your ESP32 application you need to include the [port/platform/esp-idf/mcu/esp32/components](components) directory in your `EXTRA_COMPONENT_DIRS` and add `ubxlib` to `COMPONENTS`. As an example you can have a look at [runner/CMakeLists.txt](runner/CMakeLists.txt). An `sdkconfig` configuration that allows the complete `ubxlib` test suite to be run can be found in [runner/sdkconfig.defaults](runner/sdkconfig.defaults) but the `ubxlib` core code requires no particular configuration beyond the default (just UART console output defaulting to HW block 0 for debugging); stack/heap should simply be configured as you require for your application. +To use this port in your ESP32 application you need to include the [port/platform/esp-idf/mcu/esp32/components](components) directory in your `EXTRA_COMPONENT_DIRS` and add `ubxlib` to `COMPONENTS`. As an example you can have a look at [runner/CMakeLists.txt](runner/CMakeLists.txt). An `sdkconfig` configuration that allows the complete `ubxlib` test suite to be run can be found in [runner/sdkconfig.defaults](runner/sdkconfig.defaults) but the `ubxlib` core code requires only UART console output (defaulting to HW block 0) for debugging, and `CONFIG_LWIP_PPP_SUPPORT` plus at least `CONFIG_LWIP_PPP_PAP_SUPPORT` for PPP connectivity; stack/heap should be configured as you require for your application. If you aren't already familiar with the ESP-IDF build environment, here's a step-by-step example, based on the approach ESP-IDF suggests and assuming you want to use, for instance, a sockets connection with a u-blox cellular module: diff --git a/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h b/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h index 07ad065af..67c7d8874 100644 --- a/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h +++ b/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef _U_PORT_APP_PLATFORM_SPECIFIC_H_ -#define _U_PORT_APP_PLATFORM_SPECIFIC_H_ +#ifndef _U_CFG_APP_PLATFORM_SPECIFIC_H_ +#define _U_CFG_APP_PLATFORM_SPECIFIC_H_ /** @file * @brief This header file contains configuration information for @@ -334,6 +334,6 @@ # define U_CFG_APP_CELL_PIN_GNSS_DATA_READY 24 // AKA GPIO3 #endif -#endif // _U_PORT_APP_PLATFORM_SPECIFIC_H_ +#endif // _U_CFG_APP_PLATFORM_SPECIFIC_H_ // End of file diff --git a/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt b/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt index 903e24998..a00abb1ca 100644 --- a/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt +++ b/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt @@ -67,6 +67,7 @@ set(COMPONENT_SRCS ${PLATFORM_DIR}/src/u_port_uart.c ${PLATFORM_DIR}/src/u_port_i2c.c ${PLATFORM_DIR}/src/u_port_spi.c + ${PLATFORM_DIR}/src/u_port_ppp.c ${PLATFORM_DIR}/src/u_port_private.c ${PLATFORM_DIR}/../../clib/u_port_clib_mktime64.c ${PLATFORM_DIR}/../../u_port_timezone.c @@ -76,6 +77,12 @@ set(COMPONENT_PRIV_INCLUDEDIRS ${UBXLIB_PRIVATE_INC} ) +# Add the platform-specific tests and examples +list(APPEND UBXLIB_TEST_SRC + ${PLATFORM_DIR}/test/u_espidf_ppp_test.c + ${UBXLIB_BASE}/example/sockets/main_ppp_espidf.c +) + # Export these variables to parent so they can be picked up by ubxlib_runner set(UBXLIB_TEST_SRC ${UBXLIB_TEST_SRC} PARENT_SCOPE) set(UBXLIB_TEST_INC ${UBXLIB_TEST_INC} PARENT_SCOPE) @@ -83,8 +90,9 @@ set(UBXLIB_INC ${UBXLIB_INC} PARENT_SCOPE) set(UBXLIB_PRIVATE_INC ${UBXLIB_PRIVATE_INC} PARENT_SCOPE) set(UBXLIB_BASE ${UBXLIB_BASE} PARENT_SCOPE) -# From ESP-IDF v5, for drivers, timers and the debug helper in esp_system -set(COMPONENT_REQUIRES "driver" "esp_timer" "esp_system" ${UBXLIB_EXTRA_LIBS}) +# For crypto functions and, from ESP-IDF v5, for drivers, timers +# and the debug helper in esp_system +set(COMPONENT_REQUIRES "driver" "esp_timer" "esp_system" "esp_netif" ${UBXLIB_EXTRA_LIBS}) message("COMPONENT_REQUIRES for component UBXLIB is ${COMPONENT_REQUIRES}") diff --git a/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults b/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults index f699253a0..aca6fdac5 100644 --- a/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults +++ b/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults @@ -3,7 +3,7 @@ # Note: the IDF_TARGET is not specified here: it will default to esp32 # and can be overridden by setting the environment variable IDF_TARGET -# This should be sufficient that the user is left with 5 kbytes of +# This is sufficient that the user is left with 5 kbytes of # main task stack free for themselves CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 @@ -15,6 +15,11 @@ CONFIG_ESP_CONSOLE_UART_NUM=0 # ubxlib needs no more than the NANO stuff CONFIG_NEWLIB_NANO_FORMAT=y +# Needed to include PPP interface for integration into the bottom of LWIP +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_PAP_SUPPORT=y +CONFIG_LWIP_PPP_CHAP_SUPPORT=y + # We set this to "n" as otherwise the PlatformIO builds # fail because some AES functions are not brought-in # to their pre-built ESP-IDF library; normally it would @@ -32,3 +37,8 @@ CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y CONFIG_HEAP_TRACING_STANDALONE=y CONFIG_HEAP_TRACING=y +# Uncomment these as required when debugging +#CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y +#CONFIG_LWIP_PPP_DEBUG_ON=y +#CONFIG_LWIP_DEBUG=y + diff --git a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt index d0269911a..563f685c9 100644 --- a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt +++ b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt @@ -4,5 +4,5 @@ # stuff in the test sub-directory as the tests. set(COMPONENT_SRCS "") set(COMPONENT_ADD_INCLUDEDIRS "test") -set(COMPONENT_REQUIRES "driver" "esptool_py" "unity") +set(COMPONENT_REQUIRES "driver" "esptool_py" "unity" "esp_netif") register_component() \ No newline at end of file diff --git a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt index 96ff50d70..ebdfb6af9 100644 --- a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt +++ b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt @@ -30,7 +30,7 @@ if (DEFINED ENV{U_UBXLIB_AUTO}) list(APPEND COMPONENT_SRCS ${UBXLIB_BASE}/port/platform/common/runner/u_runner.c) endif() -set(COMPONENT_REQUIRES "driver" "esptool_py" "unity" ${GEODESIC_COMPONENT}) +set(COMPONENT_REQUIRES "driver" "esptool_py" "unity" "esp_netif" ${GEODESIC_COMPONENT}) message("COMPONENT_REQUIRES for component UBXLIB_RUNNER TEST is ${COMPONENT_REQUIRES}") diff --git a/port/platform/esp-idf/src/u_port.c b/port/platform/esp-idf/src/u_port.c index 8e43285c3..474b6a983 100644 --- a/port/platform/esp-idf/src/u_port.c +++ b/port/platform/esp-idf/src/u_port.c @@ -32,6 +32,8 @@ #include "u_port_heap.h" #include "u_port_uart.h" #include "u_port_event_queue_private.h" +#include "u_port_ppp.h" +#include "u_port_ppp_private.h" #include "u_port_private.h" #include "freertos/FreeRTOS.h" // For xPortGetFreeHeapSize() @@ -108,6 +110,16 @@ int32_t uPortInit() { int32_t errorCode = 0; + // Workaround for Espressif linker missing out files that + // only contain functions which also have weak alternatives + // (see https://www.esp32.com/viewtopic.php?f=13&t=8418&p=35899). + // Basically any file that might end up containing only functions + // that also have WEAK linked counterparts will be lost, so we need + // to add a dummy function in those files and call it from somewhere + // that will always be present in the build, which for the port + // layer we choose to be here + uPortPppDefaultPrivateLink(); + if (!gInitialised) { errorCode = uPortHeapMonitorInit(NULL, NULL, NULL); if (errorCode == 0) { @@ -116,6 +128,9 @@ int32_t uPortInit() errorCode = uPortPrivateInit(); if (errorCode == 0) { errorCode = uPortUartInit(); + if (errorCode == 0) { + errorCode = uPortPppPrivateInit(); + } } } } @@ -129,6 +144,7 @@ int32_t uPortInit() void uPortDeinit() { if (gInitialised) { + uPortPppPrivateDeinit(); uPortUartDeinit(); uPortPrivateDeinit(); uPortEventQueuePrivateDeinit(); diff --git a/port/platform/esp-idf/src/u_port_ppp.c b/port/platform/esp-idf/src/u_port_ppp.c new file mode 100644 index 000000000..00b19bc97 --- /dev/null +++ b/port/platform/esp-idf/src/u_port_ppp.c @@ -0,0 +1,747 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/** @file + * @brief This file makes a connection from the bottom of ESP NETIF, i.e. + * the bottom of the IP stack inside ESP-IDF, to a PPP interface inside + * ubxlib. Such a PPP interface is provided by a cellular module. + * + * It is only compiled if CONFIG_LWIP_PPP_SUPPORT is set in your + * sdkconfig.h and U_CFG_PPP_ENABLE is defined. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // memset() + +#include "u_cfg_sw.h" +#include "u_error_common.h" + +#include "u_linked_list.h" + +#include "u_sock.h" // uSockStringToAddress() + +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_ppp.h" +#include "u_port_debug.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#ifdef CONFIG_LWIP_PPP_SUPPORT +#include "esp_event.h" +#include "esp_netif_ip_addr.h" +#include "esp_netif.h" +#include "esp_netif_defaults.h" +#include "esp_netif_ppp.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_PORT_PPP_TX_LOOP_GUARD +/** How many times around the transmit loop to allow if stuff + * won't send. + */ +# define U_PORT_PPP_TX_LOOP_GUARD 100 +#endif + +#ifndef U_PORT_PPP_TX_LOOP_DELAY_MS +/** How long to wait between transmit attempts in milliseconds + * when the data to transmit won't go all at once. + */ +# define U_PORT_PPP_TX_LOOP_DELAY_MS 10 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Forward declaration. +struct uPortPppInterface_t; + +/** Define a NETIF driver for ESP-IDF, used to provide a PPP + * connection to the bottom of the ESP-IDF IP stack. + */ +typedef struct { + esp_netif_driver_base_t base; + struct uPortPppInterface_t *pPppInterface; + uSockIpAddress_t *pIpAddress; + uSockIpAddress_t *pDnsIpAddressPrimary; + const char *pUsername; + const char *pPassword; + uPortPppAuthenticationMode_t authenticationMode; +} uPortPppNetifDriver_t; + +/** Define a PPP interface. + */ +typedef struct uPortPppInterface_t { + void *pDevHandle; + uPortSemaphoreHandle_t semaphoreExit; /**< This is created set to + 0 when the interface is + created and is given + when the eventPppChanged() + is informed that the PPP + interface has been taken + down by the attached IP + stack. */ + uPortPppConnectCallback_t *pConnectCallback; + uPortPppDisconnectCallback_t *pDisconnectCallback; + uPortPppTransmitCallback_t *pTransmitCallback; + bool pppRunning; + bool ipConnected; + uPortPppNetifDriver_t netifDriver; +} uPortPppInterface_t; + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/** Root of the linked list of PPP entities. + */ +static uLinkedList_t *gpPppInterfaceList = NULL; /**< A linked list of uPortPppInterface_t. */ + +/** Mutex to protect the linked list of PPP entities. + */ +static uPortMutexHandle_t gMutex = NULL; + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Find the PPP interface structure for the given handle. +static uPortPppInterface_t *pFindPppInterface(void *pDevHandle) +{ + uPortPppInterface_t *pPppInterface = NULL; + uLinkedList_t *pList; + + pList = gpPppInterfaceList; + while ((pList != NULL) && (pPppInterface == NULL)) { + if (((uPortPppInterface_t *) pList->p)->pDevHandle == pDevHandle) { + pPppInterface = (uPortPppInterface_t *) pList->p; + } else { + pList = pList->pNext; + } + } + + return pPppInterface; +} + +/** Convert an IP address of ours to ESP-IDF format. + */ +static int32_t convertIpAddress(uSockIpAddress_t *pIn, esp_ip_addr_t *pOut) +{ + int32_t espError = ESP_ERR_INVALID_ARG; + + memset(pOut, 0, sizeof(*pOut)); + switch (pIn->type) { + case U_SOCK_ADDRESS_TYPE_V4: + pOut->type = ESP_IPADDR_TYPE_V4; + pOut->u_addr.ip4.addr = ESP_IP4TOADDR(((pIn->address.ipv4 >> 24) && 0xFF), + ((pIn->address.ipv4 >> 16) && 0xFF), + ((pIn->address.ipv4 >> 8) && 0xFF), + (pIn->address.ipv4 && 0xFF)); + espError = ESP_OK; + break; + case U_SOCK_ADDRESS_TYPE_V6: + pOut->type = ESP_IPADDR_TYPE_V6; + for (size_t x = 0; x < sizeof(pOut->u_addr.ip6.addr) / sizeof(pOut->u_addr.ip6.addr[0]); x++) { + pOut->u_addr.ip6.addr[x] = ESP_IP4TOADDR(((pIn->address.ipv6[x] >> 24) && 0xFF), + ((pIn->address.ipv6[x] >> 16) && 0xFF), + ((pIn->address.ipv6[x] >> 8) && 0xFF), + (pIn->address.ipv6[x] && 0xFF)); + } + espError = ESP_OK; + break; + default: + break; + } + + return espError; +} + +// Switch off DHCP and tell the IP stack what our IP address is. +static esp_err_t setIpAddress(esp_netif_t *pEspNetif, uSockIpAddress_t *pIpAddress) +{ + esp_err_t espError = ESP_ERR_INVALID_ARG; + esp_ip_addr_t espIpAddress = {0}; + esp_netif_ip_info_t ipInfo = {0}; + + switch (pIpAddress->type) { + case U_SOCK_ADDRESS_TYPE_V4: + ipInfo.netmask.addr = ESP_IP4TOADDR(0xFF, 0xFF, 0xFF, 0); + // TODO ipInfo.gw + espError = convertIpAddress(pIpAddress, &espIpAddress); + break; + case U_SOCK_ADDRESS_TYPE_V6: + espError = ESP_ERR_NOT_SUPPORTED; + break; + default: + break; + } + if (espError == ESP_OK) { + memcpy(&ipInfo.ip, &espIpAddress.u_addr.ip4, sizeof(ipInfo.ip)); + espError = esp_netif_dhcpc_stop(pEspNetif); + if (espError == ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { + espError = ESP_OK; + } + if (espError == ESP_OK) { + espError = esp_netif_set_ip_info(pEspNetif, &ipInfo); + } + } + + return espError; +} + +// Set a DNS address. +static esp_err_t setDnsAddress(esp_netif_t *pEspNetif, esp_netif_dns_type_t type, + uSockIpAddress_t *pIpAddress) +{ + esp_err_t espError = ESP_ERR_INVALID_ARG; + esp_netif_dns_info_t dnsInfo = {0}; + + switch (pIpAddress->type) { + case U_SOCK_ADDRESS_TYPE_V4: + espError = convertIpAddress(pIpAddress, &dnsInfo.ip); + break; + case U_SOCK_ADDRESS_TYPE_V6: + espError = ESP_ERR_NOT_SUPPORTED; + break; + default: + break; + } + if (espError == ESP_OK) { + espError = esp_netif_set_dns_info(pEspNetif, type, &dnsInfo); + } + + return espError; +} + +// This function is provided as a callback to the NETIF layer of +// ESP-IDF in the structure esp_netif_driver_ifconfig_t, see +// postAttachStart(). +static esp_err_t espNetifTransmit(void *pHandle, + void *pData, size_t length) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_IGNORED;; + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pHandle; + struct uPortPppInterface_t *pPppInterface = pDriver->pPppInterface; + size_t guard = 0; + size_t sent = 0; + + if ((pPppInterface->pTransmitCallback != NULL) && (pPppInterface->pppRunning)) { + errorCode = 0; + while ((length > 0) && (errorCode >= 0) && (guard < U_PORT_PPP_TX_LOOP_GUARD)) { + errorCode = pPppInterface->pTransmitCallback(pPppInterface->pDevHandle, pData + sent, + length - sent); + if (errorCode > 0) { + length -= errorCode; + sent += errorCode; + } else { + vTaskDelay(U_PORT_PPP_TX_LOOP_DELAY_MS / portTICK_PERIOD_MS); + } + guard++; + } + } + + return (length == 0) ? ESP_OK : (esp_err_t) errorCode; +} + +// This function is provided as a callback to the NETIF layer of +// ESP-IDF in the structure esp_netif_driver_ifconfig_t, see +// postAttachStart(). +static void espNetifFreeRxBuffer(void *pHandle, void *pBuffer) +{ + // Not used + (void) pHandle; + (void) pBuffer; +} + +// This function is provided as a callback to the NETIF layer of +// ESP-IDF in the structure uPortPppNetifDriver_t. +static esp_err_t postAttachStart(esp_netif_t *pEspNetif, void *pArgs) +{ + esp_err_t espError; + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pArgs; + const esp_netif_driver_ifconfig_t driverIfconfig = { + .handle = pDriver, + .driver_free_rx_buffer = espNetifFreeRxBuffer, + .transmit = espNetifTransmit + }; + esp_netif_ppp_config_t pppConfig = {0}; + + pDriver->base.netif = pEspNetif; + + espError = esp_netif_set_driver_config(pEspNetif, &driverIfconfig); + if (espError == ESP_OK) { + // Switch on events so that we can tell when the IP stack + // has finished with the PPP connection + + // This pattern borrowed from + // https://github.com/espressif/esp-protocols/blob/master/components/esp_modem/src/esp_modem_netif.cpp + pppConfig.ppp_phase_event_enabled = true; + pppConfig.ppp_error_event_enabled = false; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + esp_netif_ppp_get_params(pEspNetif, &pppConfig); +#endif // ESP-IDF >= v4.4 + if (!pppConfig.ppp_error_event_enabled) { + pppConfig.ppp_error_event_enabled = true; + espError = esp_netif_ppp_set_params(pEspNetif, &pppConfig); + } + } + + if ((espError == ESP_OK) && (pDriver->pIpAddress != NULL)) { + espError = setIpAddress(pEspNetif, pDriver->pIpAddress); + pDriver->pIpAddress = NULL; // NULL'ed so that we don't think it can be used again + } + + if (espError == ESP_OK) { + if (pDriver->pDnsIpAddressPrimary != NULL) { + espError = setDnsAddress(pEspNetif, ESP_NETIF_DNS_MAIN, pDriver->pDnsIpAddressPrimary); + pDriver->pDnsIpAddressPrimary = NULL; // NULL'ed so that we don't think it can be used again + } else { + uSockAddress_t address; + if (uSockStringToAddress(U_PORT_PPP_DNS_PRIMARY_DEFAULT_STR, &address) > 0) { + espError = setDnsAddress(pEspNetif, ESP_NETIF_DNS_MAIN, &address.ipAddress); + } + } + } + // Note: secondary DNS address not supported by ESP-IDF for PPP + +#if defined(CONFIG_LWIP_PPP_PAP_SUPPORT) || defined(CONFIG_LWIP_PPP_CHAP_SUPPORT) + if (espError == ESP_OK) { + // Choose at least PAP since otherwise LCP negotiation will fail + // The enumeration used by ESP-IDF matches uPortPppAuthenticationMode_t + esp_netif_auth_type_t authenticationType = (esp_netif_auth_type_t) pDriver->authenticationMode; + if (authenticationType != NETIF_PPP_AUTHTYPE_CHAP) { + authenticationType = NETIF_PPP_AUTHTYPE_PAP; + } + // Set the username/password fields to at least be empty strings + // otherwise the authentication mode will not be accepted + if (pDriver->pUsername == NULL) { + pDriver->pUsername = ""; + } + if (pDriver->pPassword == NULL) { + pDriver->pPassword = ""; + } + espError = esp_netif_ppp_set_auth(pEspNetif, authenticationType, + pDriver->pUsername, pDriver->pPassword); + pDriver->pUsername = NULL; // NULL'ed so that we don't think + pDriver->pPassword = NULL; // they can be used again + } +#endif + + return espError; +} + +// Callback for received data. +static void receiveCallback(void *pDevHandle, const char *pData, + size_t dataSize, void *pCallbackParam) +{ + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pCallbackParam; + esp_netif_t *pEspNetif; + + (void) pDevHandle; + + pEspNetif = pDriver->base.netif; + if (pEspNetif != NULL) { + esp_netif_receive(pEspNetif, (void *) pData, dataSize, NULL); + } +} + +// Callback for IP state change events from the attached IP stack. +static void eventIpChanged(void *pArgs, esp_event_base_t eventBase, + int32_t eventId, void *pEventData) +{ + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pArgs; + struct uPortPppInterface_t *pPppInterface = pDriver->pPppInterface; + + switch (eventId) { + case IP_EVENT_PPP_GOT_IP: + pPppInterface->ipConnected = true; + break; + case IP_EVENT_PPP_LOST_IP: + pPppInterface->ipConnected = false; + break; + default: + break; + } +} + +// Callback for PPP state change events from the attached IP stack. +static void eventPppChanged(void *pArgs, esp_event_base_t eventBase, + int32_t eventId, void *pEventData) +{ + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pArgs; + struct uPortPppInterface_t *pPppInterface = pDriver->pPppInterface; + + uPortLog("U_PORT_PPP: received event %d.\n", eventId); + if ((eventId > NETIF_PPP_ERRORNONE) && (eventId < NETIF_PP_PHASE_OFFSET)) { + // This means that the IP stack is finished with us + uPortSemaphoreGive(pPppInterface->semaphoreExit); + pPppInterface->ipConnected = false; + } +} + +// Detach a PPP interface from the bottom of ESP NETIF. +static void pppDetach(uPortPppInterface_t *pPppInterface) +{ + if ((pPppInterface != NULL) && (pPppInterface->netifDriver.base.netif != NULL)) { + if (pPppInterface->ipConnected) { + esp_netif_action_disconnected(pPppInterface->netifDriver.base.netif, NULL, 0, NULL); + } + esp_netif_action_stop(pPppInterface->netifDriver.base.netif, NULL, 0, NULL); + // Wait for the IP stack to let us go + uPortLog("U_PORT_PPP: waiting to be released.\n"); + uPortSemaphoreTryTake(pPppInterface->semaphoreExit, + U_PORT_PPP_SHUTDOWN_TIMEOUT_SECONDS * 1000); + uPortLog("U_PORT_PPP: released.\n"); + if (pPppInterface->pDisconnectCallback != NULL) { + // Disconnect PPP and, if IP is still connected, also + // get it to try to terminate the PPP link + pPppInterface->pDisconnectCallback(pPppInterface->pDevHandle, + pPppInterface->ipConnected); + } + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, eventIpChanged); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected); + esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, eventPppChanged); + pPppInterface->pppRunning = false; + pPppInterface->ipConnected = false; + esp_netif_destroy(pPppInterface->netifDriver.base.netif); + pPppInterface->netifDriver.base.netif = NULL; + } +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO THIS PORT LAYER + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Initialise the PPP stuff. +int32_t uPortPppPrivateInit() +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + + if (gMutex == NULL) { + errorCode = uPortMutexCreate(&gMutex); + } + + return errorCode; +} + +// Deinitialise the PPP stuff. +void uPortPppPrivateDeinit() +{ + uLinkedList_t *pListNext; + uPortPppInterface_t *pPppInterface; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + while (gpPppInterfaceList != NULL) { + pPppInterface = (uPortPppInterface_t *) gpPppInterfaceList->p; + pListNext = gpPppInterfaceList->pNext; + uLinkedListRemove(&gpPppInterfaceList, pPppInterface); + // Make sure we don't accidentally try to call the + // down callback since the device handle will have + // been destroyed by now + pPppInterface->pDisconnectCallback = NULL; + pppDetach(pPppInterface); + uPortSemaphoreDelete(pPppInterface->semaphoreExit); + uPortFree(pPppInterface); + gpPppInterfaceList = pListNext; + } + + U_PORT_MUTEX_UNLOCK(gMutex); + uPortMutexDelete(gMutex); + gMutex = NULL; + } +} + +#else + +// Initialise the PPP stuff. +int32_t uPortPppPrivateInit() +{ + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +// Deinitialise the PPP stuff. +void uPortPppPrivateDeinit() +{ +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Attach a PPP interface to the bottom of ESP NETIF. +int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface == NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pPppInterface = (uPortPppInterface_t *) pUPortMalloc(sizeof(*pPppInterface)); + if (pPppInterface != NULL) { + memset(pPppInterface, 0, sizeof(*pPppInterface)); + errorCode = uPortSemaphoreCreate(&(pPppInterface->semaphoreExit), 0, 1); + if (errorCode == 0) { + pPppInterface->pDevHandle = pDevHandle; + pPppInterface->pConnectCallback = pConnectCallback; + pPppInterface->pDisconnectCallback = pDisconnectCallback; + pPppInterface->pTransmitCallback = pTransmitCallback; + if (uLinkedListAdd(&gpPppInterfaceList, pPppInterface)) { + // On this platform we don't do anything more + // at this point, everything else is done + // in uPortPppConnect() + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + uPortSemaphoreDelete(pPppInterface->semaphoreExit); + uPortFree(pPppInterface); + } + } else { + uPortFree(pPppInterface); + } + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Connect a PPP interface. +int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + esp_netif_config_t espNetifConfigPpp = ESP_NETIF_DEFAULT_PPP(); + esp_netif_t *pEspNetif = NULL; + size_t guardCount = 0; + + // ESP-IDF can't use a secondary DNS address on a PPP connection + (void) pDnsIpAddressSecondary; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + if ((pUsername == NULL) && (pPassword == NULL)) { + authenticationMode = U_PORT_PPP_AUTHENTICATION_MODE_NONE; + } + if (authenticationMode < U_PORT_PPP_AUTHENTICATION_MODE_MAX_NUM) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pEspNetif = esp_netif_new(&espNetifConfigPpp); + if (pEspNetif != NULL) { + // Connect PPP to ESP-IDF NETIF: this + // will call postAttachStart() which + // will populate + // pPppInterface->netifDriver.base.netif + pPppInterface->netifDriver.base.post_attach = postAttachStart; + pPppInterface->netifDriver.pPppInterface = pPppInterface; + // Note that only the pointers are stored for these parameters, + // the contents are not copied: this is fine since they are + // used by postAttachStart(), which is called by + // esp_netif_action_start(), and that's it + pPppInterface->netifDriver.pIpAddress = pIpAddress; + pPppInterface->netifDriver.pDnsIpAddressPrimary = pDnsIpAddressPrimary; + pPppInterface->netifDriver.pUsername = pUsername; + pPppInterface->netifDriver.pPassword = pPassword; + pPppInterface->netifDriver.authenticationMode = authenticationMode; + errorCode = (int32_t) U_ERROR_COMMON_PLATFORM; + if ((esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, eventPppChanged, + &(pPppInterface->netifDriver)) == ESP_OK) && + (esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, + pEspNetif) == ESP_OK) && + (esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected, + pEspNetif) == ESP_OK) && + (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, eventIpChanged, + &(pPppInterface->netifDriver)) == ESP_OK) && + (esp_netif_attach(pEspNetif, &(pPppInterface->netifDriver)) == ESP_OK)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pPppInterface->pConnectCallback != NULL) { + errorCode = pPppInterface->pConnectCallback(pDevHandle, receiveCallback, + &(pPppInterface->netifDriver), + NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + if (errorCode == 0) { + pPppInterface->pppRunning = true; + esp_netif_action_start(pEspNetif, NULL, 0, NULL); + while (!pPppInterface->ipConnected && (guardCount < 50)) { + // Wait a few seconds for PPP to connect so that + // the user gets a connection the moment we exit + uPortTaskBlock(100); + guardCount++; + } + } + } + if ((errorCode != 0) && (pEspNetif != NULL)) { + // Clean up on error + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, eventIpChanged); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected); + esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, eventPppChanged); + esp_netif_destroy(pEspNetif); + pPppInterface->netifDriver.base.netif = NULL; + } + } + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Reconnect a PPP interface. +int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + esp_netif_t *pEspNetif = NULL; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_PLATFORM; + pEspNetif = pPppInterface->netifDriver.base.netif; + if ((pEspNetif != NULL) && (setIpAddress(pEspNetif, pIpAddress) == ESP_OK)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pPppInterface->pConnectCallback != NULL) { + errorCode = pPppInterface->pConnectCallback(pDevHandle, receiveCallback, + &(pPppInterface->netifDriver), + NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Disconnect a PPP interface. +int32_t uPortPppDisconnect(void *pDevHandle) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + // No different from detach, it's going dowwwwwwn... + pppDetach(pPppInterface); + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Detach a PPP interface from the bottom of ESP NETIF. +int32_t uPortPppDetach(void *pDevHandle) +{ + uPortPppInterface_t *pPppInterface; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + uLinkedListRemove(&gpPppInterfaceList, pPppInterface); + pppDetach(pPppInterface); + uPortSemaphoreDelete(pPppInterface->semaphoreExit); + uPortFree(pPppInterface); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// End of file diff --git a/port/platform/esp-idf/src/u_port_ppp_private.h b/port/platform/esp-idf/src/u_port_ppp_private.h new file mode 100644 index 000000000..074222b82 --- /dev/null +++ b/port/platform/esp-idf/src/u_port_ppp_private.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +#ifndef _U_PORT_PPP_PRIVATE_H_ +#define _U_PORT_PPP_PRIVATE_H_ + +/** @file + * @brief Stuff private to the PPP part of the ESP32 porting layer. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Initialise the PPP stuff. + * + * @return zero on success else negative error code. + */ +int32_t uPortPppPrivateInit(); + +/** Deinitialise the PPP stuff. + */ +void uPortPppPrivateDeinit(); + +#ifdef __cplusplus +} +#endif + +#endif // _U_PORT_PPP_PRIVATE_H_ + +// End of file diff --git a/port/platform/esp-idf/test/u_espidf_ppp_test.c b/port/platform/esp-idf/test/u_espidf_ppp_test.c new file mode 100644 index 000000000..1cd89ef88 --- /dev/null +++ b/port/platform/esp-idf/test/u_espidf_ppp_test.c @@ -0,0 +1,553 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/* Only #includes of u_* and the C standard library are allowed here, + * no platform stuff and no OS stuff. Anything required from + * the platform/OS must be brought in through u_port* to maintain + * portability. + */ + +/** @file + * @brief Tests that use ESP-IDF's native sockets API to test the + * PPP-level integration of ubxlib as a transport of LWIP. These tests + * should pass on ESP32 when there is a cellular module connected. + * These tests use the network API and the test configuration information + * from the network API and sockets API to provide the communication path. + * + * The tests are only compiled if CONFIG_LWIP_PPP_SUPPORT and + * U_CFG_PPP_ENABLE are defined. + * + * IMPORTANT: see notes in u_cfg_test_platform_specific.h for the + * naming rules that must be followed when using the U_PORT_TEST_FUNCTION() + * macro. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +#include "stddef.h" // NULL, size_t etc. +#include "stdlib.h" // rand() +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // strlen() +#include "unistd.h" +#include "sys/socket.h" +#include "netdb.h" // struct addrinfo +#include "arpa/inet.h" +#include "errno.h" + +#include "u_cfg_sw.h" +#include "u_cfg_app_platform_specific.h" +#include "u_cfg_test_platform_specific.h" +#include "u_cfg_os_platform_specific.h" + +#include "u_error_common.h" + +#include "u_port_clib_platform_specific.h" /* struct timeval in some cases. */ +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_debug.h" +#include "u_port_event_queue.h" + +#include "u_test_util_resource_check.h" + +#include "u_network.h" // In order to provide a comms +#include "u_network_test_shared_cfg.h" // path for the socket + +#include "u_sock_test_shared_cfg.h" + +// These for uCellPrivateModule_t +#include "u_at_client.h" +#include "u_cell_module_type.h" +#include "u_cell.h" +#include "u_cell_file.h" +#include "u_cell_net.h" // Required by u_cell_private.h +#include "u_cell_private.h" // So that we can get at some innards + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_ESPIDF_SOCK_TEST: " + +/** Print a whole line, with terminator, prefixed for this test file. + */ +#define U_TEST_PRINT_LINE(format, ...) uPortLog(U_TEST_PREFIX format "\n", ##__VA_ARGS__) + +#ifndef U_ESPIDF_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES +/** The stack size to use for the asynchronous receive task. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES 2560 +#endif + +#ifndef U_ESPIDF_PPP_TEST_RECEIVE_TASK_PRIORITY +/** The priority to use for the asynchronous receive task. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_PRIORITY (U_CFG_OS_PRIORITY_MIN + 5) +#endif + +#ifndef U_ESPIDF_PPP_TEST_RECEIVE_TASK_RELAX_MS +/** How long the receive task should relax for between receive + * attempts. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_RELAX_MS 10 +#endif + +#ifndef U_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS +/** How long to allow for the the receive task to exit; + * should be quite a lot longer than + * #U_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS 100 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** Struct to pass to rxTask(). + */ +typedef struct { + int32_t sock; + char *pBuffer; + size_t bufferLength; + size_t bytesToSend; + size_t bytesReceived; + size_t packetsReceived; + uPortTaskHandle_t taskHandle; + bool asyncExit; +} uEspidfPppSockTestConfig_t; + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/** Some data to exchange with an echo server. + */ +static const char gSendData[] = "_____0000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____2000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789"; + +/** Data structure passed around during aynchronous receive. + */ +//lint -esym(785, gTestConfig) Suppress too few initialisers +static uEspidfPppSockTestConfig_t gTestConfig = {.taskHandle = NULL}; + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Do this before every test to ensure there is a usable network. +static uNetworkTestList_t *pStdPreamble() +{ + uNetworkTestList_t *pList; + + U_PORT_TEST_ASSERT(uPortInit() == 0); + U_PORT_TEST_ASSERT(uDeviceInit() == 0); + + // Add the device for each network configuration + // if not already added + pList = pUNetworkTestListAlloc(uNetworkTestHasPpp); + if (pList == NULL) { + U_TEST_PRINT_LINE("*** WARNING *** nothing to do."); + } + // Open the devices that are not already open + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle == NULL) { + U_TEST_PRINT_LINE("adding device %s for network %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType], + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uDeviceOpen(pTmp->pDeviceCfg, pTmp->pDevHandle) == 0); + } + } + + // Bring up each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("bringing up %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceUp(*pTmp->pDevHandle, + pTmp->networkType, + pTmp->pNetworkCfg) == 0); + } + + return pList; +} + +// Receive data echoed back to us over a socket. +static void rxTask(void *pParameter) +{ + int32_t sizeBytes; + uEspidfPppSockTestConfig_t *pTestConfig = (uEspidfPppSockTestConfig_t *) pParameter; + + U_TEST_PRINT_LINE("rxTask receiving on socket %d.", pTestConfig->sock); + // Read from the socket until there's nothing left to read + //lint -e{776} Suppress possible truncation of addition + do { + sizeBytes = recv(pTestConfig->sock, + pTestConfig->pBuffer + + pTestConfig->bytesReceived, + pTestConfig->bytesToSend - + pTestConfig->bytesReceived, 0); + if (sizeBytes > 0) { + U_TEST_PRINT_LINE("received %d byte(s) of data @%d ms.", + sizeBytes, (int32_t) uPortGetTickTimeMs()); + pTestConfig->bytesReceived += sizeBytes; + pTestConfig->packetsReceived++; + } else { + uPortTaskBlock(U_ESPIDF_PPP_TEST_RECEIVE_TASK_RELAX_MS); + } + } while ((pTestConfig->bytesReceived < pTestConfig->bytesToSend) && + !pTestConfig->asyncExit); + + U_TEST_PRINT_LINE("rxTask exiting."); + + uPortTaskDelete(NULL); +} + +// Make sure that size is greater than 0 and no more than limit. +static size_t fix(size_t size, size_t limit) +{ + if (size == 0) { + size = limit / 2; // better than 1 + } else if (size > limit) { + size = limit; + } + + return size; +} + +// Send an entire TCP data buffer until done. +static size_t sendTcp(int32_t sock, const char *pData, size_t sizeBytes) +{ + int32_t x; + size_t sentSizeBytes = 0; + int32_t startTimeMs; + + U_TEST_PRINT_LINE("sending %d byte(s) of TCP data...", sizeBytes); + startTimeMs = uPortGetTickTimeMs(); + while ((sentSizeBytes < sizeBytes) && + ((uPortGetTickTimeMs() - startTimeMs) < 10000)) { + x = send(sock, (const void *) pData, sizeBytes - sentSizeBytes, 0); + if (x > 0) { + sentSizeBytes += x; + pData += x; + U_TEST_PRINT_LINE("sent %d byte(s) of TCP data @%d ms.", + sentSizeBytes, (int32_t) uPortGetTickTimeMs()); + } + } + + return sentSizeBytes; +} + +// Check a buffer of what was sent against what was echoed back and +// print out useful info if they differ. +static bool checkAgainstSentData(const char *pDataSent, + size_t dataSentSizeBytes, + const char *pDataReceived, + size_t dataReceivedSizeBytes) +{ + bool success = true; + int32_t x; +#if U_CFG_ENABLE_LOGGING + int32_t y; + int32_t z; +#endif + + if (dataReceivedSizeBytes == dataSentSizeBytes) { + // Run through checking that the characters are the same + for (x = 0; ((*(pDataReceived + x) == *(pDataSent + x))) && + (x < (int32_t) dataSentSizeBytes); x++) { + } + if (x != (int32_t) dataSentSizeBytes) { +#if U_CFG_ENABLE_LOGGING + y = x - 5; + if (y < 0) { + y = 0; + } + z = 10; + if (y + z > (int32_t) dataSentSizeBytes) { + z = ((int32_t) dataSentSizeBytes) - y; + } + U_TEST_PRINT_LINE("difference at character %d (sent \"%*.*s\", received" + " \"%*.*s\").", x + 1, z, z, pDataSent + y, z, z, + pDataReceived + y); +#endif + success = false; + } + } else { + U_TEST_PRINT_LINE("%d byte(s) missing (%d byte(s) received when %d were" + " expected)).", dataSentSizeBytes - dataReceivedSizeBytes, + dataReceivedSizeBytes, dataSentSizeBytes); + success = false; + } + + return success; +} + +// Release OS resources that may have been left hanging +// by a failed test +static void osCleanup() +{ + if (gTestConfig.taskHandle != NULL) { + gTestConfig.asyncExit = true; + uPortTaskBlock(U_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + } + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + */ +U_PORT_TEST_FUNCTION("[espidfSock]", "espidfSockTcp") +{ + const uCellPrivateModule_t *pModule; + uNetworkTestList_t *pList; + int32_t resourceCount; + char hostIp[] = U_SOCK_TEST_ECHO_TCP_SERVER_IP_ADDRESS; + struct sockaddr_in destinationAddress; + int32_t sock; + int32_t errorCode; + int32_t x; + size_t sizeBytes = 0; + size_t offset; + int32_t startTimeMs; + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial resource count + uPortDeinit(); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + // Do the standard preamble to make sure there is + // a network underneath us + pList = pStdPreamble(); + + // Repeat for all bearers that have a supported PPP interface + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + pModule = NULL; + if (pTmp->pDeviceCfg->deviceType == U_DEVICE_TYPE_CELL) { + pModule = pUCellPrivateGetModule(*pTmp->pDevHandle); + } + if ((pModule == NULL) || + U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + + U_TEST_PRINT_LINE("doing async TCP test on %s.", + gpUNetworkTestTypeName[pTmp->networkType]); + osCleanup(); + + inet_pton(AF_INET, hostIp, &destinationAddress.sin_addr); + destinationAddress.sin_family = AF_INET; + destinationAddress.sin_port = htons(U_SOCK_TEST_ECHO_TCP_SERVER_PORT); + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + U_TEST_PRINT_LINE("opening socket() to %s:%d returned %d (errno %d).", + hostIp, U_SOCK_TEST_ECHO_TCP_SERVER_PORT, sock, errno); + U_PORT_TEST_ASSERT(sock >= 0); + + // Set socket to be non-blocking for our asynchronous receive + x = fcntl(sock, F_GETFL, 0); + U_PORT_TEST_ASSERT(x >= 0); + x &= ~O_NONBLOCK; + U_PORT_TEST_ASSERT(fcntl(sock, F_SETFL, x) == 0); + + errorCode = connect(sock, (struct sockaddr *) &destinationAddress, sizeof(destinationAddress)); + U_TEST_PRINT_LINE("connect() returned %d (errno %d).", errorCode, errno); + U_PORT_TEST_ASSERT(errorCode == 0); + + memset(&gTestConfig, 0, sizeof(gTestConfig)); + gTestConfig.sock = sock; + // We're sending all of gSendData except the + // null terminator on the end + gTestConfig.bytesToSend = sizeof(gSendData) - 1; + + // Malloc a buffer to receive TCP packets into + // and put the fill value into it + gTestConfig.bufferLength = gTestConfig.bytesToSend; + gTestConfig.pBuffer = (char *) pUPortMalloc(gTestConfig.bufferLength); + U_PORT_TEST_ASSERT(gTestConfig.pBuffer != NULL); + + // Create a task to receive data + U_PORT_TEST_ASSERT(uPortTaskCreate(rxTask, "rxTask", + U_ESPIDF_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES, + (void *) &gTestConfig, + U_ESPIDF_PPP_TEST_RECEIVE_TASK_PRIORITY, + &gTestConfig.taskHandle) == 0); + + // Throw random sized segments up... + offset = 0; + x = 0; + startTimeMs = uPortGetTickTimeMs(); + while (offset < gTestConfig.bytesToSend) { + sizeBytes = (rand() % U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE) + 1; + sizeBytes = fix(sizeBytes, U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE); + if (sizeBytes < U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE) { + sizeBytes = U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE; + } + if (offset + sizeBytes > gTestConfig.bytesToSend) { + sizeBytes = gTestConfig.bytesToSend - offset; + } + U_TEST_PRINT_LINE("write number %d.", x + 1); + U_PORT_TEST_ASSERT(sendTcp(gTestConfig.sock, + gSendData + offset, + sizeBytes) == sizeBytes); + offset += sizeBytes; + x++; + } + U_TEST_PRINT_LINE("a total of %d byte(s) sent in %d write(s).", offset, x); + + // Give the data time to come back + for (x = 10; (x > 0) && + (gTestConfig.bytesReceived < gTestConfig.bytesToSend); x--) { + uPortTaskBlock(1000); + } + + U_TEST_PRINT_LINE("TCP async receive task got %d segment(s)" + " totalling %d byte(s) and the send/receive" + " process took %d milliseconds.", + gTestConfig.packetsReceived, + gTestConfig.bytesReceived, + uPortGetTickTimeMs() - startTimeMs); + + // Check that we reassembled everything correctly + U_PORT_TEST_ASSERT(checkAgainstSentData(gSendData, + gTestConfig.bytesToSend, + gTestConfig.pBuffer, + gTestConfig.bytesReceived)); + + // Let the receive task close + gTestConfig.asyncExit = true; + uPortTaskBlock(U_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + + shutdown(sock, 0); + close(sock); + + // Free memory + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; + + // Free memory from event queues + uPortEventQueueCleanUp(); + + } else { + U_TEST_PRINT_LINE("*** WARNING *** not testing PPP since device does not support it."); + } + } + + // Remove each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); + } + // Remove each device + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("closing device %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); + U_PORT_TEST_ASSERT(uDeviceClose(*pTmp->pDevHandle, false) == 0); + *pTmp->pDevHandle = NULL; + } + } + uNetworkTestListFree(); + + uDeviceDeinit(); + uPortDeinit(); + + // Check for resource leaks + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); + resourceCount = uTestUtilGetDynamicResourceCount() - resourceCount; + U_TEST_PRINT_LINE("we have leaked %d resources(s).", resourceCount); + U_PORT_TEST_ASSERT(resourceCount <= 0); +} + +/** Clean-up to be run at the end of this round of tests, just + * in case there were test failures which would have resulted + * in the deinitialisation being skipped. + */ +U_PORT_TEST_FUNCTION("[espidfSock]", "espidfSockCleanUp") +{ + osCleanup(); + // The network test configuration is shared between + // the network, sockets, security and location tests + // so must reset the handles here in case the + // tests of one of the other APIs are coming next. + uNetworkTestCleanUp(); + uDeviceDeinit(); + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// End of file diff --git a/port/platform/platformio/inc_src.txt b/port/platform/platformio/inc_src.txt index 6ce69b5ba..455b227fd 100644 --- a/port/platform/platformio/inc_src.txt +++ b/port/platform/platformio/inc_src.txt @@ -80,6 +80,7 @@ port/clib/u_port_clib_mktime64.c port/u_port_timezone.c port/u_port_heap.c port/u_port_resource.c +port/u_port_ppp_default.c port/platform/common/mutex_debug/u_mutex_debug.c gnss/src/lib_mga/u_lib_mga.c common/network/src/u_network.c diff --git a/port/platform/windows/README.md b/port/platform/windows/README.md index 800fd18e7..7c41942c7 100644 --- a/port/platform/windows/README.md +++ b/port/platform/windows/README.md @@ -8,6 +8,4 @@ These directories provide the implementation of the porting layer on Windows. I - [mcu/win32](mcu/win32): contains the configuration and build files for Windows 32-bit. - [u_cfg_os_platform_specific.h](u_cfg_os_platform_specific.h): task priorities and stack sizes for the platform, built into this code. -Windows is a great environment for rapid development and debug visibility but note that both **stack checking** and **heap checking** cannot be done under Windows. - -Note that if you wish to run stuff such as Valgrind, which is only supported on Linux, then you can do so by running [Zephyr on Linux](..\zephyr). \ No newline at end of file +Windows is a great environment for rapid development and debug visibility but note that both **stack checking** and **heap checking** cannot be done under Windows. \ No newline at end of file diff --git a/port/platform/zephyr/CMakeLists.txt b/port/platform/zephyr/CMakeLists.txt index f766fea42..fb7c5d7a0 100644 --- a/port/platform/zephyr/CMakeLists.txt +++ b/port/platform/zephyr/CMakeLists.txt @@ -9,6 +9,8 @@ if(CONFIG_UBXLIB) get_filename_component(UBXLIB_BASE ../../../ ABSOLUTE) set(ENV{UBXLIB_BASE} ${UBXLIB_BASE}) +list(APPEND ZEPHYR_EXTRA_MODULES ${UBXLIB_BASE}/drivers) + # Add environment variables passed-in via U_FLAGS if (DEFINED ENV{U_FLAGS}) separate_arguments(U_FLAGS NATIVE_COMMAND "$ENV{U_FLAGS}") @@ -62,6 +64,7 @@ target_sources(app PRIVATE src/u_port_uart.c src/u_port_i2c.c src/u_port_spi.c + src/u_port_ppp.c src/u_port_private.c ${UBXLIB_BASE}/port/clib/u_port_clib_mktime64.c ) @@ -89,11 +92,14 @@ if (CONFIG_UBXLIB_TEST) target_sources(app PRIVATE ${UBXLIB_TEST_SRC} + test/u_zephyr_ppp_test.c + ${UBXLIB_BASE}/example/sockets/main_ppp_zephyr.c ) zephyr_include_directories( ${UBXLIB_PRIVATE_INC} ${UBXLIB_TEST_INC} + test ) # we need to build https://github.com/ThrowTheSwitch/unity diff --git a/port/platform/zephyr/README.md b/port/platform/zephyr/README.md index bb1bae910..e0da1e510 100644 --- a/port/platform/zephyr/README.md +++ b/port/platform/zephyr/README.md @@ -8,10 +8,12 @@ Note: the directory structure here differs from that in the other platform direc - [app](app): contains the code that runs the test application (both examples and unit tests) on the Zephyr platform. - [cfg](cfg): contains the configuration files for the MCU, the OS, for the application and for testing (mostly which MCU pins are connected to which module pins). +- [dts](dts): device tree bindings needed by this code; for instance, the PPP UART driver, required if you want to integrate at PPP-level with cellular, see below. - [src](src): contains the implementation of the porting layers for Zephyr platform on Nordic chips and on Linux/posix. - [runner](runner): contains the test application configuration and build files for the Nordic MCUs supported on the Zephyr platform. - [runner_linux](runner_linux): contains the test application configuration and build files for Linux/Posix on the Zephyr platform. - [boards](boards): contains custom u-blox boards that are not \[yet\] in the Zephyr repo. +- [test](test): contains tests that use Zephyr application APIs to check out the integration of `ubxlib` into Zephyr, e.g. at PPP level. # SDK Installation (NRF Connect) `ubxlib` is tested with the version of Zephyr that comes with `nRFConnect SDK version 2.5.0` (Zephyr 3.4.99ncs, which includes modifications that Nordic make to Zephyr) which is the recommended version; it is intended to build with all versions nRFConnect SDK from 1.6.1 up til 2.5.0. @@ -118,3 +120,57 @@ If this is a problem, e.g. if your board uses its own naming of the devices you ``` set U_FLAGS=-DMY_FLAG -DU_CFG_APP_PIN_CELL_ENABLE_POWER=-1 ``` + +# PPP-Level Integration With Cellular +PPP support in Zephyr, at least in Zephyr version 3.x, is marked as "experimental": despite the presence of a PAP configuration item, there is no way to configure the authentication mode or the username/password (which are hard-coded in `zephyr/subsys/net/l2/ppp/pap.c`, see function `pap_config_info_add()`) and there doesn't appear to be any code to support CHAP authentication; hence, if your operator requires a user name and password along with the APN, you have no choice but to edit the Zephyr source code. + +Also, the only integration mechanism provided at the bottom of Zephyr PPP is to a \[single\] UART (`zephyr,ppp-uart`); hence the port layer here makes the PPP stream of the \[cellular\] module appear as a UART which must be pulled-in by the application's `.dts` or a `.overlay` file to complete the connection (see below for how to do this). + +And BE WARNED, because of the way the integration has to work, the transmit side of the link is relatively slow, hence the TCP window size has to be relatively small. + +With all of that said, it otherwise appears to work, so if you wish to use native Zephyr applications (e.g. MQTT) with a cellular connection, you will need to (a) define `U_CFG_PPP_ENABLE` when building `ubxlib`, (b) put the settings below in your `prj.conf` file and (c) add a `zephyr,ppp-uart` entry to your `.dts` or `.overlay` file (see further down): + +- `CONFIG_NETWORKING=y` +- `CONFIG_NET_DRIVERS=y` +- `CONFIG_NET_IPV6=n` (the IPCP negotiation phase will fail if IPV6 is set and the module does not have an IPV6 address) +- `CONFIG_NET_IPV4=y` +- `CONFIG_PPP_NET_IF_NO_AUTO_START=y` +- `CONFIG_NET_PPP=y` +- `CONFIG_NET_PPP_ASYNC_UART=y` +- `CONFIG_NET_L2_PPP=y` +- `CONFIG_NET_L2_PPP_PAP=y` +- `CONFIG_NET_L2_PPP_TIMEOUT=10000` +- If your network operator requires a user name and password along with the APN **AND** requires CHAP authentication, then also include `CONFIG_NET_L2_PPP_CHAP=y` and hope it works, +- `CONFIG_NET_PPP_UART_BUF_LEN=512`; 512 being a suggested receive buffer length (two will be required), +- `CONFIG_NET_PPP_ASYNC_UART_TX_BUF_LEN=512`; 512 being a suggested transmit buffer length. + +If you want to use sockets from Zephyr and if you want to use BSD names for stuff (otherwise you will need a liberal sprinkling of a `zsock_` prefix), you might also want to add: + +- `CONFIG_NET_TCP=y` +- `CONFIG_NET_TCP_MAX_SEND_WINDOW_SIZE=256` (since the PPP link is relatively slow, keep the window size small) +- `CONFIG_NET_TCP_MAX_RECV_WINDOW_SIZE=256` +- `CONFIG_NET_SOCKETS=y` +- `CONFIG_NET_SOCKETS_POSIX_NAMES=y` (if you do not set this then you will beed to prefix all sockets calls with `zsock_`, e.g. `zsock_connect()`) + +Depending on how much data you expect to receive, you may want to increase `CONFIG_NET_PPP_RINGBUF_SIZE` from the default of 256 and you may need to [tune the buffer sizes that the network stack inside Zephyr uses](https://docs.zephyrproject.org/latest/connectivity/networking/net_config_guide.html#network-buffer-configuration-options). If you have trouble getting data through then look for TCP errors in the Zephyr logging output like "E: TCP failed to allocate buffer in retransmission" and "E: Data buffer (1034) allocation failed", which indicates that you need bigger buffers. In our Zephyr native sockets test, `zephyrSockTcp()`, where we send 2 kbytes up and back down again, we set `CONFIG_NET_BUF_DATA_SIZE=256`. + +With `U_CFG_PPP_ENABLE` defined `ubxlib` will, internally, create a Zephyr UART device named `u-blox,uart-ppp` and you must instantiate it and connect it to Zephyr PPP's `zephyr,ppp-uart` in the root section of your `.dts` or `.overlay` file with something like: + +``` +/* Required for PPP-level integration with ubxlib to work */ +/ { // The leading / indicates that we're in the root section + // This chooses uart instance 99 to be the zephyr,ppp-uart that ppp.c wants + chosen { + zephyr,ppp-uart = &uart99; + }; + + // This creates instance 99 of a uart that we will give to zephyr,ppp-uart + uart99: uart-ppp@8000 { // The "8000" here is irrelevant but required for Zephyr to work + compatible = "u-blox,uart-ppp"; // The important part: this is an instance of u-blox,uart-ppp + reg = <0x8000 0x100>; // This is irrelevant but required for Zephyr to work + status = "okay"; // Zephyr boiler-plate + }; +}; +``` + +If this is not correct then Zephyr `ppp.c` will fail to compile in `ppp_start()` because `__device_dts_ord_DT_CHOSEN_zephyr_ppp_uart_ORD` is undeclared (i.e. a binding for `zephyr,ppp-uart` has not been found) or your application will fail to link because something like `__device_dts_ord_6` (the driver implementation in [u_port_ppp.c](src/u_port_ppp.c)) has not been found. diff --git a/port/platform/zephyr/dts/bindings/README.md b/port/platform/zephyr/dts/bindings/README.md new file mode 100644 index 000000000..859893820 --- /dev/null +++ b/port/platform/zephyr/dts/bindings/README.md @@ -0,0 +1,8 @@ +# Why This Directory/File Is Here +This binding file is required only for PPP-level integration with Zephyr, which happens if `U_CFG_PPP_ENABLE` is defined when building `ubxlib`. It goes like this: + +- Zephyr's `ppp.c` wants to talk to a Zephyr UART that is named `zephyr,pppuart`. +- The Zephyr port code here implements such a driver, named `u-blox,uart-ppp` (see [u_port_ppp.c](../../src/u_port_ppp.c)). +- Connecting the two can ONLY be done in the application's `.dts` or `.overlay` file. +- Hence the driver must have a binding into the device tree. +- That's what [u-blox,uart-ppp.yaml](u-blox,uart-ppp.yaml) does: it lets the device-tree-creating part of the build process know that `u-blox,uart-ppp` is a UART driver, so that the application `.dts` or `.overlay` file can refer to it, and the macros at the bottom of [u_port_ppp.c](../../src/u_port_ppp.c) can create the implementation of it when that file is compiled. \ No newline at end of file diff --git a/port/platform/zephyr/dts/bindings/u-blox,uart-ppp.yaml b/port/platform/zephyr/dts/bindings/u-blox,uart-ppp.yaml new file mode 100644 index 000000000..86382a7c7 --- /dev/null +++ b/port/platform/zephyr/dts/bindings/u-blox,uart-ppp.yaml @@ -0,0 +1,5 @@ +description: virtual ubxlib UART for PPP connections + +compatible: "u-blox,uart-ppp" + +include: [uart-controller.yaml] \ No newline at end of file diff --git a/port/platform/zephyr/runner/boards/nrf52840dk_nrf52840.overlay b/port/platform/zephyr/runner/boards/nrf52840dk_nrf52840.overlay index 26cbec710..5f5048e4b 100644 --- a/port/platform/zephyr/runner/boards/nrf52840dk_nrf52840.overlay +++ b/port/platform/zephyr/runner/boards/nrf52840dk_nrf52840.overlay @@ -68,4 +68,19 @@ pinctrl-0 = <&spi3_default>; pinctrl-1 = <&spi3_sleep>; pinctrl-names = "default", "sleep"; +}; + +/* Required for PPP-level integration with ubxlib to work */ +/ { // The leading / indicates that we're in the root section + // This chooses uart instance 99 to be the zephyr,ppp-uart that ppp.c wants + chosen { + zephyr,ppp-uart = &uart99; + }; + + // This creates instance 99 of a uart that we will give to zephyr,ppp-uart + uart99: uart-ppp@8000 { // The "8000" here is irrelevant but required for Zephyr to work + compatible = "u-blox,uart-ppp"; // The important part: this is an instance of u-blox,uart-ppp + reg = <0x8000 0x100>; // This is irrelevant but required for Zephyr to work + status = "okay"; // Zephyr boiler-plate + }; }; \ No newline at end of file diff --git a/port/platform/zephyr/runner/boards/nrf5340dk_nrf5340_cpuapp.overlay b/port/platform/zephyr/runner/boards/nrf5340dk_nrf5340_cpuapp.overlay index ca6b22b36..9ec1518e5 100644 --- a/port/platform/zephyr/runner/boards/nrf5340dk_nrf5340_cpuapp.overlay +++ b/port/platform/zephyr/runner/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -70,3 +70,18 @@ pinctrl-1 = <&spi2_sleep>; pinctrl-names = "default", "sleep"; }; + +/* Required for PPP-level integration with ubxlib to work */ +/ { // The leading / indicates that we're in the root section + // This chooses uart instance 99 to be the zephyr,ppp-uart that ppp.c wants + chosen { + zephyr,ppp-uart = &uart99; + }; + + // This creates instance 99 of a uart that we will give to zephyr,ppp-uart + uart99: uart-ppp@8000 { // The "8000" here is irrelevant but required for Zephyr to work + compatible = "u-blox,uart-ppp"; // The important part: this is an instance of u-blox,uart-ppp + reg = <0x8000 0x100>; // This is irrelevant but required for Zephyr to work + status = "okay"; // Zephyr boiler-plate + }; +}; \ No newline at end of file diff --git a/port/platform/zephyr/runner/prj.conf b/port/platform/zephyr/runner/prj.conf index 5e53a43c7..49f5c876e 100644 --- a/port/platform/zephyr/runner/prj.conf +++ b/port/platform/zephyr/runner/prj.conf @@ -65,7 +65,6 @@ CONFIG_USE_SEGGER_RTT=y CONFIG_RTT_CONSOLE=y CONFIG_UART_CONSOLE=n CONFIG_LOG_BACKEND_UART=n -CONFIG_LOG_BACKEND_SHOW_COLOR=y # Default RTT buffer is 1024 - we need a little more for our tests CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=2048 # End of SEGGER RTT @@ -77,4 +76,38 @@ CONFIG_EXTRA_EXCEPTION_INFO=y CONFIG_I2C=y # Enable SPI -CONFIG_SPI=y \ No newline at end of file +CONFIG_SPI=y + +# Enable PPP-level integration with the bottom of the Zephyr IP stack +CONFIG_NETWORKING=y +CONFIG_NET_DRIVERS=y +CONFIG_NET_IPV4=y +# The cellular module will reject IPCPV6 negotiation (since it does not have an IPV6 addres) so switch it off +CONFIG_NET_IPV6=n +CONFIG_PPP_NET_IF_NO_AUTO_START=y +CONFIG_NET_PPP=y +CONFIG_NET_L2_PPP=y +CONFIG_NET_L2_PPP_PAP=y +# Without this LCP negotiation falls at the first hurdle (the value is in milliseconds) +CONFIG_NET_L2_PPP_TIMEOUT=10000 +CONFIG_NET_PPP_ASYNC_UART=y +CONFIG_UART_ASYNC_API=y +CONFIG_NET_PPP_UART_BUF_LEN=512 +CONFIG_NET_PPP_ASYNC_UART_TX_BUF_LEN=512 +# We pass a reasonable amount of data around during testing, hence need to increase this +CONFIG_NET_PPP_RINGBUF_SIZE=1024 + +# For the Zephyr native sockets test and example +CONFIG_NET_TCP=y +# Since the PPP link is relatively slow, keep the window size small +CONFIG_NET_TCP_MAX_SEND_WINDOW_SIZE=256 +CONFIG_NET_TCP_MAX_RECV_WINDOW_SIZE=256 +CONFIG_NET_SOCKETS=y + +# Sufficient TCP buffers to run the amount of data we send and receive in the native zephyr sockets test, zephyrSockTcp +CONFIG_NET_BUF_DATA_SIZE=256 + +# Uncomment these for detailed PPP debug +#CONFIG_NET_LOG=y +#CONFIG_NET_L2_PPP_LOG_LEVEL_DBG=y +#CONFIG_NET_PPP_LOG_LEVEL_DBG=y \ No newline at end of file diff --git a/port/platform/zephyr/src/u_port.c b/port/platform/zephyr/src/u_port.c index c8acc7403..8187ee271 100644 --- a/port/platform/zephyr/src/u_port.c +++ b/port/platform/zephyr/src/u_port.c @@ -40,6 +40,7 @@ #include "u_port_gpio.h" #include "u_port_uart.h" #include "u_port_event_queue_private.h" +#include "u_port_ppp_private.h" #include "u_port_private.h" #include @@ -112,12 +113,16 @@ int32_t uPortInit() if (errorCode == 0) { errorCode = uPortPrivateInit(); } + if (errorCode == 0) { + errorCode = uPortPppPrivateInit(); + } return errorCode; } // Deinitialise the porting layer. void uPortDeinit() { + uPortPppPrivateDeinit(); uPortPrivateDeinit(); uPortUartDeinit(); uPortEventQueuePrivateDeinit(); diff --git a/port/platform/zephyr/src/u_port_ppp.c b/port/platform/zephyr/src/u_port_ppp.c new file mode 100644 index 000000000..c9e4086cd --- /dev/null +++ b/port/platform/zephyr/src/u_port_ppp.c @@ -0,0 +1,1032 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/** @file + * @brief This file makes a connection from the bottom of the Zephyr IP + * stack to a PPP interface inside ubxlib. Such a PPP interface is + * provided by a cellular module. + * + * It is only compiled if CONFIG_NET_PPP and CONFIG_NET_L2_PPP are + * switched-on in your Zephyr prj.conf file. + * + * Implementation note: the Zephyr PPP driver is designed to talk + * to a UART, one specifically named "zephyr,ppp-uart" in the device + * tree. By default it writes to this UART byte-by-byte, rather than + * buffer-wise, which would be extremely inefficient since we are + * running a CMUX underneath, as every character would be wrapped in + * a CMUX frame. Hence this code expects CONFIG_NET_PPP_ASYNC_UART + * to be defined, which causes the PPP driver to give us buffer fulls + * of data to transmit. And though the interface is called "asynchronous", + * it really isn't at all since the event callback simply calls back, + * from Zephyr ppp.c, into this code. To make it behave aaynchronously, + * and to provide buffering of the TX segments being sent, an event + * queue is used on the transmit side, which makes the whole thing + * somewhat slow I'm afraid. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // memset() + +#include "u_cfg_os_platform_specific.h" // U_CFG_OS_PRIORITY_MAX +#include "u_cfg_sw.h" +#include "u_error_common.h" + +#include "u_sock.h" // uSockStringToAddress() + +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_event_queue.h" +#include "u_port_ppp.h" +#include "u_port_debug.h" + +#include + +#ifdef CONFIG_NET_PPP +# if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(3,1,0) +# include +# include +# include +# include +# include +# else +# include +# include +# include +# include +# include +# endif +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_PORT_PPP_UART_DEVICE_ID +/** The name that the virtual UART device which represents the + * PPP interface will appear as; this is the name which must + * be select as the Zephry PPP UART in the application's device + * tree for a PPP interface to work with Zephyr. + * + * IMPORTANT: MUST BE OF THE FORM pppuartx, where x is a decimal + * number + */ +# define U_PORT_PPP_UART_DEVICE_ID uartppp +#endif + +#ifndef U_PORT_PPP_CONNECT_TIMEOUT_SECONDS +/** How long to wait for PPP to connect. + */ +# define U_PORT_PPP_CONNECT_TIMEOUT_SECONDS 15 +#endif + +#ifndef U_PORT_PPP_DISCONNECT_TIMEOUT_SECONDS +/** How long to wait for PPP to disconnect. + */ +# define U_PORT_PPP_DISCONNECT_TIMEOUT_SECONDS 10 +#endif + +#ifndef U_PORT_PPP_TX_LOOP_GUARD +/** How many times around the transmit loop to allow if stuff + * won't send. + */ +# define U_PORT_PPP_TX_LOOP_GUARD 100 +#endif + +#ifndef U_PORT_PPP_TX_LOOP_DELAY_MS +/** How long to wait between transmit attempts in milliseconds + * when the data to transmit won't go all at once. + */ +# define U_PORT_PPP_TX_LOOP_DELAY_MS 10 +#endif + +#ifndef U_PORT_PPP_TX_TASK_STACK_SIZE_BYTES +/** The stack size for the asynchronous transmit task in bytes. + */ +# define U_PORT_PPP_TX_TASK_STACK_SIZE_BYTES 2048 +#endif + +#ifndef U_PORT_PPP_TX_TASK_PRIORITY +/** The priority of the transmit task: should be relatively + * high (e.g. U_CFG_OS_PRIORITY_MAX - 5, which is the same as + * the AT Client URC task). + */ +# define U_PORT_PPP_TX_TASK_PRIORITY (U_CFG_OS_PRIORITY_MAX - 5) +#endif + +#ifndef U_PORT_PPP_TX_BUFFER_COUNT +/** The number of TX buffers to have queued up. This + * is intended to work with a CONFIG_NET_TCP_MAX_SEND_WINDOW_SIZE/ + * CONFIG_NET_TCP_MAX_RECV_WINDOW_SIZE of 256 given a + * #U_PORT_EVENT_QUEUE_MAX_PARAM_LENGTH_BYTES of 128. + */ +# define U_PORT_PPP_TX_BUFFER_COUNT 4 +#endif + +#ifndef U_PORT_PPP_RX_BUFFER_COUNT +/** The number of RX buffers to have queued up. + */ +# define U_PORT_PPP_RX_BUFFER_COUNT 2 +#endif + +// THERE ARE ADDITIONAL COMPILE-TIME MACROS AT THE END OF THIS FILE + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/** Data provide by the Zephyr PPP driver to be transmitted. + */ +typedef struct { + /* buf is the biggest it can be so that we shuffle stuff through quickly */ + uint8_t buf[U_PORT_EVENT_QUEUE_MAX_PARAM_LENGTH_BYTES - (sizeof(size_t) + sizeof(int32_t))]; + size_t len; + int32_t timeoutMs; +} uPortPppTx_t; + +/** A receive buffer provided by the Zephyr PPP driver. + */ +typedef struct { + uint8_t *pBuf; + size_t len; + const uint8_t *pRead; + uint8_t *pWrite; +} uPortPppRx_t; + +/** Data assocated with a UART that will be provided + * to the Zephyr PPP interface. Normally this would + * form the "dev" context pointer of a device with + * the fixed name "ppp_uart" which PPP will look for. + * However, since there can be only one there is no + * need to do that in our case, we can simply keep + * it local. + */ +typedef struct { + uart_callback_t asyncCallback; + void *pAsyncCallbackParam; + bool rxEnabled; + uPortPppRx_t rxBuffer[U_PORT_PPP_RX_BUFFER_COUNT]; + size_t rxBufferIndexNext; + size_t rxBufferIndexRead; + size_t rxBufferIndexWrite; +} uPortPppUartDriver_t; + +/** Define a PPP interface. + */ +typedef struct uPortPppInterface_t { + void *pDevHandle; + int32_t txQueueHandle; + uPortPppConnectCallback_t *pConnectCallback; + uPortPppDisconnectCallback_t *pDisconnectCallback; + uPortPppTransmitCallback_t *pTransmitCallback; + bool pppRunning; + bool ipConnected; + struct net_if *pNetIf; + struct net_mgmt_event_callback netIfEventCallback; + uPortPppUartDriver_t pppUartDriver; +} uPortPppInterface_t; + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/** A place to hook the PPP interface (in Zephyr there can + * be only one). + */ +static uPortPppInterface_t *gpPppInterface = NULL; + +/** Mutex to protect the linked list of PPP entities. + */ +static uPortMutexHandle_t gMutex = NULL; + +// THERE ARE ADDITIONAL STATIC VARIABLES AT THE END OF THIS FILE + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS: UART API FOR ZEPHYR PPP TO TALK TO + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +# ifdef CONFIG_UART_ASYNC_API + +// Dummy initialisation function for the UART device. +static int uartPppInit(const struct device *dev) +{ + // Don't care about the device data since Zephyr only supports + // a single PPP connection + (void) dev; + + return 0; // Return success +} + +// Send an event to the asynchronous UART event callback up +// in Zephyr PPP. +static void sendEvent(const uPortPppUartDriver_t *pUartDriver, + struct uart_event *pEvent) +{ + if ((pUartDriver != NULL) && (pUartDriver->asyncCallback != NULL)) { + pUartDriver->asyncCallback(NULL, pEvent, pUartDriver->pAsyncCallbackParam); + } +} + +// Set the callback for asynchronous operation of the "UART" that +// Zephyr PPP is talking to; the callback would be called, for +// instance, when TX is complete. +static int uartCallbackSet(const struct device *pDev, + uart_callback_t cb, void *pUserData) +{ + int32_t zephyrErrorCode = -ENODEV; + uPortPppUartDriver_t *pUartDriver; + + // Don't care about the device data since Zephyr only supports + // a single PPP connection + (void) pDev; + + if ((gpPppInterface != NULL) && (gpPppInterface->pppRunning)) { + pUartDriver = &(gpPppInterface->pppUartDriver); + pUartDriver->asyncCallback = cb; + pUartDriver->pAsyncCallbackParam = pUserData; + zephyrErrorCode = 0; + } + + return zephyrErrorCode; +} + +// Asynchronous transmit function for the UART that Zephyr PPP is +// talking to. +static int uartTx(const struct device *pDev, const uint8_t *pBuf, + size_t len, int32_t timeout) +{ + int32_t zephyrErrorCode = -ENODEV; + size_t thisLen; + uPortPppTx_t tx; + + // Don't care about the device data since Zephyr only supports + // a single PPP connection + (void) pDev; + + if ((gpPppInterface != NULL) && (gpPppInterface->txQueueHandle >= 0) && + (gpPppInterface->pppRunning)) { + zephyrErrorCode = 0; + while ((len > 0) && (zephyrErrorCode == 0)) { + thisLen = len; + if (thisLen > sizeof(tx.buf)) { + thisLen = sizeof(tx.buf); + } + memcpy(tx.buf, pBuf, thisLen); + tx.len = thisLen; + tx.timeoutMs = k_ticks_to_ms_floor32(timeout); + // Put the transmit into the queue + if (uPortEventQueueSend(gpPppInterface->txQueueHandle, + &tx, sizeof(tx)) == 0) { + len -= thisLen; + pBuf += thisLen; + } else { + zephyrErrorCode = -EBUSY; + } + } + } + + return zephyrErrorCode; +} + +// Enable asynchronous UART RX into the given [initial] buffer with +// the given timeout. +static int uartRxEnable(const struct device *pDev, uint8_t *pBuf, + size_t len, int32_t timeout) +{ + int32_t zephyrErrorCode = -ENODEV; + uPortPppUartDriver_t *pUartDriver; + uPortPppRx_t *pRxBuffer; + + // Don't care about the device data since Zephyr only supports + // a single PPP connection + (void) pDev; + + // This timeout is intended to be a kind of stutter-reducing affair + // on data reception, waiting for this long for nothing to happen + // since the last byte was received before generaing a UART_RX_RDY + // event. However, the API here doesn't work like that, there can't + // be any hanging around in the receive callback, hence it is ignored + (void) timeout; + + if ((gpPppInterface != NULL) && (gpPppInterface->pppRunning)) { + pUartDriver = &(gpPppInterface->pppUartDriver); + zephyrErrorCode = -EBUSY; + if (!pUartDriver->rxEnabled) { + pUartDriver->rxBufferIndexNext = 0; + pRxBuffer = &(pUartDriver->rxBuffer[pUartDriver->rxBufferIndexNext]); + pUartDriver->rxBufferIndexNext++; + pRxBuffer->pBuf = pBuf; + pRxBuffer->len = len; + pRxBuffer->pRead = pBuf; + pRxBuffer->pWrite = pBuf; + pUartDriver->rxBufferIndexRead = 0; + pUartDriver->rxBufferIndexWrite = 0; + pUartDriver->rxEnabled = true; + zephyrErrorCode = 0; + } + } + + return zephyrErrorCode; +} + +// Set the next buffer for asynchronous UART reception. +static int uartRxBufRsp(const struct device *pDev, uint8_t *pBuf, + size_t len) +{ + int32_t zephyrErrorCode = -EACCES; + uPortPppUartDriver_t *pUartDriver; + size_t nextIndex; + uPortPppRx_t *pRxBuffer; + + // Don't care about the device data since Zephyr only supports + // a single PPP connection + (void) pDev; + + if ((gpPppInterface != NULL) && (gpPppInterface->pppRunning)) { + pUartDriver = &(gpPppInterface->pppUartDriver); + if (pUartDriver->rxEnabled) { + nextIndex = pUartDriver->rxBufferIndexNext; + pRxBuffer = &(pUartDriver->rxBuffer[nextIndex]); + pRxBuffer->pBuf = pBuf; + pRxBuffer->len = len; + pRxBuffer->pRead = pBuf; + pRxBuffer->pWrite = pBuf; + nextIndex++; + if (nextIndex > sizeof(pUartDriver->rxBuffer) / sizeof(pUartDriver->rxBuffer[0])) { + nextIndex = 0; + } + pUartDriver->rxBufferIndexNext = nextIndex; + zephyrErrorCode = 0; + } + } + + return zephyrErrorCode; +} + +// Disable UART receive; as well as being called in +// the shutdown case, Zephyr ppp.c may call this from +// the asyncCallback() in the middle of our rxCallback() +// if the event being sent is UART_RX_RDY and it has +// no buffer space to read the received data into. +static int uartRxDisable(const struct device *pDev) +{ + int32_t zephyrErrorCode = -EFAULT; + uPortPppUartDriver_t *pUartDriver; + uPortPppRx_t *pRxBuffer; + int32_t readIndex; + struct uart_event event; + + // Don't care about the device data since Zephyr only supports + // a single PPP connection + (void) pDev; + + if (gpPppInterface != NULL) { + pUartDriver = &(gpPppInterface->pppUartDriver); + if (pUartDriver->rxEnabled) { + // The guidance in Zephyr ppp.c is that this code + // should generate UART_RX_RDY for any pending received + // data, then UART_RX_BUF_RELEASED for every buffer + // scheduled, follow by a UART_RX_DISABLED event. + // However, if this function is being called because + // ppp.c is out of buffers, generating a UART_RX_RDY + // may cause it to disable RX ('cos it has nowhere + // to put the data), which will call this function, + // etc. So we don't do that. + readIndex = pUartDriver->rxBufferIndexRead; + pRxBuffer = &(pUartDriver->rxBuffer[readIndex]); + while (pRxBuffer->pBuf != NULL) { + // We are done with the current buffer, release it + event.type = UART_RX_BUF_RELEASED; + event.data.rx_buf.buf = pRxBuffer->pBuf; + pRxBuffer->pBuf = NULL; + pRxBuffer->len = 0; + readIndex++; + if (readIndex > sizeof(pUartDriver->rxBuffer) / sizeof(pUartDriver->rxBuffer[0])) { + readIndex = 0; + } + // Put the modified read index back + pUartDriver->rxBufferIndexRead = readIndex; + sendEvent(pUartDriver, &event); + // Move pRxBuffer on + pRxBuffer = &(pUartDriver->rxBuffer[readIndex]); + } + pUartDriver->rxEnabled = false; + // Acknowledge the disablement + event.type = UART_RX_DISABLED; + sendEvent(pUartDriver, &event); + zephyrErrorCode = 0; + } + } + + return zephyrErrorCode; +} + +# endif // #ifdef CONFIG_UART_ASYNC_API + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS: TRANSMIT, RECEIVE AND NETWORK EVENTS + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +// Send UART_TX_DONE or UART_TX_ABORTED. +static bool txEventSend(uPortPppUartDriver_t *pUartDriver, + const uint8_t *pBuf, int32_t len, + int32_t sent) +{ + struct uart_event event; + + event.data.tx.buf = pBuf; + event.data.tx.len = sent; + if ((len == 0) || (sent > 0)) { + // If we had nothing to send or we sent at + // least something then TX was done + event.type = UART_TX_DONE; + } else { + event.type = UART_TX_ABORTED; + } + sendEvent(pUartDriver, &event); + + return true; +} + +// Task to perform asynchronous data transmission, sits on +// the end of the transmit queue. +static void txTask(void *pParameters, size_t paramLength) +{ + uPortPppTx_t *pTransmit = (uPortPppTx_t *) pParameters; + int32_t timeoutMs = pTransmit->timeoutMs; + int32_t len = pTransmit->len; + const uint8_t *pBuf = pTransmit->buf; + uPortPppUartDriver_t *pUartDriver = NULL; + int32_t startTimeMs = k_uptime_get(); + int32_t x = 0; + size_t sent = 0; + + (void) paramLength; + + if ((gpPppInterface != NULL) && (gpPppInterface->pTransmitCallback != NULL) && + (gpPppInterface->pppRunning)) { + pUartDriver = &(gpPppInterface->pppUartDriver); + // Send off the data + while ((len > 0) && (x >= 0) && (k_uptime_get() - startTimeMs < timeoutMs)) { + x = gpPppInterface->pTransmitCallback(gpPppInterface->pDevHandle, pBuf + sent, + len - sent); + if (x > 0) { + len -= x; + sent += x; + } else { + k_msleep(U_PORT_PPP_TX_LOOP_DELAY_MS); + } + } + } + + // Let the asynchronous API callback know what happened + txEventSend(pUartDriver, pBuf, pTransmit->len, sent); +} + +// Callback for received data. +static void rxCallback(void *pDevHandle, const char *pData, + size_t dataSize, void *pCallbackParam) +{ + uPortPppUartDriver_t *pUartDriver; + uPortPppRx_t *pRxBuffer; + int32_t writeIndex; + int32_t readIndex; + size_t thisDataSize; + size_t x; + const uint8_t *pRead; + uint8_t *pWrite; + struct uart_event event; + + (void) pDevHandle; + (void) pCallbackParam; + + if (gpPppInterface != NULL) { + pUartDriver = &(gpPppInterface->pppUartDriver); + if (pUartDriver->rxEnabled) { + writeIndex = pUartDriver->rxBufferIndexWrite; + readIndex = pUartDriver->rxBufferIndexRead; + pRxBuffer = &(pUartDriver->rxBuffer[writeIndex]); + do { + // Write as much as we can to the current buffer + if (pRxBuffer->pBuf != NULL) { + // Sample the read and write pointers + pRead = pRxBuffer->pRead; + pWrite = pRxBuffer->pWrite; + + thisDataSize = dataSize; + x = pRxBuffer->len - (pRead - pWrite); + if (thisDataSize > x) { + thisDataSize = x; + } + memcpy(pWrite, pData, thisDataSize); + dataSize -= thisDataSize; + pWrite += thisDataSize; + // Tell the application that there is data to read + event.type = UART_RX_RDY; + event.data.rx.buf = pRxBuffer->pBuf; + event.data.rx.len = pWrite - pRead; + event.data.rx.offset = pRead - pRxBuffer->pBuf; + sendEvent(pUartDriver, &event); + + // The event above will have caused the application + // to read the received data; either it has read all + // of it or it will have called uartRxDisable() + if (pUartDriver->rxEnabled) { + // If we get here the two pointers must be + // equal so we can switch buffers or not based + // on just one of them. Of course this means that + // the two buffers are, actually, complete pointless, + // we could work with one, but since ppp.c allocates + // two it seems churlish not to use both. + pRead = pWrite; + // Check if we've done with the buffer + if (pRead >= pRxBuffer->pBuf + pRxBuffer->len) { + event.type = UART_RX_BUF_RELEASED; + event.data.rx_buf.buf = pRxBuffer->pBuf; + pRxBuffer->pBuf = NULL; + pRxBuffer->len = 0; + readIndex++; + if (readIndex > sizeof(pUartDriver->rxBuffer) / sizeof(pUartDriver->rxBuffer[0])) { + readIndex = 0; + } + // Put the modified read index back + pUartDriver->rxBufferIndexRead = readIndex; + // Release the buffer + sendEvent(pUartDriver, &event); + writeIndex++; + if (writeIndex > sizeof(pUartDriver->rxBuffer) / sizeof(pUartDriver->rxBuffer[0])) { + writeIndex = 0; + } + // Put the modified write index back + pUartDriver->rxBufferIndexWrite = writeIndex; + // Move pRxBuffer on + pRxBuffer = &(pUartDriver->rxBuffer[writeIndex]); + if (pRxBuffer->pBuf == NULL) { + // Don't have the next buffer yet: ask for one + event.type = UART_RX_BUF_REQUEST; + sendEvent(pUartDriver, &event); + } + } + } + } + // Loop around if receive is still enabled and we have not written + // everything and there is another, empty, free buffer + } while (pUartDriver->rxEnabled && (dataSize > 0) && + (pRxBuffer->pBuf != NULL) && (pRxBuffer->pWrite == pRxBuffer->pBuf)); + } + } +} + +// Callback for network interface events. +void netIfEventCallback(struct net_mgmt_event_callback *pCb, + uint32_t event, + struct net_if *pNetIf) +{ + (void) pCb; + (void) pNetIf; + + if (gpPppInterface != NULL) { + switch (event) { + case NET_EVENT_IPV4_ADDR_ADD: + gpPppInterface->ipConnected = true; + break; + case NET_EVENT_IPV4_ADDR_DEL: + gpPppInterface->ipConnected = false; + break; + default: + break; + } + } +} + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS: MISC + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +// Detach the Zephyr PPP interface. +static void pppDetach(uPortPppInterface_t *pPppInterface) +{ + int32_t startTimeMs; + + if ((pPppInterface != NULL) && (pPppInterface->pNetIf != NULL)) { + // Disconnect PPP + net_if_down(pPppInterface->pNetIf); + net_if_carrier_off(pPppInterface->pNetIf); + startTimeMs = uPortGetTickTimeMs(); + while ((gpPppInterface->ipConnected) && + (uPortGetTickTimeMs() - startTimeMs < U_PORT_PPP_DISCONNECT_TIMEOUT_SECONDS * 1000)) { + uPortTaskBlock(250); + } + pPppInterface->ipConnected = false; + net_mgmt_del_event_callback(&gpPppInterface->netIfEventCallback); + pPppInterface->pNetIf = NULL; + if (pPppInterface->pDisconnectCallback != NULL) { + // Disconnect the channel + pPppInterface->pDisconnectCallback(pPppInterface->pDevHandle, false); + } + pPppInterface->pppRunning = false; + uPortLog("U_PORT_PPP: disconnected.\n"); + } +} + +// Free all the memory of a PPP interface, and the interface itself. +static void freeInterface() +{ + if (gpPppInterface != NULL) { + if (gpPppInterface->txQueueHandle >= 0) { + uPortEventQueueClose(gpPppInterface->txQueueHandle); + } + uPortFree(gpPppInterface); + gpPppInterface = NULL; + } +} + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO THIS PORT LAYER + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +// Initialise the PPP stuff. +int32_t uPortPppPrivateInit() +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + + if (gMutex == NULL) { + errorCode = uPortMutexCreate(&gMutex); + } + + return errorCode; +} + +// Deinitialise the PPP stuff. +void uPortPppPrivateDeinit() +{ + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + if (gpPppInterface != NULL) { + // Make sure we don't accidentally try to call the + // down callback since the device handle will have + // been destroyed by now + gpPppInterface->pDisconnectCallback = NULL; + pppDetach(gpPppInterface); + freeInterface(); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + uPortMutexDelete(gMutex); + gMutex = NULL; + } +} + +#else + +// Initialise the PPP stuff. +int32_t uPortPppPrivateInit() +{ + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +// Deinitialise the PPP stuff. +void uPortPppPrivateDeinit() +{ +} + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +// Attach a PPP interface to the bottom of the Zephyr IP stack. +int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + + (void) pDevHandle; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (gpPppInterface == NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + gpPppInterface = (uPortPppInterface_t *) pUPortMalloc(sizeof(*gpPppInterface)); + if (gpPppInterface != NULL) { + memset(gpPppInterface, 0, sizeof(*gpPppInterface)); + gpPppInterface->txQueueHandle = -1; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pTransmitCallback != NULL) { + // We use an event queue to perform the asynchronous transmit + errorCode = uPortEventQueueOpen(txTask, "pppTxTask", + sizeof(uPortPppTx_t), + U_PORT_PPP_TX_TASK_STACK_SIZE_BYTES, + U_PORT_PPP_TX_TASK_PRIORITY, + U_PORT_PPP_TX_BUFFER_COUNT); + gpPppInterface->txQueueHandle = errorCode; + } + if (errorCode >= 0) { + gpPppInterface->pDevHandle = pDevHandle; + gpPppInterface->pConnectCallback = pConnectCallback; + gpPppInterface->pDisconnectCallback = pDisconnectCallback; + gpPppInterface->pTransmitCallback = pTransmitCallback; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + // Clean up on error + freeInterface(); + } + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Connect a PPP interface. +int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + struct net_if *pNetIf; + int32_t startTimeMs; + + // Note: Zephyr does not (as of version 3.5 at least) support + // entering a user name and password, and probably doesn't + // support CHAP authentication at all. However, it is often the + // case that networks, despite indicating that a user name and + // password are required, don't care either, hence we do not + // reject a user name and password entered here, we just let + // PPP try + + (void) pUsername; + (void) pPassword; + (void) authenticationMode; + + // PPP negotiation will set these + (void) pIpAddress; + (void) pDnsIpAddressPrimary; + (void) pDnsIpAddressSecondary; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + if (gpPppInterface != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + // Get hold of the PPP network interface + pNetIf = net_if_get_first_by_type(&NET_L2_GET_NAME(PPP)); + if (pNetIf != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (gpPppInterface->pConnectCallback != NULL) { + errorCode = gpPppInterface->pConnectCallback(pDevHandle, rxCallback, + NULL, NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + if (errorCode == 0) { + gpPppInterface->pppRunning = true; + errorCode = (int32_t) U_ERROR_COMMON_PLATFORM; + net_mgmt_init_event_callback(&gpPppInterface->netIfEventCallback, + netIfEventCallback, + // The casting here is to prevent CodeChecker thinking + // that the two values are the same; not sure why it, + // does, 'cos they ain't: 0xe0040001 and 0xe0040002 + ((uint32_t) NET_EVENT_IPV4_ADDR_ADD) | ((uint32_t) NET_EVENT_IPV4_ADDR_DEL)); + net_mgmt_add_event_callback(&gpPppInterface->netIfEventCallback); + net_if_carrier_on(pNetIf); + if (net_if_up(pNetIf) == 0) { + // Wait for netIfEventCallback to be called back + // with the event NET_EVENT_IPV4_ADDR_ADD; it + // will populate gpPppInterface->pNetIf + startTimeMs = uPortGetTickTimeMs(); + while ((!gpPppInterface->ipConnected) && + (uPortGetTickTimeMs() - startTimeMs < U_PORT_PPP_CONNECT_TIMEOUT_SECONDS * 1000)) { + uPortTaskBlock(250); + } + if (gpPppInterface->ipConnected) { + gpPppInterface->pNetIf = pNetIf; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + uPortLog("U_PORT_PPP: connected.\n"); + } + } + } + if ((errorCode != 0) && (gpPppInterface->pppRunning) && + (gpPppInterface->pDisconnectCallback != NULL)) { + // Clean up on error + gpPppInterface->pDisconnectCallback(gpPppInterface->pDevHandle, false); + } + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Reconnect a PPP interface. +int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + if ((gpPppInterface != NULL) && (gpPppInterface->ipConnected)) { + (void) pIpAddress; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (gpPppInterface->pConnectCallback != NULL) { + errorCode = gpPppInterface->pConnectCallback(pDevHandle, rxCallback, + NULL, NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Disconnect a PPP interface. +int32_t uPortPppDisconnect(void *pDevHandle) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + + (void) pDevHandle; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + if (gpPppInterface != NULL) { + // No different from detach, it's going dowwwwwwn... + pppDetach(gpPppInterface); + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Detach a PPP interface from the bottom of the Zephyr IP stack. +int32_t uPortPppDetach(void *pDevHandle) +{ + (void) pDevHandle; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + if (gpPppInterface != NULL) { + pppDetach(gpPppInterface); + freeInterface(); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * MORE VARIABLES: THOSE RELATED TO THE LINK INTO ZEPHYR PPP + * These are conventionally placed at the end of a Zephyr driver file. + * -------------------------------------------------------------- */ + +#if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +// Zephyr UART driver structure, used because Zephyr PPP wants to +// talk to a UART. We only populate the calls that PPP needs, the +// rest we let the compiler initialise to NULL. +static const struct uart_driver_api gUart = { +# ifdef CONFIG_UART_ASYNC_API + .callback_set = uartCallbackSet, + .tx = uartTx, + // Note tx_abort() not populated since the Zephyr PPP driver + // never calls it and, in any case, there is no easy way + // to abort an asynchronous transmit that is in the queue + .rx_enable = uartRxEnable, + .rx_buf_rsp = uartRxBufRsp, + .rx_disable = uartRxDisable +# endif + // *INDENT-OFF* (otherwise AStyle makes a mess of this) + // Note: poll_in() not populated since the Zephyr PPP driver + // never calls it when running in asynchronous mode, + // poll_out() not populated since we use the asynchronous + // transmit mode in order to get a buffer-full of data to + // send, rather than single bytes at a time + // Note: none of the interrupt driven functions are populated + // since they are not used when the asynchronous API is employed + // *INDENT-ON* +}; + +// This needs to be defined for the DEVICE_DT_INST_DEFINE() +// macro to work. The name we give to the driver in the binding +// file over in the "dts" directory, following Zephyr convention, +// is "u-blox,uart-ppp", but the device tree macros needs any +// punctuation to be replaced with an underscore, hence the name +// becomes "u_blox_uart_ppp" +#define DT_DRV_COMPAT u_blox_uart_ppp + +// Define a UART device with the given device name: this device +// must be instantiated and mapped to the device "zephyr,ppp-uart" +// (that Zephyr ppp.c is looking for) in the application's .dts or +// .overlay file with something like: +// +// / { +// chosen { +// zephyr,ppp-uart = &uart99; +// }; +// uart99: uart-ppp@8000 { +// compatible = "u-blox,uart-ppp"; +// reg = <0x8000 0x100>; +// status = "okay"; +// }; +//}; +// +// Note that the "@8000 and the "reg" line are all irrelevant but +// are required for Zephyr to understand what we want. The only +// thing that really matters is that uartX is an instance of the +// driver "u-blox,uart-ppp" (which is defined as a UART over +// in the binding file "u-blox,uart-ppp.yaml") and that uartX is +// chosen as the zephyr,ppp-uart. + +#define U_PORT_PPP_UART_DEFINE(i) \ +DEVICE_DT_INST_DEFINE(i, \ + uartPppInit, /* Initialisation callback function */ \ + NULL, /* pm_device */ \ + NULL, /* Context: this would be pDev->data if we needed it */ \ + NULL, /* Constant configuration data */ \ + APPLICATION, /* Device initialisation level */ \ + CONFIG_SERIAL_INIT_PRIORITY, /* Initialisation priority */ \ + &gUart); // API jump-table + +DT_INST_FOREACH_STATUS_OKAY(U_PORT_PPP_UART_DEFINE) + +#endif // #if defined(CONFIG_NET_PPP) && defined(U_CFG_PPP_ENABLE) + +// End of file diff --git a/port/platform/zephyr/src/u_port_ppp_private.h b/port/platform/zephyr/src/u_port_ppp_private.h new file mode 100644 index 000000000..b4478bf86 --- /dev/null +++ b/port/platform/zephyr/src/u_port_ppp_private.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +#ifndef _U_PORT_PPP_PRIVATE_H_ +#define _U_PORT_PPP_PRIVATE_H_ + +/** @file + * @brief Stuff private to the PPP part of the Zephyr porting layer. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Initialise the PPP stuff. + * + * @return zero on success else negative error code. + */ +int32_t uPortPppPrivateInit(); + +/** Deinitialise the PPP stuff. + */ +void uPortPppPrivateDeinit(); + +#ifdef __cplusplus +} +#endif + +#endif // _U_PORT_PPP_PRIVATE_H_ + +// End of file diff --git a/port/platform/zephyr/test/u_zephyr_ppp_test.c b/port/platform/zephyr/test/u_zephyr_ppp_test.c new file mode 100644 index 000000000..2ef1abfb0 --- /dev/null +++ b/port/platform/zephyr/test/u_zephyr_ppp_test.c @@ -0,0 +1,549 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/* Only #includes of u_* and the C standard library are allowed here, + * no platform stuff and no OS stuff. Anything required from + * the platform/OS must be brought in through u_port* to maintain + * portability. + */ + +/** @file + * @brief Tests that use Zephyr's native sockets API to test the + * PPP-level integration of ubxlib into Zephyr. These tests should + * pass on Zephyr when there is a cellular module connected. + * These tests use the network API and the test configuration information + * from the network API and sockets API to provide the communication path. + * + * IMPORTANT: see notes in u_cfg_test_platform_specific.h for the + * naming rules that must be followed when using the U_PORT_TEST_FUNCTION() + * macro. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // strlen() +#include "errno.h" + +#include "u_cfg_sw.h" +#include "u_cfg_app_platform_specific.h" +#include "u_cfg_test_platform_specific.h" +#include "u_cfg_os_platform_specific.h" + +#include "u_error_common.h" + +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_debug.h" +#include "u_port_event_queue.h" + +#include "u_test_util_resource_check.h" + +#include "u_network.h" // In order to provide a comms path +#include "u_network_test_shared_cfg.h" + +#include "u_sock_test_shared_cfg.h" + +// These for uCellPrivateModule_t +#include "u_at_client.h" +#include "u_cell_module_type.h" +#include "u_cell.h" +#include "u_cell_file.h" +#include "u_cell_net.h" // Required by u_cell_private.h +#include "u_cell_private.h" // So that we can get at some innards + +#include + +#if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(3,1,0) +#include +#include +#include +#else +#include +#include +#include +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_ZEPHYR_SOCK_TEST: " + +/** Print a whole line, with terminator, prefixed for this test file. + */ +#define U_TEST_PRINT_LINE(format, ...) uPortLog(U_TEST_PREFIX format "\n", ##__VA_ARGS__) + +#ifndef U_ZEPHYR_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES +/** The stack size to use for the asynchronous receive task. + */ +# define U_ZEPHYR_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES 2560 +#endif + +#ifndef U_ZEPHYR_PPP_TEST_RECEIVE_TASK_PRIORITY +/** The priority to use for the asynchronous receive task. + */ +# define U_ZEPHYR_PPP_TEST_RECEIVE_TASK_PRIORITY (U_CFG_OS_PRIORITY_MIN + 5) +#endif + +#ifndef U_ZEPHYR_PPP_TEST_RECEIVE_TASK_RELAX_MS +/** How long the receive task should relax for between receive + * attempts. + */ +# define U_ZEPHYR_PPP_TEST_RECEIVE_TASK_RELAX_MS 10 +#endif + +#ifndef U_ZEPHYR_PPP_TEST_RECEIVE_TASK_EXIT_MS +/** How long to allow for the the receive task to exit; + * should be quite a lot longer than + * #U_ZEPHYR_PPP_TEST_RECEIVE_TASK_EXIT_MS. + */ +# define U_ZEPHYR_PPP_TEST_RECEIVE_TASK_EXIT_MS 100 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** Struct to pass to rxTask(). + */ +typedef struct { + int32_t sock; + char *pBuffer; + size_t bufferLength; + size_t bytesToSend; + size_t bytesReceived; + size_t packetsReceived; + uPortTaskHandle_t taskHandle; + bool asyncExit; +} uZephyrPppSockTestConfig_t; + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/** Some data to exchange with an echo server. + */ +static const char gSendData[] = "_____0000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____2000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789"; + +/** Data structure passed around during aynchronous receive. + */ +//lint -esym(785, gTestConfig) Suppress too few initialisers +static uZephyrPppSockTestConfig_t gTestConfig = {.taskHandle = NULL}; + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Do this before every test to ensure there is a usable network. +static uNetworkTestList_t *pStdPreamble() +{ + uNetworkTestList_t *pList; + + U_PORT_TEST_ASSERT(uPortInit() == 0); + U_PORT_TEST_ASSERT(uDeviceInit() == 0); + + // Add the device for each network configuration + // if not already added + pList = pUNetworkTestListAlloc(uNetworkTestHasSock); + if (pList == NULL) { + U_TEST_PRINT_LINE("*** WARNING *** nothing to do."); + } + // Open the devices that are not already open + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle == NULL) { + U_TEST_PRINT_LINE("adding device %s for network %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType], + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uDeviceOpen(pTmp->pDeviceCfg, pTmp->pDevHandle) == 0); + } + } + + // Bring up each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("bringing up %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceUp(*pTmp->pDevHandle, + pTmp->networkType, + pTmp->pNetworkCfg) == 0); + } + + return pList; +} + +// Receive data echoed back to us over a socket. +static void rxTask(void *pParameter) +{ + int32_t sizeBytes; + uZephyrPppSockTestConfig_t *pTestConfig = (uZephyrPppSockTestConfig_t *) pParameter; + + U_TEST_PRINT_LINE("rxTask receiving on socket %d.", pTestConfig->sock); + // Read from the socket until there's nothing left to read + //lint -e{776} Suppress possible truncation of addition + do { + sizeBytes = zsock_recv(pTestConfig->sock, + pTestConfig->pBuffer + + pTestConfig->bytesReceived, + pTestConfig->bytesToSend - + pTestConfig->bytesReceived, 0); + if (sizeBytes > 0) { + U_TEST_PRINT_LINE("received %d byte(s) of data @%d ms.", + sizeBytes, (int32_t) uPortGetTickTimeMs()); + pTestConfig->bytesReceived += sizeBytes; + pTestConfig->packetsReceived++; + } else { + uPortTaskBlock(U_ZEPHYR_PPP_TEST_RECEIVE_TASK_RELAX_MS); + } + } while ((pTestConfig->bytesReceived < pTestConfig->bytesToSend) && + !pTestConfig->asyncExit); + + U_TEST_PRINT_LINE("rxTask exiting."); + + uPortTaskDelete(NULL); +} + +// Make sure that size is greater than 0 and no more than limit. +static size_t fix(size_t size, size_t limit) +{ + if (size == 0) { + size = limit / 2; // better than 1 + } else if (size > limit) { + size = limit; + } + + return size; +} + +// Send an entire TCP data buffer until done. +static size_t sendTcp(int32_t sock, const char *pData, size_t sizeBytes) +{ + int32_t x; + size_t sentSizeBytes = 0; + int32_t startTimeMs; + + U_TEST_PRINT_LINE("sending %d byte(s) of TCP data...", sizeBytes); + startTimeMs = uPortGetTickTimeMs(); + while ((sentSizeBytes < sizeBytes) && + ((uPortGetTickTimeMs() - startTimeMs) < 10000)) { + x = zsock_send(sock, (const void *) pData, sizeBytes - sentSizeBytes, 0); + if (x > 0) { + sentSizeBytes += x; + pData += x; + U_TEST_PRINT_LINE("sent %d byte(s) of TCP data @%d ms.", + sentSizeBytes, (int32_t) uPortGetTickTimeMs()); + } + } + + return sentSizeBytes; +} + +// Check a buffer of what was sent against what was echoed back and +// print out useful info if they differ. +static bool checkAgainstSentData(const char *pDataSent, + size_t dataSentSizeBytes, + const char *pDataReceived, + size_t dataReceivedSizeBytes) +{ + bool success = true; + int32_t x; +#if U_CFG_ENABLE_LOGGING + int32_t y; + int32_t z; +#endif + + if (dataReceivedSizeBytes == dataSentSizeBytes) { + // Run through checking that the characters are the same + for (x = 0; ((*(pDataReceived + x) == *(pDataSent + x))) && + (x < (int32_t) dataSentSizeBytes); x++) { + } + if (x != (int32_t) dataSentSizeBytes) { +#if U_CFG_ENABLE_LOGGING + y = x - 5; + if (y < 0) { + y = 0; + } + z = 10; + if (y + z > (int32_t) dataSentSizeBytes) { + z = ((int32_t) dataSentSizeBytes) - y; + } + U_TEST_PRINT_LINE("difference at character %d (sent \"%*.*s\", received" + " \"%*.*s\").", x + 1, z, z, pDataSent + y, z, z, + pDataReceived + y); +#endif + success = false; + } + } else { + U_TEST_PRINT_LINE("%d byte(s) missing (%d byte(s) received when %d were" + " expected)).", dataSentSizeBytes - dataReceivedSizeBytes, + dataReceivedSizeBytes, dataSentSizeBytes); + success = false; + } + + return success; +} + +// Release OS resources that may have been left hanging +// by a failed test +static void osCleanup() +{ + if (gTestConfig.taskHandle != NULL) { + gTestConfig.asyncExit = true; + uPortTaskBlock(U_ZEPHYR_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + } + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + */ +U_PORT_TEST_FUNCTION("[zephyrSock]", "zephyrSockTcp") +{ + const uCellPrivateModule_t *pModule; + uNetworkTestList_t *pList; + int32_t resourceCount; + char hostIp[] = U_SOCK_TEST_ECHO_TCP_SERVER_IP_ADDRESS; + struct sockaddr_in destinationAddress; + int32_t sock; + int32_t errorCode; + int32_t x; + size_t sizeBytes = 0; + size_t offset; + int32_t startTimeMs; + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial heap size + uPortDeinit(); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + // Do the standard preamble to make sure there is + // a network underneath us + pList = pStdPreamble(); + + // Repeat for all bearers + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + pModule = NULL; + if (pTmp->pDeviceCfg->deviceType == U_DEVICE_TYPE_CELL) { + pModule = pUCellPrivateGetModule(*pTmp->pDevHandle); + } + if ((pModule == NULL) || + U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + + U_TEST_PRINT_LINE("doing async TCP test on %s.", + gpUNetworkTestTypeName[pTmp->networkType]); + osCleanup(); + + zsock_inet_pton(AF_INET, hostIp, &destinationAddress.sin_addr); + destinationAddress.sin_family = AF_INET; + destinationAddress.sin_port = htons(U_SOCK_TEST_ECHO_TCP_SERVER_PORT); + + errno = 0; + sock = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + U_TEST_PRINT_LINE("opening socket() to %s:%d returned %d (errno %d).", + hostIp, U_SOCK_TEST_ECHO_TCP_SERVER_PORT, sock, errno); + U_PORT_TEST_ASSERT(sock >= 0); + + // Set socket to be non-blocking for our asynchronous receive + x = zsock_fcntl(sock, F_GETFL, 0); + U_PORT_TEST_ASSERT(x >= 0); + x &= ~O_NONBLOCK; + U_PORT_TEST_ASSERT(zsock_fcntl(sock, F_SETFL, x) == 0); + + errorCode = zsock_connect(sock, (struct sockaddr *) &destinationAddress, + sizeof(destinationAddress)); + U_TEST_PRINT_LINE("connect() returned %d (errno %d).", errorCode, errno); + U_PORT_TEST_ASSERT(errorCode == 0); + + memset(&gTestConfig, 0, sizeof(gTestConfig)); + gTestConfig.sock = sock; + // We're sending all of gSendData except the + // null terminator on the end + gTestConfig.bytesToSend = sizeof(gSendData) - 1; + + // Malloc a buffer to receive TCP packets into + // and put the fill value into it + gTestConfig.bufferLength = gTestConfig.bytesToSend; + gTestConfig.pBuffer = (char *) pUPortMalloc(gTestConfig.bufferLength); + U_PORT_TEST_ASSERT(gTestConfig.pBuffer != NULL); + + // Create a task to receive data + U_PORT_TEST_ASSERT(uPortTaskCreate(rxTask, "rxTask", + U_ZEPHYR_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES, + (void *) &gTestConfig, + U_ZEPHYR_PPP_TEST_RECEIVE_TASK_PRIORITY, + &gTestConfig.taskHandle) == 0); + + // Throw random sized segments up... + offset = 0; + x = 0; + startTimeMs = uPortGetTickTimeMs(); + while (offset < gTestConfig.bytesToSend) { + sizeBytes = (rand() % U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE) + 1; + sizeBytes = fix(sizeBytes, U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE); + if (sizeBytes < U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE) { + sizeBytes = U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE; + } + if (offset + sizeBytes > gTestConfig.bytesToSend) { + sizeBytes = gTestConfig.bytesToSend - offset; + } + U_TEST_PRINT_LINE("write number %d.", x + 1); + U_PORT_TEST_ASSERT(sendTcp(gTestConfig.sock, + gSendData + offset, + sizeBytes) == sizeBytes); + offset += sizeBytes; + x++; + } + U_TEST_PRINT_LINE("a total of %d byte(s) sent in %d write(s).", offset, x); + + // Give the data time to come back + for (x = 20; (x > 0) && + (gTestConfig.bytesReceived < gTestConfig.bytesToSend); x--) { + uPortTaskBlock(1000); + } + + U_TEST_PRINT_LINE("TCP async receive task got %d segment(s)" + " totalling %d byte(s) and the send/receive" + " process took %d milliseconds.", + gTestConfig.packetsReceived, + gTestConfig.bytesReceived, + uPortGetTickTimeMs() - startTimeMs); + + // Check that we reassembled everything correctly + U_PORT_TEST_ASSERT(checkAgainstSentData(gSendData, + gTestConfig.bytesToSend, + gTestConfig.pBuffer, + gTestConfig.bytesReceived)); + + // Let the receive task close + gTestConfig.asyncExit = true; + uPortTaskBlock(U_ZEPHYR_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + + zsock_shutdown(sock, 0); + zsock_close(sock); + + // Free memory + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; + + // Free memory from event queues + uPortEventQueueCleanUp(); + + } else { + U_TEST_PRINT_LINE("*** WARNING *** not testing PPP since device does not support it."); + } + } + + // Remove each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); + } + // Remove each device + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("closing device %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); + U_PORT_TEST_ASSERT(uDeviceClose(*pTmp->pDevHandle, false) == 0); + *pTmp->pDevHandle = NULL; + } + } + uNetworkTestListFree(); + + uDeviceDeinit(); + uPortDeinit(); + + // Check for resource leaks + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); + resourceCount = uTestUtilGetDynamicResourceCount() - resourceCount; + U_TEST_PRINT_LINE("we have leaked %d resources(s).", resourceCount); + U_PORT_TEST_ASSERT(resourceCount <= 0); +} + +/** Clean-up to be run at the end of this round of tests, just + * in case there were test failures which would have resulted + * in the deinitialisation being skipped. + */ +U_PORT_TEST_FUNCTION("[zephyrSock]", "zephyrSockCleanUp") +{ + // The network test configuration is shared between + // the network, sockets, security and location tests + // so must reset the handles here in case the + // tests of one of the other APIs are coming next. + uNetworkTestCleanUp(); + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +// End of file diff --git a/port/u_port_ppp_default.c b/port/u_port_ppp_default.c new file mode 100644 index 000000000..dee7e698d --- /dev/null +++ b/port/u_port_ppp_default.c @@ -0,0 +1,125 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed 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. + */ + +/** @file + * @brief Default implementations of uPortPppAttach(), uPortPppConnect(), + * uPortPppDisconnect() and uPortPppDetach() which simply return + * #U_ERROR_COMMON_NOT_SUPPORTED. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +/* ---------------------------------------------------------------- + * INCLUDE FILES + * -------------------------------------------------------------- */ + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" + +#include "u_compiler.h" // U_WEAK + +#include "u_error_common.h" + +#include "u_port_ppp.h" + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: WORKAROUND FOR LINKER ISSUE + * -------------------------------------------------------------- */ + +void uPortPppDefaultPrivateLink() +{ + //dummy +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Attach a PPP interface to the bottom of the IP stack of a platform. +U_WEAK int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback) +{ + (void) pDevHandle; + (void) pConnectCallback; + (void) pDisconnectCallback; + (void) pTransmitCallback; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Bring a previously attached PPP interface up. +U_WEAK int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode) +{ + (void) pDevHandle; + (void) pIpAddress; + (void) pDnsIpAddressPrimary; + (void) pDnsIpAddressSecondary; + (void) pUsername; + (void) pPassword; + (void) authenticationMode; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Reconnect a PPP interface after it was lost. +U_WEAK int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress) +{ + (void) pDevHandle; + (void) pIpAddress; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Take a previously attached PPP interface down. +U_WEAK int32_t uPortPppDisconnect(void *pDevHandle) +{ + (void) pDevHandle; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Detach a PPP interface from the bottom of a platform's IP stack. +U_WEAK int32_t uPortPppDetach(void *pDevHandle) +{ + (void) pDevHandle; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// End of file diff --git a/port/ubxlib.cmake b/port/ubxlib.cmake index b4e9fd61d..22306ced1 100644 --- a/port/ubxlib.cmake +++ b/port/ubxlib.cmake @@ -151,6 +151,9 @@ list(APPEND UBXLIB_SRC ${UBXLIB_BASE}/port/u_port_timezone.c) # Default uPortXxxResource implementation list(APPEND UBXLIB_SRC ${UBXLIB_BASE}/port/u_port_resource.c) +# Default uPortPppAttach()/uPortPppDetach() implementation +list(APPEND UBXLIB_SRC ${UBXLIB_BASE}/port/u_port_ppp_default.c) + # Optional features # short range diff --git a/port/ubxlib.mk b/port/ubxlib.mk index 7be58cda4..1f1427b1d 100644 --- a/port/ubxlib.mk +++ b/port/ubxlib.mk @@ -70,6 +70,9 @@ UBXLIB_SRC += ${UBXLIB_BASE}/port/u_port_timezone.c # Default uPortXxxResource implementation UBXLIB_SRC += ${UBXLIB_BASE}/port/u_port_resource.c +# Default uPortPppAttach()/uPortPppDetach() implementation +UBXLIB_SRC += ${UBXLIB_BASE}/port/u_port_ppp_default.c + # Optional short range related files and directories ifneq ($(filter short_range,$(UBXLIB_FEATURES)),) UBXLIB_MODULE_DIRS += \ diff --git a/zephyr/module.yml b/zephyr/module.yml index 9ed1c6101..2d3d799e9 100644 --- a/zephyr/module.yml +++ b/zephyr/module.yml @@ -1,3 +1,5 @@ build: cmake: port/platform/zephyr/ - kconfig: port/platform/zephyr/Kconfig \ No newline at end of file + kconfig: port/platform/zephyr/Kconfig + settings: + dts_root: port/platform/zephyr \ No newline at end of file