diff --git a/boards/shields/swir_hl78xx_ev_kit/Kconfig.shield b/boards/shields/swir_hl78xx_ev_kit/Kconfig.shield new file mode 100644 index 000000000000..e5c5d711b342 --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/Kconfig.shield @@ -0,0 +1,5 @@ +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +config SHIELD_SWIR_HL78XX_EV_KIT + def_bool $(shields_list_contains,swir_hl78xx_ev_kit) diff --git a/boards/shields/swir_hl78xx_ev_kit/doc/img/SW-Dev-RC76.3.webp b/boards/shields/swir_hl78xx_ev_kit/doc/img/SW-Dev-RC76.3.webp new file mode 100644 index 000000000000..f2f339ed522d Binary files /dev/null and b/boards/shields/swir_hl78xx_ev_kit/doc/img/SW-Dev-RC76.3.webp differ diff --git a/boards/shields/swir_hl78xx_ev_kit/doc/index.rst b/boards/shields/swir_hl78xx_ev_kit/doc/index.rst new file mode 100644 index 000000000000..cc6a9ce761f6 --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/doc/index.rst @@ -0,0 +1,79 @@ +.. _swir_hl78xx_ev_kit: + +HL/RC Module Evaluation Kit Shield +################################## + +Overview +******** + +Welcome to the HL78 module getting started guide. +This guide will help you set up the evaluation kit (eval kit) +for sending AT commands to the HL78 module and initiating data transmission. + +.. figure:: img/SW-Dev-RC76.3.webp + :align: center + :alt: HL/RC Module Evaluation Kit Shield Shield + + HL/RC Module Evaluation Kit Shield Shield (Credit: Sierrra Wireless) + +More information about the shield can be found at the `HL/RC Module Evaluation Kit Shield guide website`_. + +Pins Assignment of HL/RC Module Evaluation Kit Shield Shield +============================================================ ++--------------------------+----------------------------------------------------------+ +| Shield Connector Pin | Function | ++==========================+==========================================================+ +| CN403 alias | UART 1 (with CTS and RTS pins) | ++--------------------------+----------------------------------------------------------+ +| CN303 alias | SPI / UART 3 | ++--------------------------+----------------------------------------------------------+ +| CN1000 alias | GPIO Test Pins | ++--------------------------+----------------------------------------------------------+ +| GPIO6 CN1000_3 | LOW POWER MONITORING | ++--------------------------+----------------------------------------------------------+ +| VGPIO alias | Indirect indicator of hibernate mode entry/exit | ++--------------------------+----------------------------------------------------------+ +| RESET CN1000_12 | RESET SIGNAL | ++--------------------------+----------------------------------------------------------+ +| WAKE-UP CN1000_8 | SPI / UART 3 | ++--------------------------+----------------------------------------------------------+ + +Please refer to the website for more information about HL/RC Module Evaluation Kit Shield Shield setup. +.. _HL/RC Module Evaluation Kit Shield guide website: + +Checking Your Basic Configurations in PuTTY +=========================================== +Before trying to set up a wired connection between the board and a host MCU, +it's a good idea to first go through this list of basic AT commands over a +USB COM port on a PC. For reference, you can find all the AT commands for the +HL78xx modules in the Source. + +Requirements +************ + +This shield can be used with any boards which provides a configuration for +header connectors and defines node aliases for UART, SPI and USB interfaces (see +:ref:`shields` for more details). + +Programming +*********** + +Set ``--shield swir_hl78xx_ev_kit`` when you invoke ``west build``. For +example: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/modem/hello_hl78xx + :board: st/nucleo_u575zi_q + :shield: swir_hl78xx_ev_kit + :goals: build + +References +********** + +.. target-notes:: + +.. _HL/RC Module Evaluation Kit Shield guide website: + https://source.sierrawireless.com/resources/airprime/development_kits/hl78xx-hl7900-development-kit-guide/ + +.. _HL/RC Module Evaluation Kit Shield specification website: + https://info.sierrawireless.com/iot-modules-evaluation-kit#guide-for-the-hl78-series-evaluation-kit diff --git a/boards/shields/swir_hl78xx_ev_kit/swir_hl78xx_ev_kit.overlay b/boards/shields/swir_hl78xx_ev_kit/swir_hl78xx_ev_kit.overlay new file mode 100644 index 000000000000..b7408f4cd3d8 --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/swir_hl78xx_ev_kit.overlay @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + modem-uart = &usart2; + modem = &modem; + }; +}; + +&usart2 { + pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3 &usart2_rts_pd4 &usart2_cts_pd3 >; + pinctrl-1 = <&analog_pa2 &analog_pa3 &analog_pd4 &analog_pd3 >; + dmas = <&gpdma1 0 27 STM32_DMA_PERIPH_TX + &gpdma1 1 26 STM32_DMA_PERIPH_RX>; + dma-names = "tx", "rx"; + pinctrl-names = "default", "sleep"; + current-speed = <115200>; + status = "okay"; + hw-flow-control; + modem: hl_modem { + compatible = "swir,hl7812"; + status = "okay"; + mdm-reset-gpios = <&gpiod 5 (GPIO_ACTIVE_LOW)>; + mdm-wake-gpios = <&gpioe 15 (GPIO_ACTIVE_HIGH)>; + mdm-vgpio-gpios = <&gpiob 2 0>; + mdm-uart-cts-gpios = <&gpiod 3 0>; + mdm-gpio6-gpios = <&gpioa 8 0>; + }; + +}; diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt index 62b84bd79294..2e8a0ad90470 100644 --- a/drivers/modem/CMakeLists.txt +++ b/drivers/modem/CMakeLists.txt @@ -35,5 +35,7 @@ if (CONFIG_MODEM_SIM7080) zephyr_library_sources(simcom-sim7080.c) endif() +add_subdirectory_ifdef(CONFIG_MODEM_HL78XX hl78xx) + zephyr_library_sources_ifdef(CONFIG_MODEM_CELLULAR modem_cellular.c) zephyr_library_sources_ifdef(CONFIG_MODEM_AT_SHELL modem_at_shell.c) diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig index b8e66ef42748..833bebff3178 100644 --- a/drivers/modem/Kconfig +++ b/drivers/modem/Kconfig @@ -192,7 +192,7 @@ source "drivers/modem/Kconfig.quectel-bg9x" source "drivers/modem/Kconfig.wncm14a2a" source "drivers/modem/Kconfig.cellular" source "drivers/modem/Kconfig.at_shell" - +source "drivers/modem/hl78xx/Kconfig.hl78xx" source "drivers/modem/Kconfig.hl7800" source "drivers/modem/Kconfig.simcom-sim7080" diff --git a/drivers/modem/hl78xx/CMakeLists.txt b/drivers/modem/hl78xx/CMakeLists.txt new file mode 100644 index 000000000000..f8439ea96142 --- /dev/null +++ b/drivers/modem/hl78xx/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# +zephyr_library() + +zephyr_library_sources( + hl78xx.c + hl78xx_sockets.c + hl78xx_utility.c + hl78xx_apis.c +) + +add_subdirectory_ifdef(CONFIG_HL78XX_EVT_MONITOR hl78xx_evt_monitor) + +zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) +zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/lib/sockets) diff --git a/drivers/modem/hl78xx/Kconfig.hl78xx b/drivers/modem/hl78xx/Kconfig.hl78xx new file mode 100644 index 000000000000..e9f5a8825993 --- /dev/null +++ b/drivers/modem/hl78xx/Kconfig.hl78xx @@ -0,0 +1,830 @@ +#Sierra Wireless HL78XX driver driver options + +#Copyright(c) 2025 Netfeasa Ltd. +#SPDX - License - Identifier : Apache - 2.0 + +config MODEM_HL78XX + bool "HL78XX modem driver" + select MODEM_MODULES + select MODEM_CHAT + select MODEM_PIPE + select MODEM_PIPELINK + select MODEM_BACKEND_UART + select RING_BUFFER + select MODEM_SOCKET + select NET_OFFLOAD + select MODEM_CONTEXT + select EXPERIMENTAL + depends on !MODEM_CELLULAR + imply GPIO + help + Choose this setting to enable Sierra Wireless HL78XX driver LTE-CatM1/NB-IoT modem + driver. + +if MODEM_HL78XX + +choice MODEM_HL78XX_VARIANT + bool "Sierra Wireless hl78xx variant selection" + default MODEM_HL7812 + +config MODEM_HL7812 + bool "Sierra Wireless hl7812" + help + Support for hl7812 modem + +config MODEM_HL7800_1 + bool "Sierra Wireless hl7800" + help + Support for hl7800 modem + +config MODEM_HL78XX_AUTODETECT_VARIANT + bool "detect automatically" + help + Automatic detection of modem variant (MODEM_HL7812 or MODEM_HL7800) + +endchoice + +if MODEM_HL7812 + +config MODEM_FW_R6 + bool "Modem firmware R6" + help + Only for HL7812, enable this setting to use NBNTN rat. + This is required for NBNTN mode. + NBNTN mode is supported with R6 firmware. + +endif # MODEM_HL7812 + +config MODEM_HL78XX_UART_BUFFER_SIZES + int "The UART receive and transmit buffer sizes in bytes." + default 512 + +config MODEM_HL78XX_CHAT_BUFFER_SIZES + int "The size of the buffers used for the chat scripts in bytes." + default 512 + +config MODEM_HL78XX_USER_PIPE_BUFFER_SIZES + int "The size of the buffers used for each user pipe in bytes." + default 512 + +config MODEM_HL78XX_NEW_BAUDRATE + int "New baudrate to configure modem to, if supported" + range 9600 4000000 + default 3000000 if DT_HAS_U_BLOX_LARA_R6_ENABLED + default 115200 + +config MODEM_HL78XX_NEW_BAUDRATE_DELAY + int "Time modem takes to change baudrate, in milliseconds" + range 0 1000 + default 100 if DT_HAS_U_BLOX_LARA_R6_ENABLED + default 300 + +config MODEM_HL78XX_RECV_BUF_CNT + int "The number of allocated network buffers" + default 30 + +config MODEM_HL78XX_RECV_BUF_SIZE + int "The size of the network buffers in bytes" + default 128 + +config MODEM_HL78XX_RX_WORKQ_STACK_SIZE + int "Stack size for the Sierra Wireless HL78XX driver modem driver work queue" + default 2048 + help + This stack is used by the work queue to pass off net_pkt data + to the rest of the network stack, letting the rx thread continue + processing data. + +choice MODEM_HL78XX_ADDRESS_FAMILY + prompt "IP Address family" + default MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6 + help + The address family for IP connections. + +config MODEM_HL78XX_ADDRESS_FAMILY_IPV4 + bool "IPv4" + +config MODEM_HL78XX_ADDRESS_FAMILY_IPV6 + bool "IPv6" + +config MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6 + bool "IPv4v6" + +endchoice + +choice MODEM_HL78XX_BOOT_MODE + prompt "Modem Boot Type" + default MODEM_HL78XX_BOOT_IN_AIRPLANE_MODE + help + Set Modem Functionality see, AT+CFUN + Consider reset conditions after settings, second parameter of cfun + 0 — Do not reset the MT before setting it to power level. + 1 — Reset the MT before setting it to power level. + +config MODEM_HL78XX_BOOT_IN_MINIMUM_FUNCTIONAL_MODE + bool "MINIMUM FUNCTIONAL MODE" + help + - AT+CFUN = 0,0 + — Minimum functionality, SIM powered off + - Consider reset conditions second parameter of cfun + +config MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + bool "FULL FUNCTIONAL MODE" + help + - AT+CFUN = 1,0 + - Full functionality, starts cellular searching + - Consider reset conditions after settings, second parameter of cfun + +config MODEM_HL78XX_BOOT_IN_AIRPLANE_MODE + bool "AIRPLANE MODE" + help + - AT+CFUN = 4,0 + - Disable radio transmit and receive; SIM powered on. (i.e. "Airplane + Mode") + - Consider reset conditions after settings, second parameter of cfun +endchoice + +if MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + +config MODEM_STAY_IN_BOOT_MODE_FOR_ROAMING + bool "WAIT FOR ROAMING" + help + Keep the device in boot mode until have +CREG/+CEREG: 1(normal) or 5(roaming) +endif # MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + +config MODEM_HL78XX_PERIODIC_SCRIPT_MS + int "Periodic script interval in milliseconds" + default 2000 + +choice MODEM_HL78XX_APN_SOURCE + prompt "APN SOURCE" + default MODEM_HL78XX_APN_SOURCE_NETWORK + help + Select the source for automatically detecting the APN. + You can choose between IMSI (International Mobile Subscriber Identity) + or ICCID (Integrated Circuit Card Identifier) as the reference for APN association. + +config MODEM_HL78XX_APN_SOURCE_ICCID + bool "CCID Associated APN" + help + - AT+CCID + - Multiple ICCID and APN combinations can be stored in APN PROFILE configuration + see MODEM_HL78XX_APN_PROFILES + +config MODEM_HL78XX_APN_SOURCE_IMSI + bool "CIMI Associated APN" + help + - AT+CIMI + - Multiple CIMI and APN combinations can be stored in APN PROFILE configuration + see MODEM_HL78XX_APN_PROFILES + +config MODEM_HL78XX_APN_SOURCE_KCONFIG + bool "User defined Single APN" + help + - Use the APN defined in MODEM_HL78XX_APN + - Supports only one APN + +config MODEM_HL78XX_APN_SOURCE_NETWORK + bool "Network Provided APN" + help + - AT+CGCONTRDP=1 + - Use the APN provided by the network +endchoice + +if MODEM_HL78XX_APN_SOURCE_KCONFIG + +config MODEM_HL78XX_APN + string "APN for establishing network connection" + default "xxxxxxxx" + help + This setting is used in the AT+CGDCONT command to set the APN name + for the network connection context. This value is specific to + the network provider and has to be changed. + +endif # MODEM_HL78XX_APN_SOURCE_KCONFIG + +if MODEM_HL78XX_APN_SOURCE_ICCID || MODEM_HL78XX_APN_SOURCE_IMSI + +config MODEM_HL78XX_APN_PROFILES + string "list of profiles to search when autodetecting APN" + default "hologram=23450, wm=20601, int=29505" + help + Set a comma separated list of profiles, each containing of: + = ... + = ... + +endif # MODEM_HL78XX_APN_SOURCE_ICCID || MODEM_HL78XX_APN_SOURCE_IMSI + +config MODEM_HL78XX_RSSI_WORK + bool "RSSI polling work" + default y + help + Sierra Wireless HL78XX driver device is configured to poll for RSSI + +config MODEM_HL78XX_RSSI_WORK_PERIOD + int "Configure RSSI WORK polling frequency" + depends on MODEM_HL78XX_RSSI_WORK + default 30 + help + This settings is used to configure the period of RSSI polling + +config MODEM_HL78XX_AUTORAT + bool "automatic RAT switching and set the PRL profiles" + default y + help + AT+KSRAT is provided for backwards compatibility only. AT+KSELACQ is recommended for RAT switching. + (See RAT Switching Application Note (Doc# 2174296) for details.) + +if MODEM_HL78XX_AUTORAT + +config MODEM_HL78XX_AUTORAT_OVER_WRITE_PRL + bool "Overwrite PRL profiles always at boot" + depends on MODEM_HL78XX_AUTORAT + help + If you enable this option, the PRL profiles on the modem will be overwritten by the app + with the PRL profile values at boot everytime. + +config MODEM_HL78XX_AUTORAT_PRL_PROFILES + string "Configure Preferred Radio Access Technology List" + depends on MODEM_HL78XX_AUTORAT + default "1,2,3" + help + AT+KSELACQ=0,1,2,3 , MODEM,CAT-M1, NB-IoT, GSM + +config MODEM_HL78XX_AUTORAT_NB_BAND_CFG + string "NB-IoT band configuration (comma-separated list)" + default "8,20,28" + depends on MODEM_HL78XX_AUTORAT + help + Specify which LTE bands (e.g., 8,20,28) to use for NB-IoT when using Auto RAT. + This string is parsed at runtime or build-time. + +config MODEM_HL78XX_AUTORAT_M1_BAND_CFG + string "Cat-M1 band configuration (comma-separated list)" + default "3,5,12" + depends on MODEM_HL78XX_AUTORAT + help + Specify which LTE bands (e.g., 3,5,12) to use for Cat-M1 when using Auto RAT. + +endif # MODEM_HL78XX_AUTORAT + +choice MODEM_HL78XX_RAT + bool "Radio Access Technology Mode" + default MODEM_HL78XX_RAT_M1 + depends on !MODEM_HL78XX_AUTORAT + +config MODEM_HL78XX_RAT_M1 + bool "LTE-M1" + help + Enable LTE Cat-M1 mode during modem init. + In the Read response, '0' indicates CAT-M1. + +config MODEM_HL78XX_RAT_NB1 + bool "NB-IoT" + help + Enable LTE Cat-NB1 mode during modem init. + 1 — NB-IoT (HL78XX/HL7802/HL7810/HL7845/HL7812 only) + +config MODEM_HL78XX_RAT_GSM + bool "GSM" + depends on MODEM_HL7812 + help + Enable GSM mode during modem init. + 2 — GSM (for HL7802/HL7812 only) + +config MODEM_HL78XX_RAT_NBNTN + bool "NB-NTN" + depends on MODEM_FW_R6 + help + Enable NBNTN mode during modem init. + 3 — NBNTN (for HL7810/HL7812 only), It does not support = 1 + +endchoice + +menuconfig MODEM_HL78XX_CONFIGURE_BANDS + bool "Configure modem bands" + default "y" if !MODEM_HL78XX_AUTORAT + help + Choose this setting to configure which LTE bands the + HL78XX modem should use at boot time. + +if MODEM_HL78XX_CONFIGURE_BANDS + +config MODEM_HL78XX_BAND_1 + bool "Band 1 (2000MHz)" + default y + help + Enable Band 1 (2000MHz) + +config MODEM_HL78XX_BAND_2 + bool "Band 2 (1900MHz)" + default y + help + Enable Band 2 (1900MHz) + +config MODEM_HL78XX_BAND_3 + bool "Band 3 (1800MHz)" + default y + help + Enable Band 3 (1800MHz) + +config MODEM_HL78XX_BAND_4 + bool "Band 4 (1700MHz)" + default y + help + Enable Band 4 (1700MHz) + +config MODEM_HL78XX_BAND_5 + bool "Band 5 (850MHz)" + default y + help + Enable Band 5 (850MHz) + +config MODEM_HL78XX_BAND_8 + bool "Band 8 (900MHz)" + default y + help + Enable Band 8 (900MHz) + +config MODEM_HL78XX_BAND_9 + bool "Band 9 (1900MHz)" + help + Enable Band 9 (1900MHz) + +config MODEM_HL78XX_BAND_10 + bool "Band 10 (2100MHz)" + help + Enable Band 10 (2100MHz) + +config MODEM_HL78XX_BAND_12 + bool "Band 12 (700MHz)" + default y + help + Enable Band 12 (700MHz) + +config MODEM_HL78XX_BAND_13 + bool "Band 13 (700MHz)" + default y + help + Enable Band 13 (700MHz) + +config MODEM_HL78XX_BAND_17 + bool "Band 17 (700MHz)" + help + Enable Band 17 (700MHz) + +config MODEM_HL78XX_BAND_18 + bool "Band 18 (800MHz)" + help + Enable Band 18 (800MHz) + +config MODEM_HL78XX_BAND_19 + bool "Band 19 (800MHz)" + help + Enable Band 19 (800MHz) + +config MODEM_HL78XX_BAND_20 + bool "Band 20 (800MHz)" + default y + help + Enable Band 20 (800MHz) + +config MODEM_HL78XX_BAND_23 + bool "Band 23 (2000MHz)" + help + Enable Band 23 (2000MHz) + +config MODEM_HL78XX_BAND_25 + bool "Band 25 (1900MHz)" + help + Enable Band 25 (1900MHz) + +config MODEM_HL78XX_BAND_26 + bool "Band 26 (800MHz)" + help + Enable Band 26 (800MHz) + +config MODEM_HL78XX_BAND_27 + bool "Band 27 (800MHz)" + help + Enable Band 27 (800MHz) + +config MODEM_HL78XX_BAND_28 + bool "Band 28 (700MHz)" + default y + help + Enable Band 28 (700MHz) + +config MODEM_HL78XX_BAND_31 + bool "Band 31 (450MHz)" + help + Enable Band 31 (450MHz) + +config MODEM_HL78XX_BAND_66 + bool "Band 66 (1800MHz)" + help + Enable Band 66 (1800MHz) + +config MODEM_HL78XX_BAND_72 + bool "Band 72 (450MHz)" + help + Enable Band 72 (450MHz) + +config MODEM_HL78XX_BAND_73 + bool "Band 73 (450MHz)" + help + Enable Band 73 (450MHz) + +config MODEM_HL78XX_BAND_85 + bool "Band 85 (700MHz)" + help + Enable Band 85 (700MHz) + +config MODEM_HL78XX_BAND_87 + bool "Band 87 (410MHz)" + help + Enable Band 87 (410MHz) + +config MODEM_HL78XX_BAND_88 + bool "Band 88 (410MHz)" + help + Enable Band 88 (410MHz) + +config MODEM_HL78XX_BAND_106 + bool "Band 106 (900MHz)" + help + Enable Band 106 (900MHz) + +config MODEM_HL78XX_BAND_107 + bool "Band 107 (1800MHz)" + help + Enable Band 107 (1800MHz) + +config MODEM_HL78XX_BAND_255 + bool "Band 255 (1500MHz)" + help + Enable Band 255 (1500MHz) + +config MODEM_HL78XX_BAND_256 + bool "Band 256 (2000MHz)" + help + Enable Band 256 (2000MHz) + +endif # MODEM_HL78XX_CONFIGURE_BAND + +config MODEM_HL78XX_LOW_POWER_MODE + bool "Low power modes" + help + Choose this setting to enable a low power mode for the HL78XX modem + +if MODEM_HL78XX_LOW_POWER_MODE + +config MODEM_HL78XX_EDRX + bool "eDRX" + depends on MODEM_HL78XX_LOW_POWER_MODE + help + Enable LTE eDRX + +config MODEM_HL78XX_PSM + bool "PSM" + depends on MODEM_HL78XX_LOW_POWER_MODE + default y + help + Enable Power Save Mode (PSM) + +if !MODEM_HL78XX_EDRX && !MODEM_HL78XX_PSM + +config MODEM_HL78XX_POWER_DOWN + bool "Turn OFF" + depends on MODEM_HL78XX_LOW_POWER_MODE + help + Power off modem + +endif # If eDRX or PSM is enabled, the modem will not power down. + +if MODEM_HL78XX_EDRX + +config MODEM_HL78XX_EDRX_VALUE + int "Requested eDRX timer" + default 5 + range 0 15 + help + Half a byte in a 4-bit format. The eDRX value refers to bit 4 to 1 + of octet 3 of the Extended DRX parameters information element. + Default value is 5 (81.92) seconds. + refer to Requested_eDRX_value + +config MODEM_HL78XX_PTW_VALUE + int "Requested eDRX Paging Time Window (PTW) timer" + default 0 + help + The PTW value refers to bits 8 to 5 of octet 3 of the Extended DRX + parameters information element + · CAT-M1 - actual PTW length = 1.28 sec x (1 + PTW) + · NB-IoT - actual PTW length = 2.56 sec x (1 + PTW) + +endif # MODEM_HL78XX_EDRX + +if MODEM_HL78XX_PSM + +config MODEM_HL78XX_PSM_PERIODIC_TAU + string "Requested extended periodic TAU timer" + default "10101111" + help + Requested extended periodic TAU (tracking area update) value (T3412) + to be allocated to the UE in E-UTRAN. One byte in an 8-bit format. + Default value is 15 minute. + +config MODEM_HL78XX_PSM_ACTIVE_TIME + string "Requested active time" + default "00001111" + help + Requested Active Time value (T3324) to be allocated to the UE. + One byte in an 8-bit format. Default value is 30 seconds. + +endif # MODEM_HL78XX_PSM + +if MODEM_HL78XX_POWER_DOWN + +choice MODEM_HL78XX_POWER_DOWN_MODE + prompt "Power Down Mode" + default MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN + help + Choose the logic used to determine when the HL78XX modem powers down. + Only one method can be selected: + - 'Use delay after last data activity' + - 'Use active time after RRC connection' + +config MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN + bool "Use delay after last data activity" + help + Power down the modem after a specified delay (MODEM_HL78XX_POWER_DOWN_DELAY) + following the last socket closure or data transmission. + +config MODEM_HL78XX_USE_ACTIVE_TIME_BASED_POWER_DOWN + bool "Use active time after RRC connection" + help + Power down the modem based on a PSM-like active time (MODEM_HL78XX_POWER_DOWN_ACTIVE_TIME) + starting after establishing an RRC connection. + * This timer is reset only when a new RRC connection is established. + +endchoice + +if MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN + +config MODEM_HL78XX_POWER_DOWN_DELAY + int "Delay in seconds before power down" + default 15 + range 10 600 + help + Delay in seconds before powering down the modem after the last + data transfer. + +endif # MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN + +if MODEM_HL78XX_USE_ACTIVE_TIME_BASED_POWER_DOWN + +config MODEM_HL78XX_POWER_DOWN_ACTIVE_TIME + int "Modem active time before power down" + default 30 + range 20 600 + help + The active time value to be allocated to the UE before + powering down the modem. One byte in an 8-bit format. + Default value is 30 seconds. + This setting is used to keep the modem active for a short period + before powering it down. + +endif # MODEM_HL78XX_USE_ACTIVE_TIME_BASED_POWER_DOWN +endif # MODEM_HL78XX_POWER_DOWN + +if !MODEM_HL78XX_POWER_DOWN + +choice MODEM_DEFAULT_SLEEP_LEVEL + prompt "Default Sleep Level" + default MODEM_HL78XX_SLEEP_LEVEL_HIBERNATE + help + The application can override this setting + +config MODEM_HL78XX_SLEEP_LEVEL_HIBERNATE + bool "Hibernate" + help + Lowest power consumption + IO state not retained + Application subsystem OFF + +config MODEM_HL78XX_SLEEP_LEVEL_LITE_HIBERNATE + bool "Lite Hibernate" + help + IO state retained + Application subsystem OFF + +config MODEM_HL78XX_SLEEP_LEVEL_SLEEP + bool "Sleep" + help + Highest power consumption of modem sleep states + IO state retained + Application subsystem ON + Allows sockets to remain open + +endchoice + +config MODEM_HL78XX_SLEEP_DELAY_AFTER_REBOOT + int "Delay in seconds before sleep after reboot" + default 10 +endif # !MODEM_HL78XX_POWER_DOWN +endif # MODEM_HL78XX_LOW_POWER_MODE + +choice MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG + prompt "Network Registration Status Report Configuration" + default MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE + help + · 0 — Disable network registration unsolicited result code. + · 1 — Enable network registration unsolicited result code +CEREG: + · 2 — Enable network registration and location information unsolicited result + code: + +CEREG: [,[],[],[]] + · 3 — Enable network registration, location information and EMM cause value + information unsolicited result code: + +CEREG: [,[],[],[][,, ]] + · 4 — For a UE that wants to apply PSM, enable network registration and + location information unsolicited result code: + +CEREG: [,[],[],[][,,[,[],[]]]] + · 5 — For a UE that wants to apply PSM, enable network registration, location + information and EMM cause value information unsolicited result code: + +CEREG: [,[],[],[][,[],[][,[] []]]] + +config MODEM_HL78XX_DISABLE_NETWORK_STATUS_URC_REPORT + bool "Disable network status URC report" + help + Disable network registration unsolicited result code. + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT + bool "Network status URC report" + help + Enable network registration unsolicited result code +CEREG: + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION + bool "Network status URC report with location" + help + Enable network registration and location information unsolicited result + +CEREG: [,[],[],[]] + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION_AND_CAUSE + bool "Network status URC report with location and cause" + help + Enable network registration, location information and EMM cause value + information unsolicited result code: + +CEREG: [,[],[],[][,, ]] + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM + bool "Network status URC report with PSM" + help + For a UE that wants to apply PSM, enable network registration and + location information unsolicited result code: + +CEREG: [,[],[],[][,,[,[],[]]]] + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE + bool "Network status URC report with PSM and cause" + help + For a UE that wants to apply PSM, enable network registration, location + information and EMM cause value information unsolicited result code: + +CEREG: [,[],[],[][,[],[][,[] []]]] + +endchoice + +config MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG_CODE + string + default "5" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE + default "4" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM + default "3" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION_AND_CAUSE + default "2" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION + default "1" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT + default "0" if MODEM_HL78XX_DISABLE_NETWORK_STATUS_URC_REPORT + help + This setting is used to configure the network registration status report + configuration code. It is used in the AT+CREG/CEREG command to set the network + registration status report configuration. + +config MODEM_MIN_ALLOWED_SIGNAL_STRENGTH + int "Minimum allowed RSRP signal strength (dBm)" + default -140 + range -140 0 + help + The average power received from a single Reference signal, + and Its typical range is around -44dbm (good) to -140dbm(bad). + Note: (Anything < - 115 dBm unusable/unreliable) + EXCELLENT_SIGNAL_STRENGTH + bool ">= -80(dBm)" + default -80 + range - 80 0 + GOOD_SIGNAL_STRENGTH + bool ">= -90(dBm)" + default -90 + range - 90 0 + MID_CELL_SIGNAL_STRENGTH + bool ">= -100(dBm)" + default -100 + range - 100 0 + CELL_EDGE_SIGNAL_STRENGTH + bool "<= -100(dBm)" + default -110 + range - 110 0 + POOR_SIGNAL_STRENGTH + bool ">= -140(dBm)" + default -140 + range - 140 0 + +config MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + bool "Advanced socket configuration" + help + Enable advanced socket configuration options + +if MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + +config MODEM_HL78XX_NUM_SOCKETS + int "Maximum number of sockets" + default 6 + range 1 6 + help + Maximum number of sockets that can be opened at the same time + +config MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC + int "display data in URC" + default 2 + help + 0 — Do not display data in URC + 1 — Display data in URC automatically + 2 — Do not display data in URC and KUDPRCV command is required to dump + data. If there is no KUDPRCV command after rcv_timeout, the original data is + dropped and URC re-enabled. + +config MODEM_HL78XX_SOCKET_RESTORE_ON_BOOT + bool "Restore sockets on boot" + help + only the first session is restored + For HL780x, restore_on_boot is required to restore the first session across + eDRX/PSM hibernate cycles or reset. + • For HL781x/45, all sessions are maintained across eDRX/PSM hibernate cycles + independent of this configuration. It is only required for reset cases. + • For a restored client session (e.g. after a reset or exiting hibernation), +KTCPCNX + must be used to establish a connection before sending/receiving any data. + 0 — Do not restore sockets on boot + 1 — Restore sockets on boot + +endif # MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + +config MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG +bool "Verbose debug output in the HL78xx" + help + Enabling this setting will turn on VERY heavy debugging from the + modem. Do NOT leave on for production. + +config MODEM_HL78XX_DEV_POWER_PULSE_DURATION + int "Duration of the power-on pulse in milliseconds." + default 1500 + help + Trigger a power-on sequence by setting a power on GPIO pin high + for a specific amount of time. + +config MODEM_HL78XX_DEV_RESET_PULSE_DURATION + int "Duration of the power-reset pulse in milliseconds." + default 100 + help + Trigger a power-reset sequence by setting a reset GPIO pin high + for a specific amount of time. + +config MODEM_HL78XX_DEV_STARTUP_TIME + int "Wait before assuming the device is ready." + default 1000 + help + The expected time (in milliseconds) the modem needs to fully power on + and become operational. + +config MODEM_HL78XX_DEV_SHUTDOWN_TIME + int "Wait before assuming the device is completely off." + default 1000 + help + The amount of time (in milliseconds) the system should wait for the modem + to fully shut down + +config MODEM_HL78XX_DEV_INIT_PRIORITY + int "Sierra Wireless HL78XX device driver init priority" + default 80 + help + Sierra Wireless HL78XX device driver initialization priority. + Do not mess with it unless you know what you are doing. + +config MODEM_HL78XX_OFFLOAD_INIT_PRIORITY + int "Sierra Wireless HL78XX offload driver init priority" + default 79 + help + Sierra Wireless HL78XX driver device driver initialization priority. + Do not mess with it unless you know what you are doing. + Make sure offload init priority higher than dev init priority + +rsource "hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor" + +endif # MODEM_HL78XX diff --git a/drivers/modem/hl78xx/hl78xx.c b/drivers/modem/hl78xx/hl78xx.c new file mode 100644 index 000000000000..1b396cd79aa1 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx.c @@ -0,0 +1,2586 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "hl78xx.h" + +#define MAX_SCRIPT_AT_CMD_RETRY 3 + +#define MDM_NODE DT_ALIAS(modem) + +/* GPIO availability macros */ +#define HAS_PWR_ON_GPIO DT_NODE_HAS_PROP(MDM_NODE, mdm_pwr_on_gpios) +#define HAS_FAST_SHUTD_GPIO DT_NODE_HAS_PROP(MDM_NODE, mdm_fast_shutd_gpios) +#define HAS_UART_DTR_GPIO DT_NODE_HAS_PROP(MDM_NODE, mdm_uart_dtr_gpios) +#define HAS_GPIO6_GPIO DT_NODE_HAS_PROP(MDM_NODE, mdm_gpio6_gpios) +#define HAS_UART_DSR_GPIO DT_NODE_HAS_PROP(MDM_NODE, mdm_uart_dsr_gpios) + +/* GPIO count macro */ +#define GPIO_CONFIG_LEN \ + (3 /* reset, wake, vgpio */ + HAS_PWR_ON_GPIO + HAS_FAST_SHUTD_GPIO + HAS_UART_DTR_GPIO + \ + HAS_GPIO6_GPIO + HAS_UART_DSR_GPIO) + +LOG_MODULE_REGISTER(hl78xx_dev, CONFIG_MODEM_LOG_LEVEL); + +/* RX thread work queue */ +K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_HL78XX_RX_WORKQ_STACK_SIZE); + +static struct k_work_q modem_workq; +hl78xx_evt_monitor_handler_t event_dispatcher; + +static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt); +static int hl78xx_on_idle_state_enter(struct hl78xx_data *data); +static int hl78xx_on_state_enter(struct hl78xx_data *data); +static int hl78xx_on_state_leave(struct hl78xx_data *data); +#ifdef CONFIG_MODEM_HL78XX_POWER_DOWN +static int hl78xx_pwr_dwn_feed_timer(struct hl78xx_data *data); +#endif +static void event_dispatcher_dispatch(struct hl78xx_evt *notif) +{ + if (event_dispatcher != NULL) { + event_dispatcher(notif); + } +} + +static const char *hl78xx_state_str(enum hl78xx_state state) +{ + switch (state) { + case MODEM_HL78XX_STATE_IDLE: + return "idle"; + case MODEM_HL78XX_STATE_RESET_PULSE: + return "reset pulse"; + case MODEM_HL78XX_STATE_POWER_ON_PULSE: + return "power pulse"; + case MODEM_HL78XX_STATE_AWAIT_POWER_ON: + return "await power on"; + case MODEM_HL78XX_STATE_SET_BAUDRATE: + return "set baudrate"; + case MODEM_HL78XX_STATE_RUN_INIT_SCRIPT: + return "run init script"; + case MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT: + return "init fail diagnostic script "; + case MODEM_HL78XX_STATE_RUN_PMC_CONFIG_SCRIPT: + return "run pmc cfg script"; + case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT: + return "run rat cfg script"; + case MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT: + return "run enable gprs script"; + case MODEM_HL78XX_STATE_AWAIT_WAKEUP: + return "await wakeup"; + case MODEM_HL78XX_STATE_AWAIT_REGISTERED: + return "await registered"; + case MODEM_HL78XX_STATE_CARRIER_ON: + return "carrier on"; + case MODEM_HL78XX_STATE_CARRIER_OFF: + return "carrier on"; + case MODEM_HL78XX_STATE_INIT_POWER_OFF: + return "init power off"; + case MODEM_HL78XX_STATE_POWER_OFF_PULSE: + return "power off pulse"; + case MODEM_HL78XX_STATE_SIM_POWER_OFF: + return "sim power off"; + case MODEM_HL78XX_STATE_AIRPLANE: + return "airplane mode"; + case MODEM_HL78XX_STATE_AWAIT_POWER_OFF: + return "await power off"; + default: + return "UNKNOWN state"; + } + + return ""; +} + +static const char *hl78xx_event_str(enum hl78xx_event event) +{ + switch (event) { + case MODEM_HL78XX_EVENT_RESUME: + return "resume"; + case MODEM_HL78XX_EVENT_SUSPEND: + return "suspend"; + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + return "script success"; + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + return "script failed"; + case MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART: + return "script require restart"; + case MODEM_HL78XX_EVENT_TIMEOUT: + return "timeout"; + case MODEM_HL78XX_EVENT_REGISTERED: + return "registered"; + case MODEM_HL78XX_EVENT_DEREGISTERED: + return "deregistered"; + case MODEM_HL78XX_EVENT_BUS_OPENED: + return "bus opened"; + case MODEM_HL78XX_EVENT_BUS_CLOSED: + return "bus closed"; + case MODEM_HL78XX_EVENT_SOCKET_READY: + return "socket ready"; + case MODEM_HL78XX_EVENT_SOCKET_CLOSED: + return "socket closed"; + case MODEM_HL78XX_EVENT_DEVICE_AWAKE: + return "device awake"; + case MODEM_HL78XX_EVENT_DEVICE_ASLEEP: + return "device asleep"; + default: + return "unknown event"; + } + + return ""; +} + +static bool hl78xx_gpio_is_enabled(const struct gpio_dt_spec *gpio) +{ + return (gpio->port != NULL); +} + +static void hl78xx_log_event(enum hl78xx_event evt) +{ + LOG_DBG("event %s", hl78xx_event_str(evt)); +} + +static void hl78xx_start_timer(struct hl78xx_data *data, k_timeout_t timeout) +{ + k_work_schedule(&data->timeout_work, timeout); +} + +static void hl78xx_stop_timer(struct hl78xx_data *data) +{ + k_work_cancel_delayable(&data->timeout_work); +} + +static void hl78xx_timeout_handler(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct hl78xx_data *data = CONTAINER_OF(dwork, struct hl78xx_data, timeout_work); + + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_TIMEOUT); +} + +static void hl78xx_bus_pipe_handler(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + switch (event) { + case MODEM_PIPE_EVENT_OPENED: + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_OPENED); + break; + + case MODEM_PIPE_EVENT_CLOSED: + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_CLOSED); + break; + + default: + break; + } +} + +static void hl78xx_log_state_changed(enum hl78xx_state last_state, enum hl78xx_state new_state) +{ + LOG_DBG("switch from %s to %s", hl78xx_state_str(last_state), hl78xx_state_str(new_state)); +} + +static void hl78xx_event_dispatch_handler(struct k_work *item) +{ + struct hl78xx_data *data = + CONTAINER_OF(item, struct hl78xx_data, events.event_dispatch_work); + + uint8_t events[sizeof(data->events.event_buf)]; + uint8_t events_cnt; + + k_mutex_lock(&data->events.event_rb_lock, K_FOREVER); + + events_cnt = (uint8_t)ring_buf_get(&data->events.event_rb, events, + sizeof(data->events.event_buf)); + + k_mutex_unlock(&data->events.event_rb_lock); + + for (uint8_t i = 0; i < events_cnt; i++) { + hl78xx_event_handler(data, (enum hl78xx_event)events[i]); + } +} + +void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt) +{ + k_mutex_lock(&data->events.event_rb_lock, K_FOREVER); + ring_buf_put(&data->events.event_rb, (uint8_t *)&evt, 1); + k_mutex_unlock(&data->events.event_rb_lock); + k_work_submit_to_queue(&modem_workq, &data->events.event_dispatch_work); +} + +static void hl78xx_chat_callback_handler(struct modem_chat *chat, + enum modem_chat_script_result result, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_SUCCESS); + } else { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_FAILED); + } +} + +static void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + enum hl78xx_registration_status registration_status = 0; + + if (argc < 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %d %s", __LINE__, __func__, argc, argv[0]); +#endif + if (argc > 2 && strlen(argv[1]) == 1 && strlen(argv[2]) == 1) { + /* This is a condition to distinguish received message between URC message and User + * request network status request. If the message is User message, then argv[1] and + * argv[2] will be 1 character long. If the message is URC request network status + * request, then argv[1] will be 1 character long while argv[2] will be 2 characters + * long. + */ + registration_status = atoi(argv[2]); + } else { + registration_status = atoi(argv[1]); + } + + if (registration_status == data->status.registration.network_state_current) { + LOG_DBG("Registration status unchanged: %d", registration_status); + return; + } + + data->status.registration.network_state_previous = + data->status.registration.network_state_current; + /* Update the current registration state */ + data->status.registration.network_state_current = registration_status; + struct hl78xx_evt event = {.type = HL78XX_LTE_REGISTRATION_STAT_UPDATE, + .content.reg_status = + data->status.registration.network_state_current}; + + event_dispatcher_dispatch(&event); + data->status.registration.is_registered_previously = + data->status.registration.is_registered_currently; + if (hl78xx_is_registered(data)) { + data->status.registration.is_registered_currently = true; + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_REGISTERED); +#if defined CONFIG_MODEM_HL78XX_POWER_DOWN && \ + defined CONFIG_MODEM_HL78XX_USE_ACTIVE_TIME_BASED_POWER_DOWN + hl78xx_pwr_dwn_feed_timer(data); +#endif + } else { + data->status.registration.is_registered_currently = false; + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEREGISTERED); + } +} + +static void hl78xx_on_psmev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %d", __LINE__, __func__, argc); +#endif + if (argc < 2) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + data->status.psm.psmev_previous = data->status.psm.psmev_current; + data->status.psm.psmev_current = ATOI(argv[1], 0, "psmev"); + struct hl78xx_evt event = {.type = HL78XX_LTE_PSMEV, + .content.psm_event = data->status.psm.psmev_current}; + + event_dispatcher_dispatch(&event); +} + +static void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc != 2) { + return; + } + + int module_status = ATOI(argv[1], 0, "ksup"); + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("Module status: %d", module_status); +#endif + struct hl78xx_evt event = {.type = HL78XX_LTE_MODEM_STARTUP, + .content.value = module_status}; + + event_dispatcher_dispatch(&event); +} + +static void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("IMEI: %s %s", argv[0], argv[1]); +#endif + strncpy(data->identity.imei, argv[1], sizeof(data->identity.imei) - 1); +} + +static void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("cgmm: %s %s", argv[0], argv[1]); +#endif + strncpy(data->identity.model_id, argv[1], sizeof(data->identity.model_id) - 1); +} + +static void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("IMSI: %s %s", argv[0], argv[1]); +#endif + strncpy(data->identity.imsi, argv[1], sizeof(data->identity.imsi) - 1); +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) + /* set the APN automatically */ + modem_detect_apn(data, argv[1]); +#endif +} + +static void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("cgmi: %s %s", argv[0], argv[1]); +#endif + strncpy(data->identity.manufacturer, argv[1], sizeof(data->identity.manufacturer) - 1); +} + +static void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("cgmr: %s %s", argv[0], argv[1]); +#endif + strncpy(data->identity.fw_version, argv[1], sizeof(data->identity.fw_version) - 1); +} + +static void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("ICCID: %s %s", argv[0], argv[1]); +#endif + strncpy(data->identity.iccid, argv[1], sizeof(data->identity.iccid) - 1); +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) + /* set the APN automatically */ + modem_detect_apn(data, argv[1]); +#endif +} + +/* Handler: +KSTATEV: */ +static void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE; + + if (argc != 3) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("KSTATEV: %s %s %s", argv[0], argv[1], argv[2]); +#endif + rat_mode = ATOI(argv[2], 0, "rat_mode"); + + hl78xx_on_kstatev_parser(data, ATOI(argv[1], 0, "status")); + if (rat_mode != data->status.registration.rat_mode) { + struct hl78xx_evt event = {.type = HL78XX_RAT_UPDATE, + .content.rat_mode = data->status.registration.rat_mode}; + + event_dispatcher_dispatch(&event); + } + data->status.registration.rat_mode = rat_mode; +} + +static void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); +#endif +} + +static void hl78xx_on_socknotifydata(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + int socket_id = -1; + int new_total = -1; + + if (argc < 2) { + return; + } + socket_id = ATOI(argv[1], -1, "socket_id"); + new_total = ATOI(argv[2], -1, "length"); +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d %d", __LINE__, socket_id, new_total); +#endif + socket_notify_data(socket_id, new_total); +} + +static void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + int socket_id = -1; + int tcp_notif = -1; + + if (argc < 2) { + return; + } + socket_id = ATOI(argv[1], -1, "socket_id"); + tcp_notif = ATOI(argv[2], -1, "tcp_notif"); +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d %d", __LINE__, socket_id, tcp_notif); +#endif + + if (tcp_notif == -1) { + return; + } + + tcp_notify_data(socket_id, tcp_notif); +} + +static void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 2) { + return; + } + + data->status.ksrep = (uint8_t)atoi(argv[1]); +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("KSREP: %s %s", argv[0], argv[1]); +#endif +} +#ifndef CONFIG_MODEM_HL78XX_AUTORAT +static void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 2) { + return; + } + + data->status.registration.rat_mode = (uint8_t)atoi(argv[1]); + + struct hl78xx_evt event = {.type = HL78XX_RAT_UPDATE, + .content.rat_mode = data->status.registration.rat_mode}; + + event_dispatcher_dispatch(&event); + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("KSRAT: %s %s", argv[0], argv[1]); +#endif +} +#else +static void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } + + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc > 3) { + data->kselacq_data.mode = 0; + data->kselacq_data.rat1 = ATOI(argv[1], 0, "rat1"); + data->kselacq_data.rat2 = ATOI(argv[2], 0, "rat2"); + data->kselacq_data.rat3 = ATOI(argv[3], 0, "rat3"); + } else { + data->kselacq_data.mode = 0; + data->kselacq_data.rat1 = 0; + data->kselacq_data.rat2 = 0; + data->kselacq_data.rat3 = 0; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%d] [%d] [%d] [%d]", __LINE__, argc, argv[0], data->kselacq_data.mode, + data->kselacq_data.rat1, data->kselacq_data.rat2, data->kselacq_data.rat3); +#endif +} +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + +static void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 3) { + return; + } + + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); +#endif + + uint8_t rat_id = ATOI(argv[1], 0, "rat"); + uint8_t kbnd_bitmap_size = strlen(argv[2]); + + if (kbnd_bitmap_size >= MDM_BAND_HEX_STR_LEN) { + LOG_ERR("%d %s Unexpected band bitmap length of %d", __LINE__, __func__, + kbnd_bitmap_size); + return; + } + if (rat_id >= HL78XX_RAT_COUNT) { + return; + } + + data->status.kbndcfg[rat_id].rat = rat_id; + strncpy(data->status.kbndcfg[rat_id].bnd_bitmap, argv[2], kbnd_bitmap_size); + data->status.kbndcfg[rat_id].bnd_bitmap[kbnd_bitmap_size] = '\0'; +} + +static void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 3) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); +#endif + + data->status.rssi = ATOI(argv[1], 0, "rssi"); +} + +static void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 7) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); +#endif + + data->status.rsrq = ATOI(argv[5], 0, "rssq"); + data->status.rsrp = ATOI(argv[6], 0, "rssp"); +} + +static void hl78xx_on_kcellmeas(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + if (argc < 5) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); +#endif + data->status.rsrp = (int)ATOD(argv[1], 0, "rsrp"); + LOG_DBG("%d %s RSRP: %d", __LINE__, __func__, data->status.rsrp); + if (hl78xx_is_rsrp_valid(data)) { + data->status.registration.is_registered_currently = true; + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_REGISTERED); + hl78xx_release_socket_comms(); + } +} + +static void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] ", __LINE__, argc, argv[0], argv[1]); +#endif + + data->status.phone_functionality = ATOI(argv[1], 0, "phone_func"); +} + +static void hl78xx_on_ksleep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] ", __LINE__, argc, argv[0], argv[1]); +#endif + if (argc > 3) { + data->status.pmc_sleep.mngt = ATOI(argv[1], 0, "mngt"); + data->status.pmc_sleep.level = ATOI(argv[2], 0, "level"); + data->status.pmc_sleep.delay = ATOI(argv[3], 0, "delay"); + } else { + /* only the case when the modem sleep mode is always disabled + * AT+KSLEEP? + * +KSLEEP: 2 + * OK + */ + data->status.pmc_sleep.mngt = ATOI(argv[1], 0, "mngt"); + data->status.pmc_sleep.level = 0; + data->status.pmc_sleep.delay = 0; + } +} +static void hl78xx_on_cpsms(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[4], argv[5]); +#endif + if (argc > 3) { + data->status.pmc_cpsms.mode = ATOI(argv[1], 0, "mode"); + int8_t active_time = binary_str_to_byte(argv[5]); + int8_t periodic_tau = binary_str_to_byte(argv[4]); + + data->status.pmc_cpsms.active_time = (active_time == -EINVAL) ? 0 : active_time; + data->status.pmc_cpsms.periodic_tau = (periodic_tau == -EINVAL) ? 0 : periodic_tau; + } else { + data->status.pmc_cpsms.mode = ATOI(argv[1], 0, "mode"); + data->status.pmc_cpsms.active_time = 0; + data->status.pmc_cpsms.periodic_tau = 0; + } +} +static void hl78xx_on_kedrxcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 3) { + return; + } + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + int act_type = ATOI(argv[2], -1, "act_type"); +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d [%s] [%s] ", __LINE__, argc, argv[0], argv[1]); +#endif + if (act_type < 3 || act_type > 5) { + LOG_ERR("Invalid act_type %d", act_type); + return; + } + + if (argc > 3) { + data->status.pmc_kedrxcfg[act_type - HL78XX_ACT_TYPE_RAT_MASK].mode = + ATOI(argv[1], 0, "mode"); + data->status.pmc_kedrxcfg[act_type - HL78XX_ACT_TYPE_RAT_MASK].ack_type = + ATOI(argv[2], 0, "ack_type"); + data->status.pmc_kedrxcfg[act_type - HL78XX_ACT_TYPE_RAT_MASK].requested_edrx = + ATOI(argv[3], 0, "r_edrx"); + } else { + data->status.pmc_kedrxcfg[act_type - HL78XX_ACT_TYPE_RAT_MASK].mode = + ATOI(argv[1], 0, "mode"); + data->status.pmc_kedrxcfg[act_type - HL78XX_ACT_TYPE_RAT_MASK].ack_type = + ATOI(argv[2], 0, "ack_type"); + data->status.pmc_kedrxcfg[act_type - HL78XX_ACT_TYPE_RAT_MASK].requested_edrx = 0; + } +} +MODEM_CHAT_MATCH_DEFINE(ok_match, "OK", "", NULL); +MODEM_CHAT_MATCHES_DEFINE(allow_match, MODEM_CHAT_MATCH("OK", "", NULL), + MODEM_CHAT_MATCH("+CME ERROR: ", "", NULL)); + +MODEM_CHAT_MATCHES_DEFINE(unsol_matches, MODEM_CHAT_MATCH("+CREG: ", ",", hl78xx_on_cxreg), + MODEM_CHAT_MATCH("+CEREG: ", ",", hl78xx_on_cxreg), + MODEM_CHAT_MATCH("+CGREG: ", ",", hl78xx_on_cxreg), + MODEM_CHAT_MATCH("+PSMEV: ", "", hl78xx_on_psmev), + MODEM_CHAT_MATCH("+KSTATEV: ", ",", hl78xx_on_kstatev), + MODEM_CHAT_MATCH("+KUDP_DATA: ", ",", hl78xx_on_socknotifydata), + MODEM_CHAT_MATCH("+KTCP_DATA: ", ",", hl78xx_on_socknotifydata), + MODEM_CHAT_MATCH("+KTCP_NOTIF: ", ",", hl78xx_on_ktcpnotif), + MODEM_CHAT_MATCH("+KUDP_RCV: ", ",", hl78xx_on_udprcv), + MODEM_CHAT_MATCH("+KBNDCFG: ", ",", hl78xx_on_kbndcfg), + MODEM_CHAT_MATCH("+CSQ: ", ",", hl78xx_on_csq), + MODEM_CHAT_MATCH("+CESQ: ", ",", hl78xx_on_cesq), + MODEM_CHAT_MATCH("+KCELLMEAS: ", ",", hl78xx_on_kcellmeas), + MODEM_CHAT_MATCH("+CFUN: ", "", hl78xx_on_cfun), + MODEM_CHAT_MATCH("+KEDRXCFG: ", ",", hl78xx_on_kedrxcfg)); + +MODEM_CHAT_MATCHES_DEFINE(abort_matches, MODEM_CHAT_MATCH("ERROR", "", NULL)); +MODEM_CHAT_MATCH_DEFINE(at_ready_match, "+KSUP: ", "", hl78xx_on_ksup); +MODEM_CHAT_MATCH_DEFINE(imei_match, "", "", hl78xx_on_imei); +MODEM_CHAT_MATCH_DEFINE(cgmm_match, "", "", hl78xx_on_cgmm); +MODEM_CHAT_MATCH_DEFINE(cimi_match, "", "", hl78xx_on_imsi); +MODEM_CHAT_MATCH_DEFINE(cgmi_match, "", "", hl78xx_on_cgmi); +MODEM_CHAT_MATCH_DEFINE(cgmr_match, "", "", hl78xx_on_cgmr); +MODEM_CHAT_MATCH_DEFINE(iccid_match, "+CCID: ", "", hl78xx_on_iccid); +MODEM_CHAT_MATCH_DEFINE(ksrep_match, "+KSREP: ", ",", hl78xx_on_ksrep); +#ifndef CONFIG_MODEM_HL78XX_AUTORAT +MODEM_CHAT_MATCH_DEFINE(ksrat_match, "+KSRAT: ", "", hl78xx_on_ksrat); +#else +MODEM_CHAT_MATCH_DEFINE(kselacq_match, "+KSELACQ: ", ",", hl78xx_on_kselacq); +#endif /* CONFIG_MODEM_HL78XX_RAT */ +MODEM_CHAT_MATCH_DEFINE(ksleep_match, "+KSLEEP: ", ",", hl78xx_on_ksleep); +MODEM_CHAT_MATCH_DEFINE(cpsms_match, "+CPSMS: ", ",", hl78xx_on_cpsms); + +static void hl78xx_init_pipe(const struct device *dev) +{ + const struct hl78xx_config *cfg = dev->config; + struct hl78xx_data *data = dev->data; + + const struct modem_backend_uart_config uart_backend_config = { + .uart = cfg->uart, + .receive_buf = data->buffers.uart_rx, + .receive_buf_size = sizeof(data->buffers.uart_rx), + .transmit_buf = data->buffers.uart_tx, + .transmit_buf_size = ARRAY_SIZE(data->buffers.uart_tx), + }; + + data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config); +} + +static int modem_init_chat(const struct device *dev) +{ + struct hl78xx_data *data = dev->data; + + const struct modem_chat_config chat_config = { + .user_data = data, + .receive_buf = data->buffers.chat_rx, + .receive_buf_size = sizeof(data->buffers.chat_rx), + .delimiter = data->buffers.delimiter, + .delimiter_size = strlen(data->buffers.delimiter), + .filter = data->buffers.filter, + .filter_size = data->buffers.filter ? strlen(data->buffers.filter) : 0, + .argv = data->buffers.argv, + .argv_size = ARRAY_SIZE(data->buffers.argv), + .unsol_matches = unsol_matches, + .unsol_matches_size = ARRAY_SIZE(unsol_matches), + }; + + return modem_chat_init(&data->chat, &chat_config); +} +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_periodic_chat_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSIMDET?", ok_match), + /* MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG?", ok_match) */); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_periodic_chat_script, hl78xx_periodic_chat_script_cmds, + abort_matches, hl78xx_chat_callback_handler, 4); + +MODEM_CHAT_SCRIPT_CMDS_DEFINE( + hl78xx_init_chat_script_cmds, MODEM_CHAT_SCRIPT_CMD_RESP("", at_ready_match), +#ifdef CONFIG_MODEM_HL78XX_LOW_POWER_MODE + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KHWIOCFG=3,1,6", ok_match), +#endif + MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP_MULT("AT+CGACT=0", allow_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=4", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSLEEP?", ksleep_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPSMS?", cpsms_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KEDRXCFG?", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KPATTERN=" + "\"" EOF_PATTERN "\"", + ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CCID", iccid_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CMEE=1", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+GNSSCONF=10,1", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+GNSSNMEA=0,1000,0,104F", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGSN", imei_match), MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMM", cgmm_match), MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMI", cgmi_match), MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMR", cgmr_match), MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CIMI", cimi_match), MODEM_CHAT_SCRIPT_CMD_RESP("", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", ok_match), +#ifdef CONFIG_MODEM_HL78XX_LOW_POWER_MODE + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KPSMEV=1", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KCELLMEAS=1,35", ok_match), +#endif + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGEREP=2", ok_match), +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSELACQ?", kselacq_match), +#else + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", ksrat_match), +#endif + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KBNDCFG?", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGACT?", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CREG=0", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP( + "AT+CEREG=" CONFIG_MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG_CODE, ok_match)); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_init_chat_script, hl78xx_init_chat_script_cmds, abort_matches, + hl78xx_chat_callback_handler, 100); + +int modem_cmd_send_int(struct hl78xx_data *data, modem_chat_script_callback script_user_callback, + const uint8_t *cmd, uint16_t cmd_size, + const struct modem_chat_match *response_matches, uint16_t matches_size, + bool user_cmd) +{ + int ret = 0; +#if defined CONFIG_MODEM_HL78XX_POWER_DOWN && defined CONFIG_MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN + if (data->status.state == MODEM_HL78XX_STATE_CARRIER_ON) { + hl78xx_pwr_dwn_feed_timer(data); + } +#endif + ret = k_mutex_lock(&data->tx_lock, K_NO_WAIT); + if (ret < 0) { + if (user_cmd == false) { + errno = -ret; + } + return -1; + } + /* Optional logic: only set capture mode for CONNECT-type commands */ + struct modem_chat_script_chat dynamic_script = { + .request = cmd, + .request_size = cmd_size, + .response_matches = response_matches, + .response_matches_size = matches_size, + .timeout = 1000, + }; + struct modem_chat_script chat_script = {.name = "dynamic_script", + .script_chats = &dynamic_script, + .script_chats_size = 1, + .abort_matches = abort_matches, + .abort_matches_size = ARRAY_SIZE(abort_matches), + .callback = script_user_callback, + .timeout = 1000}; + + ret = modem_chat_run_script(&data->chat, &chat_script); + if (ret < 0) { + LOG_ERR("%d %s Failed to run at command: %d", __LINE__, __func__, ret); + } else { + LOG_DBG("Chat script executed successfully."); + } + ret = k_mutex_unlock(&data->tx_lock); + if (ret < 0) { + if (user_cmd == false) { + errno = -ret; + } + return -1; + } + return ret; +} + +void mdm_vgpio_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_vgpio; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("VGPIO GPIO spec is not configured properly"); + return; + } + + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + + LOG_DBG("VGPIO ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec)); +} + +#if HAS_UART_DSR_GPIO +void mdm_uart_dsr_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_dsr; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("DSR GPIO spec is not configured properly"); + return; + } + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + + LOG_DBG("DSR ISR callback %d", gpio_pin_get_dt(spec)); +} +#endif + +void mdm_gpio6_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_gpio6; + enum pm_device_state state; + int rc = 0; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("GPIO6 GPIO spec is not configured properly"); + return; + } + + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + + bool pin_state = gpio_pin_get_dt(spec); + + LOG_DBG("GPIO6 ISR callback %s %d %d", spec->port->name, spec->pin, pin_state); + + rc = pm_device_state_get(config->uart, &state); + if (rc == 0) { + LOG_DBG("PM state %d ret: %d", state, rc); + } + + if (!pin_state) { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEVICE_ASLEEP); + data->status.pmc_power_down.status_previously = + data->status.pmc_power_down.status_currently; + data->status.pmc_power_down.status_currently = true; + } else { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEVICE_AWAKE); + data->status.pmc_power_down.status_previously = + data->status.pmc_power_down.status_currently; + data->status.pmc_power_down.status_currently = false; + } +} + +void mdm_uart_cts_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_cts; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("CTS GPIO spec is not configured properly"); + return; + } + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + + LOG_DBG("CTS ISR callback %d", gpio_pin_get_dt(spec)); +} + +static int hl78xx_on_reset_pulse_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 0); + } + + gpio_pin_set_dt(&config->mdm_gpio_reset, 1); + hl78xx_start_timer(data, K_MSEC(config->reset_pulse_duration_ms)); + return 0; +} + +static void hl78xx_reset_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + default: + break; + } +} + +static int hl78xx_on_reset_pulse_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + gpio_pin_set_dt(&config->mdm_gpio_reset, 0); + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 1); + } + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_power_on_pulse_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1); + } + hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms)); + return 0; +} + +static void hl78xx_power_on_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + default: + break; + } +} + +static int hl78xx_on_power_on_pulse_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0); + } + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_await_power_on_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + hl78xx_start_timer(data, K_MSEC(config->startup_time_ms)); + return 0; +} + +static void hl78xx_await_power_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + default: + break; + } +} +static int hl78xx_on_run_init_script_state_enter(struct hl78xx_data *data) +{ + modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data); + return modem_pipe_open_async(data->uart_pipe); +} + +static void hl78xx_run_init_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + switch (evt) { + case MODEM_HL78XX_EVENT_BUS_OPENED: + modem_chat_attach(&data->chat, data->uart_pipe); + modem_chat_run_script_async(&data->chat, config->init_chat_script); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_PMC_CONFIG_SCRIPT); + + break; + + case MODEM_HL78XX_EVENT_BUS_CLOSED: + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT); + break; + + default: + break; + } +} + +MODEM_CHAT_SCRIPT_CMDS_DEFINE(init_fail_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", ksrep_match)); + +MODEM_CHAT_SCRIPT_DEFINE(init_fail_script, init_fail_script_cmds, abort_matches, + hl78xx_chat_callback_handler, 10); + +MODEM_CHAT_SCRIPT_CMDS_DEFINE(swir_hl78xx_enable_ksup_urc_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP=1", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", ksrep_match)); + +MODEM_CHAT_SCRIPT_DEFINE(swir_hl78xx_enable_ksup_urc_script, swir_hl78xx_enable_ksup_urc_cmds, + abort_matches, hl78xx_chat_callback_handler, 4); + +static int hl78xx_on_run_init_diagnose_script_state_enter(struct hl78xx_data *data) +{ + modem_chat_run_script_async(&data->chat, &init_fail_script); + return 0; +} +static void hl78xx_run_init_fail_script_event_handler(struct hl78xx_data *data, + enum hl78xx_event evt) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + if (data->status.ksrep == 0) { + modem_chat_run_script_async(&data->chat, + &swir_hl78xx_enable_ksup_urc_script); + hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms)); + } else { + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + } + } + break; + case MODEM_HL78XX_EVENT_TIMEOUT: + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + break; + } + + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + case MODEM_HL78XX_EVENT_BUS_CLOSED: + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + if (!hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + LOG_ERR("modem wake pin is not enabled, make sure modem low power is " + "disabled, if you are not sure enable wake up pin by adding it " + "dts!!"); + } + + if (data->status.script_fail_counter++ < MAX_SCRIPT_AT_CMD_RETRY) { + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + break; + } + } + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + default: + break; + } +} + +static int hl78xx_rat_cfg(struct hl78xx_data *data, bool *modem_require_restart, + enum hl78xx_cell_rat_mode *rat_request) +{ + int ret = 0; +#if defined(CONFIG_MODEM_HL78XX_AUTORAT) + /* Check autorat status/configs */ + if (IS_ENABLED(CONFIG_MODEM_HL78XX_AUTORAT_OVER_WRITE_PRL) || + (data->kselacq_data.rat1 == 0 && data->kselacq_data.rat2 == 0 && + data->kselacq_data.rat3 == 0)) { + char cmd_kselq[] = "AT+KSELACQ=0," CONFIG_MODEM_HL78XX_AUTORAT_PRL_PROFILES; + /* Re-congfiguring PRL context definition */ + ret = modem_cmd_send_int(data, NULL, cmd_kselq, strlen(cmd_kselq), &ok_match, 1, + false); + if (ret < 0) { + goto error; + } else { + *modem_require_restart = true; + } + } + + *rat_request = HL78XX_RAT_MODE_AUTO; +#else + /* Check a active rat config */ + if (data->kselacq_data.rat1 != 0 && data->kselacq_data.rat2 != 0 && + data->kselacq_data.rat3 != 0) { + char const *cmd_kselq_disable = (const char *)DISABLE_RAT_AUTO; + + /* Re-congfiguring PRL context definition */ + ret = modem_cmd_send_int(data, NULL, cmd_kselq_disable, strlen(cmd_kselq_disable), + &ok_match, 1, false); + if (ret < 0) { + goto error; + } + } + + char const *cmd_ksrat_query = (const char *)KSRAT_QUERY; + + /* Re-congfiguring PRL context definition */ + ret = modem_cmd_send_int(data, NULL, cmd_ksrat_query, strlen(cmd_ksrat_query), &ksrat_match, + 1, false); + if (ret < 0) { + goto error; + } +#if !defined(CONFIG_MODEM_HL78XX_RAT_M1) && !defined(CONFIG_MODEM_HL78XX_RAT_NB1) && \ + !defined(CONFIG_MODEM_HL78XX_RAT_GSM) && !defined(CONFIG_MODEM_HL78XX_RAT_NBNTN) +#error "No rat has been selected." +#endif + const char *cmd_set_rat = NULL; + + if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_M1)) { + cmd_set_rat = (const char *)SET_RAT_M1_CMD_LEGACY; + + *rat_request = HL78XX_RAT_CAT_M1; + } else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_NB1)) { + cmd_set_rat = (const char *)SET_RAT_NB1_CMD_LEGACY; + + *rat_request = HL78XX_RAT_NB1; + } +#ifdef CONFIG_MODEM_HL7812 + else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_GSM)) { + + cmd_set_rat = (const char *)SET_RAT_GSM_CMD_LEGACY; + + *rat_request = HL78XX_RAT_GSM; + } + +#ifdef CONFIG_MODEM_FW_R6 + else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_NBNTN)) { + cmd_set_rat = (const char *)SET_RAT_NBNTN_CMD_LEGACY; + + *rat_request = HL78XX_RAT_NBNTN; + } +#endif /* CONFIG_MODEM_FW_R6 */ + +#endif + else { + LOG_ERR("%d %s No rat has been selected.", __LINE__, __func__); + } + + if (cmd_set_rat == NULL || *rat_request == HL78XX_RAT_MODE_NONE) { + ret = -EINVAL; + goto error; + } + + if (*rat_request != data->status.registration.rat_mode) { + ret = modem_cmd_send_int(data, NULL, cmd_set_rat, strlen(cmd_set_rat), &ok_match, 1, + false); + if (ret < 0) { + goto error; + } else { + *modem_require_restart = true; + } + } + +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + +error: + return ret; +} + +static int hl78xx_band_cfg(struct hl78xx_data *data, bool *modem_require_restart, + enum hl78xx_cell_rat_mode rat_config_request) +{ + int ret = 0; + char bnd_bitmap[MDM_BAND_HEX_STR_LEN] = {0}; + + if (rat_config_request == HL78XX_RAT_MODE_NONE) { + return -EINVAL; + } +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + /* In Auto-RAT mode, configure both M1 and NB-IoT band configs */ + for (int rat = HL78XX_RAT_CAT_M1; rat <= HL78XX_RAT_NB1; rat++) { + if (rat == HL78XX_RAT_GSM) { + continue; /* skip unsupported RAT for band config */ + } +#else + /* Otherwise, just configure for the requested RAT */ + int rat = rat_config_request; +#endif + ret = hl78xx_get_band_default_config_for_rat(rat, bnd_bitmap, + ARRAY_SIZE(bnd_bitmap)); + if (ret) { + LOG_ERR("%d %s error get band default config %d", __LINE__, __func__, ret); + goto error; + } + const char *modem_trimmed = + hl78xx_trim_leading_zeros(data->status.kbndcfg[rat].bnd_bitmap); + const char *expected_trimmed = hl78xx_trim_leading_zeros(bnd_bitmap); + + if (strcmp(modem_trimmed, expected_trimmed) != 0) { + char cmd_bnd[80] = {0}; + + snprintf(cmd_bnd, sizeof(cmd_bnd), "AT+KBNDCFG=%d,%s", rat, + bnd_bitmap); /* RAT=0 for CAT-M1 */ + ret = modem_cmd_send_int(data, NULL, cmd_bnd, strlen(cmd_bnd), &ok_match, 1, + false); + if (ret < 0) { + goto error; + } else { + *modem_require_restart |= true; + } + } else { + LOG_DBG("The band configs (%s) matched with exist configs (%s) for rat: " + "[%d]", + modem_trimmed, expected_trimmed, rat); + } +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + } +#endif +error: + return ret; +} + +static int hl78xx_on_rat_cfg_script_state_enter(struct hl78xx_data *data) +{ + int ret = 0; + bool modem_require_restart = false; + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + enum hl78xx_cell_rat_mode rat_config_request = HL78XX_RAT_MODE_NONE; + + ret = hl78xx_rat_cfg(data, &modem_require_restart, &rat_config_request); + if (ret < 0) { + goto error; + } + + ret = hl78xx_band_cfg(data, &modem_require_restart, rat_config_request); + if (ret < 0) { + goto error; + } + + if (modem_require_restart) { + const char *cmd_restart = (const char *)SET_AIRPLANE_MODE_CMD; + + ret = modem_cmd_send_int(data, NULL, cmd_restart, strlen(cmd_restart), &ok_match, 1, + false); + if (ret < 0) { + goto error; + } + hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms)); + return 0; + } + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data); + return 0; +error: + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data); + LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret); + return ret; +} + +static void hl78xx_run_rat_cfg_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + int ret = 0; + char const *cmd_ksrat_query = (const char *)KSRAT_QUERY; + + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + LOG_DBG("Rebooting modem to apply new RAT settings"); + ret = modem_cmd_send_int(data, NULL, NULL, 0, &at_ready_match, 1, false); + if (ret < 0) { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND); + } + + /* Re-check if rat config is correct */ + ret = modem_cmd_send_int(data, NULL, cmd_ksrat_query, strlen(cmd_ksrat_query), + &ksrat_match, 1, false); + if (ret < 0) { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND); + } + + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_SUCCESS); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + default: + break; + } +} + +static int hl78xx_on_run_rat_cfg_script_state_leave(struct hl78xx_data *data) +{ + return 0; +} +#ifndef CONFIG_MODEM_HL78XX_LOW_POWER_MODE +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_disable_pmc_chat_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSLEEP=2", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPSMS=0", ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KEDRXCFG=0", ok_match)); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_disable_pmc_chat_script, hl78xx_disable_pmc_chat_script_cmds, + abort_matches, hl78xx_chat_callback_handler, 10); + +static int hl78xx_disabe_pmc(struct hl78xx_data *data) +{ + LOG_DBG("%d Disabling Power Management Config", __LINE__); + return modem_chat_run_script_async(&data->chat, &hl78xx_disable_pmc_chat_script); +} +#else +static int hl78xx_enable_pmc(struct hl78xx_data *data) +{ + const char *turn_on_pmc_cmd = "AT+KSLEEP=1,2,0"; + + LOG_DBG("%d Enabling Power Management Config", __LINE__); + return modem_cmd_send_int(data, NULL, turn_on_pmc_cmd, strlen(turn_on_pmc_cmd), &ok_match, + 1, false); +} + +static int hl78xx_psm_settings(struct hl78xx_data *data) +{ + if (data->status.registration.rat_mode != HL78XX_RAT_NB1 && + data->status.registration.rat_mode != HL78XX_RAT_CAT_M1) { + LOG_DBG("PSM is not supported for RAT mode: %d", + data->status.registration.rat_mode); + return 0; + } +#ifdef CONFIG_MODEM_HL78XX_PSM + if (data->status.pmc_cpsms.mode == false) { + const char *turn_on_psm_cmd = "AT+CPSMS=1,,,\"" CONFIG_MODEM_HL78XX_PSM_PERIODIC_TAU + "\",\"" CONFIG_MODEM_HL78XX_PSM_ACTIVE_TIME "\""; + + return modem_cmd_send_int(data, NULL, turn_on_psm_cmd, strlen(turn_on_psm_cmd), + &ok_match, 1, false); + } +#else + if (data->status.pmc_cpsms.mode == true) { + const char *turn_off_psm_cmd = "AT+CPSMS=0"; + + return modem_cmd_send_int(data, NULL, turn_off_psm_cmd, strlen(turn_off_psm_cmd), + &ok_match, 1, false); + } +#endif + LOG_DBG("PSM is already configured for RAT mode: %d", data->status.registration.rat_mode); + return 0; /* PSM already disabled */ +} + +static int hl78xx_edrx_settings(struct hl78xx_data *data) +{ + if (data->status.registration.rat_mode != HL78XX_RAT_NB1 && + data->status.registration.rat_mode != HL78XX_RAT_CAT_M1) { + LOG_DBG("eDRX is not supported for RAT mode: %d", + data->status.registration.rat_mode); + return 0; + } +#ifdef CONFIG_MODEM_HL78XX_EDRX + if (data->status.pmc_kedrxcfg[data->status.registration.rat_mode].mode == + HL78XX_KEDRX_MODE_DISABLE || + data->status.pmc_kedrxcfg[data->status.registration.rat_mode].mode == + HL78XX_KEDRX_MODE_DISABLE_AND_ERASE_CFG) { + char turn_on_edrx_cmd[sizeof("AT+KEDRXCFG=1,X,XXXX,XXXX")] = {0}; + uint8_t ack_type = 4; + + ack_type = (data->status.registration.rat_mode == HL78XX_RAT_NB1) ? 5 : ack_type; + + snprintf(turn_on_edrx_cmd, sizeof(turn_on_edrx_cmd), "AT+KEDRXCFG=1,%hhu,%d,%d", + ack_type, CONFIG_MODEM_HL78XX_EDRX_VALUE, CONFIG_MODEM_HL78XX_PTW_VALUE); + + return modem_cmd_send_int(data, NULL, turn_on_edrx_cmd, strlen(turn_on_edrx_cmd), + &ok_match, 1, false); + } +#else + if (data->status.pmc_kedrxcfg[data->status.registration.rat_mode].mode == + HL78XX_KEDRX_MODE_ENABLE || + data->status.pmc_kedrxcfg[data->status.registration.rat_mode].mode == + HL78XX_KEDRX_MODE_ENABLE_W_URC) { + char *turn_off_edrx_cmd = "AT+KEDRXCFG=0"; + + return modem_cmd_send_int(data, NULL, turn_off_edrx_cmd, strlen(turn_off_edrx_cmd), + &ok_match, 1, false); + } +#endif + + LOG_DBG("eDRX is already configured for RAT mode: %d", data->status.registration.rat_mode); + return 0; +} +#ifdef CONFIG_MODEM_HL78XX_POWER_DOWN +void hl78xx_pwr_dwn_work_handler(struct k_work *work_item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work_item); + struct hl78xx_data *data = CONTAINER_OF(dwork, struct hl78xx_data, hl78xx_pwr_dwn_work); + + LOG_DBG("%d %s: Power down work handler called", __LINE__, __func__); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + data->status.pmc_power_down.requested_previously = + data->status.pmc_power_down.requested_currently; + data->status.pmc_power_down.requested_currently = true; +} + +static int hl78xx_pwr_dwn_feed_timer(struct hl78xx_data *data) +{ +#ifdef CONFIG_MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN + k_work_reschedule(&data->hl78xx_pwr_dwn_work, + K_SECONDS(CONFIG_MODEM_HL78XX_POWER_DOWN_DELAY)); +#else + k_work_reschedule(&data->hl78xx_pwr_dwn_work, + K_SECONDS(CONFIG_MODEM_HL78XX_POWER_DOWN_ACTIVE_TIME)); +#endif + data->status.pmc_power_down.requested_previously = + data->status.pmc_power_down.requested_currently; + data->status.pmc_power_down.requested_currently = false; + + return 0; +} + +static int hl78xx_pwr_dwn_settings(struct hl78xx_data *data) +{ + LOG_DBG("%d Modem Power Down Settings", __LINE__); + return 0; +} +#endif +#endif /* CONFIG_MODEM_HL78XX_LOW_POWER_MODE */ + +static int hl78xx_on_pmc_cfg_script_state_enter(struct hl78xx_data *data) +{ + int ret = 0; + bool modem_require_restart = false; + +#ifdef CONFIG_MODEM_HL78XX_LOW_POWER_MODE + hl78xx_enable_pmc(data); + hl78xx_psm_settings(data); + hl78xx_edrx_settings(data); +#ifdef CONFIG_MODEM_HL78XX_POWER_DOWN + hl78xx_pwr_dwn_settings(data); +#endif +#else + hl78xx_disabe_pmc(data); + LOG_DBG("%d Disabling Power Management Config", __LINE__); +#endif + if (modem_require_restart) { + const char *cmd_restart = (const char *)SET_AIRPLANE_MODE_CMD; + + LOG_DBG("%d Reset required", __LINE__); + + ret = modem_cmd_send_int(data, NULL, cmd_restart, strlen(cmd_restart), &ok_match, 1, + false); + if (ret < 0) { + goto error; + } + hl78xx_start_timer(data, K_MSEC(100)); + return 0; + } +#ifdef CONFIG_MODEM_HL78XX_LOW_POWER_MODE + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data); +#endif + return 0; +error: + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data); + LOG_ERR("Failed to send command: %d", ret); + return ret; +} + +static void hl78xx_run_pmc_cfg_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + LOG_DBG("Rebooting modem to apply new RAT settings"); + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + case MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART: + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + break; + } + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + default: + break; + } +} + +static int hl78xx_on_run_pmc_cfg_script_state_leave(struct hl78xx_data *data) +{ + return 0; +} + +static int hl78xx_on_await_power_off_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms)); + return 0; +} + +static void hl78xx_await_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + if (evt == MODEM_HL78XX_EVENT_TIMEOUT) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + } +} + +static int hl78xx_on_enable_gprs_state_enter(struct hl78xx_data *data) +{ + int ret = 0; + +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG) + snprintf(data->identity.apn, sizeof(data->identity.apn), "%s", CONFIG_MODEM_HL78XX_APN); +#elif defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) + /* autodetect APN from IMSI */ + /* the list of SIM profiles. Global scope, so the app can change it */ + /* AT+CCID or AT+CIMI needs to be run here if it is not ran in the init script */ + if (strlen(data->identity.apn) < 1) { + LOG_WRN("%d %s APN is left blank", __LINE__, __func__); + } +#else /* defined(CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK) */ +/* set blank string to get apn from network */ +#endif + ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_AIRPLANE, false); + if (ret) { + goto error; + } + + ret = hl78xx_set_apn_internal(data, data->identity.apn, strlen(data->identity.apn)); + if (ret) { + goto error; + } +#if defined(CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE) + ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_FULLY_FUNCTIONAL, false); + if (ret) { + goto error; + } +#endif /* CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE */ + + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data); + + return 0; +error: + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data); + LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret); + return ret; +} + +static void hl78xx_enable_gprs_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + hl78xx_start_timer(data, MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT); + break; + + case MODEM_HL78XX_EVENT_TIMEOUT: + break; + + case MODEM_HL78XX_EVENT_REGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} +static int hl78xx_on_await_wakeup_state_enter(struct hl78xx_data *data) +{ + hl78xx_start_timer(data, MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT); + return 0; +} + +static void hl78xx_await_wakeup_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_BUS_OPENED: + modem_chat_attach(&data->chat, data->uart_pipe); + break; + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + break; + case MODEM_HL78XX_EVENT_DEVICE_ASLEEP: + break; + case MODEM_HL78XX_EVENT_DEVICE_AWAKE: + hl78xx_stop_timer(data); + modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data); + modem_pipe_open_async(data->uart_pipe); + break; + case MODEM_HL78XX_EVENT_TIMEOUT: + /* If timeout occurs, that means the device could not be waken up for + * MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT, + * so take some action here + */ + break; + + case MODEM_HL78XX_EVENT_REGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_await_registered_state_enter(struct hl78xx_data *data) +{ + /* Start timer to check if psm event received */ + hl78xx_start_timer(data, K_MSEC(MDM_PSMEVT_RECEIVE_TIMEOUT)); + return 0; +} + +static void hl78xx_await_registered_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + break; + + case MODEM_HL78XX_EVENT_TIMEOUT: + if (data->status.psm.psmev_current == HL78XX_PSM_EVENT_ENTER) { + /* Just confirm the previous state was registered */ + if (data->status.registration.is_registered_previously) { + LOG_DBG("%d: PSM event received, modem is in PSM mode", __LINE__); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + } + } else { + if (data->status.registration.is_registered_previously) { + if (data->status.psm.psmev_current == HL78XX_PSM_EVENT_ENTER) { + LOG_DBG("%d: PSM exit requested, waiting for " + "registration", + __LINE__); + } + /* Most likely out of coverage. take a action here */ + } else { + /* There was no previous registration state */ + /* Do nothing wait for it if searching is in progress */ + LOG_DBG("%d waiting for registration : Current network " + "state: %d", + __LINE__, data->status.registration.network_state_current); + } + } + break; + + case MODEM_HL78XX_EVENT_REGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON); + + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_await_registered_state_leave(struct hl78xx_data *data) +{ + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_carrier_on_state_enter(struct hl78xx_data *data) +{ + LOG_DBG("%d %s: Entering carrier on state %d %d %d %d", __LINE__, __func__, + data->status.registration.is_registered_previously, + data->status.registration.is_registered_currently, + data->status.registration.network_state_current, + data->status.registration.network_state_previous); + if (!data->status.psm.psmev_previous && !data->status.psm.psmev_current && + data->status.registration.network_state_previous != HL78XX_REGISTRATION_UNKNOWN) { + iface_status_work_cb(data, hl78xx_chat_callback_handler); + } + + notif_carrier_on(); + LOG_DBG("%d psm %d %d", __LINE__, data->status.psm.psmev_previous, + data->status.psm.psmev_current); + return 0; +} + +static void hl78xx_carrier_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + if (!data->status.psm.psmev_previous && !data->status.psm.psmev_current && + data->status.registration.network_state_previous != + HL78XX_REGISTRATION_UNKNOWN) { + hl78xx_start_timer(data, K_SECONDS(2)); + } + break; + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + break; + case MODEM_HL78XX_EVENT_TIMEOUT: + LOG_DBG("%d %d %d %d", __LINE__, data->status.psm.psmev_previous, + data->status.psm.psmev_current, + data->status.registration.network_state_previous); + LOG_DBG("%d %d %d", __LINE__, data->status.pmc_power_down.requested_previously, + data->status.pmc_power_down.requested_currently); + if (data->status.pmc_power_down.requested_previously && + !data->status.pmc_power_down.requested_currently) { + dns_work_cb(true); + hl78xx_release_socket_comms(); + + } else if ((!data->status.psm.psmev_previous && !data->status.psm.psmev_current && + data->status.registration.network_state_previous != + HL78XX_REGISTRATION_UNKNOWN)) { + dns_work_cb(false); + } else { + LOG_DBG("%d Unexpected condition in carrier on state: %d", __LINE__, evt); + } + break; + case MODEM_HL78XX_EVENT_DEREGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED); + break; + case MODEM_HL78XX_EVENT_SUSPEND: + LOG_DBG("%d %s: Modem is suspended, entering power off state", __LINE__, __func__); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + case MODEM_HL78XX_EVENT_SOCKET_CLOSED: + break; + default: + break; + } +} + +static int hl78xx_on_carrier_on_state_leave(struct hl78xx_data *data) +{ + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_carrier_off_state_enter(struct hl78xx_data *data) +{ + LOG_DBG("%d carrier off", __LINE__); + notif_carrier_off(); + /* Check whether or not there is any sockets are connected, + * if true, wait until sockets are closed properly + */ + hl78xx_start_timer(data, K_MSEC(100)); + if (check_if_any_socket_connected() == false) { + LOG_DBG("%d THERE is socket connected", __LINE__); + } + return 0; +} + +static void hl78xx_carrier_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + case MODEM_HL78XX_EVENT_TIMEOUT: + notif_carrier_on(); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON); + break; + case MODEM_HL78XX_EVENT_DEREGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_carrier_off_state_leave(struct hl78xx_data *data) +{ + hl78xx_stop_timer(data); + return 0; +} + +MODEM_CHAT_SCRIPT_CMDS_DEFINE(swir_hl78xx_pwroff_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP(SET_AIRPLANE_MODE_CMD_LEGACY, ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPWROFF", ok_match), + /* MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", ksrep_match) */); + +MODEM_CHAT_SCRIPT_DEFINE(swir_hl78xx_pwroff_script, swir_hl78xx_pwroff_cmds, abort_matches, + hl78xx_chat_callback_handler, 4); + +static int hl78xx_on_init_power_off_state_enter(struct hl78xx_data *data) +{ + return modem_chat_run_script_async(&data->chat, &swir_hl78xx_pwroff_script); +} + +static void hl78xx_init_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + notif_carrier_off(); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + case MODEM_HL78XX_EVENT_TIMEOUT: + break; + case MODEM_HL78XX_EVENT_DEREGISTERED: + hl78xx_stop_timer(data); + break; + default: + break; + } +} + +static int hl78xx_on_init_power_off_state_leave(struct hl78xx_data *data) +{ + modem_chat_release(&data->chat); + return 0; +} + +static int hl78xx_on_power_off_pulse_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1); + } + hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms)); + return 0; +} + +static void hl78xx_power_off_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + if (evt == MODEM_HL78XX_EVENT_TIMEOUT) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_OFF); + } +} + +static int hl78xx_on_power_off_pulse_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0); + } + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_idle_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + LOG_DBG("%d: Wake pin is enabled, setting it to low", __LINE__); + gpio_pin_set_dt(&config->mdm_gpio_wake, 0); + } + + modem_chat_release(&data->chat); + modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data); + modem_pipe_close_async(data->uart_pipe); + k_sem_give(&data->suspended_sem); + return 0; +} + +static void hl78xx_idle_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; +#if defined CONFIG_MODEM_HL78XX_LOW_POWER_MODE && defined CONFIG_PM_DEVICE + enum pm_device_state state; + enum pm_device_action action; + int rc; +#endif + switch (evt) { + case MODEM_HL78XX_EVENT_BUS_CLOSED: +#if defined CONFIG_MODEM_HL78XX_LOW_POWER_MODE && defined CONFIG_PM_DEVICE + rc = pm_device_state_get(config->uart, &state); + if (rc == 0) { + LOG_DBG("PM state %d ret: %d", state, rc); + } + action = PM_DEVICE_ACTION_SUSPEND; + uart_irq_rx_disable(config->uart); + rc = pm_device_action_run(config->uart, action); + if (rc == 0 || rc == -EALREADY) { + LOG_DBG("PM action run: %d", rc); + } else { + LOG_DBG("PM action run failed: %d", rc); + } +#else + LOG_DBG("%d: Bus closed, entering idle state", __LINE__); +#endif + break; + + case MODEM_HL78XX_EVENT_RESUME: + if (data->status.psm.psmev_current == HL78XX_PSM_EVENT_ENTER) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_WAKEUP); + + break; + } + if (config->autostarts) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + } + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + k_sem_give(&data->suspended_sem); + break; + + default: + break; + } +} + +static int hl78xx_on_idle_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; +#if defined CONFIG_MODEM_HL78XX_LOW_POWER_MODE && defined CONFIG_PM_DEVICE + enum pm_device_state state; + enum pm_device_action action; + int rc = 0; +#endif /* CONFIG_MODEM_HL78XX_LOW_POWER_MODE && CONFIG_PM_DEVICE */ + k_sem_take(&data->suspended_sem, K_NO_WAIT); + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + gpio_pin_set_dt(&config->mdm_gpio_reset, 0); + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 1); + } +#if defined CONFIG_MODEM_HL78XX_LOW_POWER_MODE && defined CONFIG_PM_DEVICE + rc = pm_device_state_get(config->uart, &state); + if (rc == 0) { + LOG_DBG("PM state: %d ret: %d", state, rc); + } else { + LOG_DBG("PM state get failed: %d", rc); + } + action = PM_DEVICE_ACTION_RESUME; + rc = pm_device_action_run(config->uart, action); + if (rc == 0 || rc == -EALREADY) { + LOG_DBG("PM action run: %d", rc); + } else { + LOG_DBG("PM action run failed: %d", rc); + } + uart_irq_rx_enable(config->uart); +#endif /* CONFIG_MODEM_HL78XX_LOW_POWER_MODE && CONFIG_PM_DEVICE */ + return 0; +} + +static int hl78xx_on_state_enter(struct hl78xx_data *data) +{ + int ret = 0; +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d", __LINE__, data->status.state); +#endif + switch (data->status.state) { + case MODEM_HL78XX_STATE_IDLE: + ret = hl78xx_on_idle_state_enter(data); + break; + + case MODEM_HL78XX_STATE_RESET_PULSE: + ret = hl78xx_on_reset_pulse_state_enter(data); + break; + + case MODEM_HL78XX_STATE_POWER_ON_PULSE: + ret = hl78xx_on_power_on_pulse_state_enter(data); + break; + + case MODEM_HL78XX_STATE_AWAIT_POWER_ON: + ret = hl78xx_on_await_power_on_state_enter(data); + break; + + case MODEM_HL78XX_STATE_SET_BAUDRATE: + break; + + case MODEM_HL78XX_STATE_RUN_INIT_SCRIPT: + ret = hl78xx_on_run_init_script_state_enter(data); + break; + case MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT: + ret = hl78xx_on_run_init_diagnose_script_state_enter(data); + break; + + case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT: + ret = hl78xx_on_rat_cfg_script_state_enter(data); + break; + case MODEM_HL78XX_STATE_RUN_PMC_CONFIG_SCRIPT: + ret = hl78xx_on_pmc_cfg_script_state_enter(data); + break; + + case MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT: + ret = hl78xx_on_enable_gprs_state_enter(data); + break; + + case MODEM_HL78XX_STATE_AWAIT_WAKEUP: + ret = hl78xx_on_await_wakeup_state_enter(data); + break; + + case MODEM_HL78XX_STATE_AWAIT_REGISTERED: + ret = hl78xx_on_await_registered_state_enter(data); + break; + + case MODEM_HL78XX_STATE_CARRIER_ON: + ret = hl78xx_on_carrier_on_state_enter(data); + break; + + case MODEM_HL78XX_STATE_CARRIER_OFF: + ret = hl78xx_on_carrier_off_state_enter(data); + break; + + case MODEM_HL78XX_STATE_INIT_POWER_OFF: + ret = hl78xx_on_init_power_off_state_enter(data); + break; + + case MODEM_HL78XX_STATE_POWER_OFF_PULSE: + ret = hl78xx_on_power_off_pulse_state_enter(data); + break; + + case MODEM_HL78XX_STATE_AWAIT_POWER_OFF: + ret = hl78xx_on_await_power_off_state_enter(data); + break; + + default: + ret = 0; + break; + } + + return ret; +} + +static int hl78xx_on_state_leave(struct hl78xx_data *data) +{ + int ret = 0; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d", __LINE__, data->status.state); +#endif + switch (data->status.state) { + case MODEM_HL78XX_STATE_IDLE: + ret = hl78xx_on_idle_state_leave(data); + break; + + case MODEM_HL78XX_STATE_RESET_PULSE: + ret = hl78xx_on_reset_pulse_state_leave(data); + break; + + case MODEM_HL78XX_STATE_POWER_ON_PULSE: + ret = hl78xx_on_power_on_pulse_state_leave(data); + break; + + case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT: + ret = hl78xx_on_run_rat_cfg_script_state_leave(data); + break; + + case MODEM_HL78XX_STATE_RUN_PMC_CONFIG_SCRIPT: + ret = hl78xx_on_run_pmc_cfg_script_state_leave(data); + break; + + case MODEM_HL78XX_STATE_AWAIT_REGISTERED: + ret = hl78xx_on_await_registered_state_leave(data); + break; + + case MODEM_HL78XX_STATE_CARRIER_ON: + ret = hl78xx_on_carrier_on_state_leave(data); + break; + + case MODEM_HL78XX_STATE_CARRIER_OFF: + ret = hl78xx_on_carrier_off_state_leave(data); + break; + + case MODEM_HL78XX_STATE_INIT_POWER_OFF: + ret = hl78xx_on_init_power_off_state_leave(data); + break; + + case MODEM_HL78XX_STATE_POWER_OFF_PULSE: + ret = hl78xx_on_power_off_pulse_state_leave(data); + break; + + default: + ret = 0; + break; + } + + return ret; +} + +void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state) +{ + int ret; + + ret = hl78xx_on_state_leave(data); + + if (ret < 0) { + LOG_WRN("failed to leave state, error: %i", ret); + + return; + } + + data->status.state = state; + ret = hl78xx_on_state_enter(data); + + if (ret < 0) { + LOG_WRN("failed to enter state error: %i", ret); + } +} + +static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + enum hl78xx_state state; + + state = data->status.state; + + hl78xx_log_event(evt); + + switch (data->status.state) { + case MODEM_HL78XX_STATE_IDLE: + hl78xx_idle_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_RESET_PULSE: + hl78xx_reset_pulse_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_POWER_ON_PULSE: + hl78xx_power_on_pulse_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_AWAIT_POWER_ON: + hl78xx_await_power_on_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_SET_BAUDRATE: + break; + + case MODEM_HL78XX_STATE_RUN_INIT_SCRIPT: + hl78xx_run_init_script_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT: + hl78xx_run_init_fail_script_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT: + hl78xx_run_rat_cfg_script_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_RUN_PMC_CONFIG_SCRIPT: + hl78xx_run_pmc_cfg_script_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT: + hl78xx_enable_gprs_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_AWAIT_WAKEUP: + hl78xx_await_wakeup_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_AWAIT_REGISTERED: + hl78xx_await_registered_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_CARRIER_ON: + hl78xx_carrier_on_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_CARRIER_OFF: + hl78xx_carrier_off_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_INIT_POWER_OFF: + hl78xx_init_power_off_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_POWER_OFF_PULSE: + hl78xx_power_off_pulse_event_handler(data, evt); + break; + + case MODEM_HL78XX_STATE_AWAIT_POWER_OFF: + hl78xx_await_power_off_event_handler(data, evt); + break; + default: + LOG_ERR("%d %s unknown event", __LINE__, __func__); + break; + } + + if (state != data->status.state) { + hl78xx_log_state_changed(state, data->status.state); + } +} + +#ifdef CONFIG_PM_DEVICE +static int hl78xx_driver_pm_action(const struct device *dev, enum pm_device_action action) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + int ret = 0; + + LOG_WRN("%d %s PM_DEVICE_ACTION: %d", __LINE__, __func__, action); + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + /* suspend the device */ + LOG_DBG("%d %s PM_DEVICE_ACTION_SUSPEND", __LINE__, __func__); + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND); + ret = k_sem_take(&data->suspended_sem, K_SECONDS(30)); + break; + case PM_DEVICE_ACTION_RESUME: + LOG_DBG("%d %s PM_DEVICE_ACTION_RESUME", __LINE__, __func__); + /* resume the device */ + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME); + break; + case PM_DEVICE_ACTION_TURN_ON: + /* + * powered on the device, used when the power + * domain this device belongs is resumed. + */ + LOG_DBG("%d %s PM_DEVICE_ACTION_TURN_ON", __LINE__, __func__); + break; + case PM_DEVICE_ACTION_TURN_OFF: + /* + * power off the device, used when the power + * domain this device belongs is suspended. + */ + LOG_DBG("%d %s PM_DEVICE_ACTION_TURN_OFF", __LINE__, __func__); + break; + default: + return -ENOTSUP; + } + return ret; +} +#endif /* CONFIG_PM_DEVICE */ + +static int hl78xx_init(const struct device *dev) +{ + int ret; + const struct hl78xx_config *config = (const struct hl78xx_config *)dev->config; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + data->dev = dev; +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s", __LINE__, __func__); +#endif + /* Initialize work queue and event handling */ + k_work_queue_start(&modem_workq, modem_workq_stack, + K_KERNEL_STACK_SIZEOF(modem_workq_stack), K_PRIO_COOP(7), NULL); + k_work_init_delayable(&data->timeout_work, hl78xx_timeout_handler); + k_work_init(&data->events.event_dispatch_work, hl78xx_event_dispatch_handler); + ring_buf_init(&data->events.event_rb, sizeof(data->events.event_buf), + data->events.event_buf); + k_sem_init(&data->suspended_sem, 0, 1); + k_sem_init(&data->script_stopped_sem_tx_int, 0, 1); + k_sem_init(&data->script_stopped_sem_rx_int, 0, 1); +#ifdef CONFIG_MODEM_HL78XX_POWER_DOWN + k_work_init_delayable(&data->hl78xx_pwr_dwn_work, hl78xx_pwr_dwn_work_handler); +#endif + /* reset to default */ + data->buffers.eof_pattern_size = strlen(data->buffers.eof_pattern); + memset(data->identity.apn, 0, MDM_APN_MAX_LENGTH); + + /* GPIO validation */ + const struct gpio_dt_spec *gpio_pins[GPIO_CONFIG_LEN] = { + &config->mdm_gpio_reset, &config->mdm_gpio_wake, &config->mdm_gpio_vgpio, +#if HAS_GPIO6_GPIO + &config->mdm_gpio_gpio6, +#endif +#if HAS_PWR_ON_GPIO + &config->mdm_gpio_pwr_on, +#endif +#if HAS_FAST_SHUTD_GPIO + &config->mdm_gpio_fast_shtdown, +#endif +#if HAS_UART_DTR_GPIO + &config->mdm_gpio_uart_dtr, +#endif + }; + + for (int i = 0; i < ARRAY_SIZE(gpio_pins); i++) { + if (!gpio_is_ready_dt(gpio_pins[i])) { + LOG_ERR("GPIO port (%s) not ready!", gpio_pins[i]->port->name); + return -ENODEV; + } + } + + /* GPIO configuration */ + struct { + const struct gpio_dt_spec *spec; + gpio_flags_t flags; + const char *name; + } gpio_config[GPIO_CONFIG_LEN] = { + {&config->mdm_gpio_reset, GPIO_OUTPUT, "reset"}, + {&config->mdm_gpio_wake, GPIO_OUTPUT, "wake"}, + {&config->mdm_gpio_vgpio, GPIO_INPUT, "VGPIO"}, +#if HAS_PWR_ON_GPIO + {&config->mdm_gpio_pwr_on, GPIO_OUTPUT, "pwr_on"}, +#endif +#if HAS_FAST_SHUTD_GPIO + {&config->mdm_gpio_fast_shtdown, GPIO_OUTPUT, "fast_shutdown"}, +#endif +#if HAS_UART_DTR_GPIO + {&config->mdm_gpio_uart_dtr, GPIO_OUTPUT, "DTR"}, +#endif +#if HAS_GPIO6_GPIO + {&config->mdm_gpio_gpio6, GPIO_INPUT, "GPIO6"}, +#endif + }; + + for (int i = 0; i < ARRAY_SIZE(gpio_config); i++) { + ret = gpio_pin_configure_dt(gpio_config[i].spec, gpio_config[i].flags); + if (ret < 0) { + LOG_ERR("Failed to configure %s pin", gpio_config[i].name); + goto error; + } + } + + /* VGPIO interrupt setup */ + gpio_init_callback(&data->gpio_cbs.vgpio_cb, mdm_vgpio_callback_isr, + BIT(config->mdm_gpio_vgpio.pin)); + + ret = gpio_add_callback(config->mdm_gpio_vgpio.port, &data->gpio_cbs.vgpio_cb); + if (ret) { + LOG_ERR("Cannot setup VGPIO callback! (%d)", ret); + goto error; + } + + ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_vgpio, GPIO_INT_EDGE_BOTH); + if (ret) { + LOG_ERR("Error configuring VGPIO interrupt! (%d)", ret); + goto error; + } +#if HAS_GPIO6_GPIO + LOG_DBG("%d %s: GPIO6 is enabled", __LINE__, __func__); + /* GPIO6 interrupt setup */ + gpio_init_callback(&data->gpio_cbs.gpio6_cb, mdm_gpio6_callback_isr, + BIT(config->mdm_gpio_gpio6.pin)); + + ret = gpio_add_callback(config->mdm_gpio_gpio6.port, &data->gpio_cbs.gpio6_cb); + if (ret) { + LOG_ERR("Cannot setup GPIO6 callback! (%d)", ret); + goto error; + } + + ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_gpio6, GPIO_INT_EDGE_BOTH); + if (ret) { + LOG_ERR("Error configuring GPIO6 interrupt! (%d)", ret); + goto error; + } +#endif /* HAS_GPIO6_GPIO */ + (void)hl78xx_init_pipe(dev); + + ret = modem_init_chat(dev); + if (ret < 0) { + goto error; + } + + hl78xx_socket_init(data); + +#ifndef CONFIG_PM_DEVICE + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME); +#else + pm_device_init_suspended(dev); +#endif /* CONFIG_PM_DEVICE */ + + return 0; + +error: + return ret; +} + +int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_handler_t handler) +{ + event_dispatcher = handler; + return 0; +} + +static DEVICE_API(hl78xx, hl78xx_api) = { + + .get_signal = hl78xx_api_func_get_signal, + .get_registration_status = hl78xx_api_func_get_registration_status, + .get_modem_info = hl78xx_api_func_get_modem_info, + .set_apn = hl78xx_api_func_set_apn, + .set_phone_functionality = hl78xx_api_func_set_phone_functionality, + .get_phone_functionality = hl78xx_api_func_get_phone_functionality, + .send_at_cmd = hl78xx_api_func_modem_cmd_send_int, +}; + +#define MODEM_HL78XX_DEFINE_INSTANCE(inst, power_ms, reset_ms, startup_ms, shutdown_ms, start, \ + init_script, periodic_script) \ + static const struct hl78xx_config hl78xx_cfg_##inst = { \ + .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .mdm_gpio_reset = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_reset_gpios, {}), \ + .mdm_gpio_wake = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_wake_gpios, {}), \ + .mdm_gpio_pwr_on = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_pwr_on_gpios, {}), \ + .mdm_gpio_fast_shtdown = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_fast_shutd_gpios, {}), \ + .mdm_gpio_uart_dtr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dtr_gpios, {}), \ + .mdm_gpio_uart_dsr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dsr_gpios, {}), \ + .mdm_gpio_uart_cts = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_cts_gpios, {}), \ + .mdm_gpio_vgpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_vgpio_gpios, {}), \ + .mdm_gpio_gpio6 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio6_gpios, {}), \ + .mdm_gpio_gpio8 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio8_gpios, {}), \ + .mdm_gpio_sim_switch = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_sim_select_gpios, {}), \ + .power_pulse_duration_ms = (power_ms), \ + .reset_pulse_duration_ms = (reset_ms), \ + .startup_time_ms = (startup_ms), \ + .shutdown_time_ms = (shutdown_ms), \ + .autostarts = (start), \ + .init_chat_script = (init_script), \ + .periodic_chat_script = (periodic_script), \ + }; \ + static struct hl78xx_data hl78xx_data_##inst = { \ + .buffers.delimiter = "\r\n", \ + .buffers.eof_pattern = EOF_PATTERN, \ + }; \ + PM_DEVICE_DT_INST_DEFINE(inst, hl78xx_driver_pm_action); \ + DEVICE_DT_INST_DEFINE(inst, hl78xx_init, PM_DEVICE_DT_INST_GET(inst), &hl78xx_data_##inst, \ + &hl78xx_cfg_##inst, POST_KERNEL, \ + CONFIG_MODEM_HL78XX_DEV_INIT_PRIORITY, &hl78xx_api); + +#define MODEM_DEVICE_SWIR_HL78XX(inst) \ + MODEM_HL78XX_DEFINE_INSTANCE(inst, CONFIG_MODEM_HL78XX_DEV_POWER_PULSE_DURATION, \ + CONFIG_MODEM_HL78XX_DEV_RESET_PULSE_DURATION, \ + CONFIG_MODEM_HL78XX_DEV_STARTUP_TIME, \ + CONFIG_MODEM_HL78XX_DEV_SHUTDOWN_TIME, false, \ + &hl78xx_init_chat_script, &hl78xx_periodic_chat_script) + +#define DT_DRV_COMPAT swir_hl7812 +DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT + +#define DT_DRV_COMPAT swir_hl7800 +DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT diff --git a/drivers/modem/hl78xx/hl78xx.h b/drivers/modem/hl78xx/hl78xx.h new file mode 100644 index 000000000000..708afce81837 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx.h @@ -0,0 +1,815 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef HL78XX_H +#define HL78XX_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../modem_context.h" +#include "../modem_socket.h" +#include + +#define MDM_CMD_TIMEOUT (10) /*K_SECONDS*/ +#define MDM_DNS_TIMEOUT (70) /*K_SECONDS*/ +#define MDM_CELL_BAND_SEARCH_TIMEOUT (60) /*K_SECONDS*/ +#define MDM_CMD_CONN_TIMEOUT (120) /*K_SECONDS*/ +#define MDM_REGISTRATION_TIMEOUT (180) /*K_SECONDS*/ +#define MDM_PROMPT_CMD_DELAY (50) /*K_MSEC*/ +#define MDM_RESET_LOW_TIME (1) /*K_MSEC*/ +#define MDM_RESET_HIGH_TIME (10) /*K_MSEC*/ +#define MDM_BOOT_TIME (12) /*K_SECONDS*/ +#define MDM_DNS_ADD_TIMEOUT (100) /*K_MSEC*/ +#define MDM_PSMEVT_RECEIVE_TIMEOUT (100) /*K_MSEC*/ +#define MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT K_MSEC(CONFIG_MODEM_HL78XX_PERIODIC_SCRIPT_MS) + +#define MDM_MAX_DATA_LENGTH CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES + +#define MDM_MAX_SOCKETS 6 +#define MDM_BASE_SOCKET_NUM 1 +#define MDM_BAND_BITMAP_LEN_BYTES 32 +#define MDM_BAND_HEX_STR_LEN (MDM_BAND_BITMAP_LEN_BYTES * 2 + 1) + +#define MDM_KBND_BITMAP_MAX_ARRAY_SIZE 64 + +#define MDM_MANUFACTURER_LENGTH 20 +#define MDM_MODEL_LENGTH 16 +#define MDM_REVISION_LENGTH 64 +#define MDM_IMEI_LENGTH 16 +#define MDM_IMSI_LENGTH 23 +#define MDM_ICCID_LENGTH 22 +#define MDM_APN_MAX_LENGTH 64 + +#define ADDRESS_FAMILY_IP "IP" +#define ADDRESS_FAMILY_IP4 "IPV4" +#define ADDRESS_FAMILY_IPV6 "IPV6" +#define ADDRESS_FAMILY_IPV4V6 "IPV4V6" +#define MDM_HL78XX_SOCKET_AF_IPV4 0 +#define MDM_HL78XX_SOCKET_AF_IPV6 1 +#if defined(CONFIG_MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6) +#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4V6 +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT "####:####:####:####:####:####:####:####" +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN \ + sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16") +#elif defined(CONFIG_MODEM_HL78XX_ADDRESS_FAMILY_IPV4) +#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4 +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT "###.###.###.###" +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN sizeof(MODEM_HL78XX_ADDRESS_FAMILY_FORMAT) + +#else +#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV6 +#endif +#define HL78XX_ACT_TYPE_RAT_MASK 4 + +/* Modem Communication Patterns */ +#define EOF_PATTERN "--EOF--Pattern--" +#define EOF_PATTERN_GNSS "+++" +#define CONNECT_STRING "CONNECT" +#define CME_ERROR_STRING "+CME ERROR: " +#define OK_STRING "OK" + +/* RAT (Radio Access Technology) commands */ +#define SET_RAT_M1_CMD_LEGACY "AT+KSRAT=0" +#define SET_RAT_NB1_CMD_LEGACY "AT+KSRAT=1" +#define SET_RAT_GSM_CMD_LEGACY "AT+KSRAT=2" +#define SET_RAT_NBNTN_CMD_LEGACY "AT+KSRAT=3" + +#define KSRAT_QUERY "AT+KSRAT?" +#define DISABLE_RAT_AUTO "AT+KSELACQ=0,0" + +#define SET_RAT_M1_CMD "AT+KSRAT=0,1" +#define SET_RAT_NB1_CMD "AT+KSRAT=1,1" +#define SET_RAT_GMS_CMD "AT+KSRAT=2,1" +#define SET_RAT_NBNTN_CMD "AT+KSRAT=3,1" + +/* Power mode commands */ +#define SET_AIRPLANE_MODE_CMD_LEGACY "AT+CFUN=4,0" +#define SET_AIRPLANE_MODE_CMD "AT+CFUN=4,1" +#define SET_FULLFUNCTIONAL_MODE_CMD_LEGACY "AT+CFUN=1,0" +#define SET_FULLFUNCTIONAL_MODE_CMD "AT+CFUN=1,1" +#define MDM_POWER_OFF_CMD_LEGACY "AT+CPWROFF" +#define MDM_POWER_FAST_OFF_CMD_LEGACY "AT+CPWROFF=1" +#define GET_FULLFUNCTIONAL_MODE_CMD "AT+CFUN?" +/* PDP Context commands */ +#define DEACTIVATE_PDP_CONTEXT "AT+CGACT=0" +#define ACTIVATE_PDP_CONTEXT "AT+CGACT=1" + +/* Helper macros */ +#define ATOI(s_, value_, desc_) modem_atoi(s_, value_, desc_, __func__) +#define ATOD(s_, value_, desc_) modem_atod(s_, value_, desc_, __func__) + +/* Enums */ + +enum hl78xx_gnss_event { + HL78XX_GNSS_EVENT_INVALID = -1, + HL78XX_GNSS_EVENT_INIT, + HL78XX_GNSS_EVENT_START, + HL78XX_GNSS_EVENT_STOP, + HL78XX_GNSS_EVENT_POSITION, +}; + +enum hl78xx_gnss_status { + HL78XX_GNSS_STATUS_INVALID = -1, + HL78XX_GNSS_STATUS_FAILURE, + HL78XX_GNSS_STATUS_SUCCESS, +}; + +enum hl78xx_gnss_position_event { + HL78XX_GNSS_POSITION_EVENT_INVALID = -1, + HL78XX_GNSS_POSITION_EVENT_LOST_OR_NOT_AVAILABLE_YET, + HL78XX_GNSS_POSITION_EVENT_PREDICTION_AVAILABLE, + HL78XX_GNSS_POSITION_EVENT_2D_AVAILABLE, + HL78XX_GNSS_POSITION_EVENT_3D_AVAILABLE, + HL78XX_GNSS_POSITION_EVENT_FIXED_TO_INVALID, + HL78XX_GNSS_POSITION_EVENT_SATELLITE_TIMEOUT, +}; + +enum hl78xx_state { + MODEM_HL78XX_STATE_IDLE = 0, + MODEM_HL78XX_STATE_RESET_PULSE, + MODEM_HL78XX_STATE_POWER_ON_PULSE, + MODEM_HL78XX_STATE_AWAIT_POWER_ON, + MODEM_HL78XX_STATE_SET_BAUDRATE, + MODEM_HL78XX_STATE_RUN_INIT_SCRIPT, + MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT, + MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT, + MODEM_HL78XX_STATE_RUN_PMC_CONFIG_SCRIPT, + MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT, + /* Full functionality, searching + * CFUN=1 + */ + MODEM_HL78XX_STATE_AWAIT_REGISTERED, + MODEM_HL78XX_STATE_AWAIT_WAKEUP, + MODEM_HL78XX_STATE_CARRIER_ON, + /* Minimum functionality, SIM powered off, Modem Power down + * CFUN=0 + */ + MODEM_HL78XX_STATE_CARRIER_OFF, + MODEM_HL78XX_STATE_SIM_POWER_OFF, + /* Minimum functionality / Airplane mode + * Sim still powered on + * CFUN=4 + */ + MODEM_HL78XX_STATE_AIRPLANE, + MODEM_HL78XX_STATE_INIT_POWER_OFF, + MODEM_HL78XX_STATE_POWER_OFF_PULSE, + MODEM_HL78XX_STATE_AWAIT_POWER_OFF, +}; + +enum hl78xx_event { + MODEM_HL78XX_EVENT_RESUME = 0, + MODEM_HL78XX_EVENT_SUSPEND, + MODEM_HL78XX_EVENT_SCRIPT_SUCCESS, + MODEM_HL78XX_EVENT_SCRIPT_FAILED, + MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART, + MODEM_HL78XX_EVENT_TIMEOUT, + MODEM_HL78XX_EVENT_REGISTERED, + MODEM_HL78XX_EVENT_DEREGISTERED, + MODEM_HL78XX_EVENT_BUS_OPENED, + MODEM_HL78XX_EVENT_BUS_CLOSED, + MODEM_HL78XX_EVENT_SOCKET_READY, + MODEM_HL78XX_EVENT_SOCKET_CLOSED, + MODEM_HL78XX_EVENT_DEVICE_AWAKE, + MODEM_HL78XX_EVENT_DEVICE_ASLEEP, +}; + +enum hl78xx_pmc_modes { + HL78XX_PMC_MODE_EDRX, + HL78XX_PMC_MODE_PSM, + HL78XX_PMC_MODE_POWER_DOWN, + HL78XX_PMC_MODE_NONE +}; + +enum hl78xx_tcp_notif { + TCP_NOTIF_NETWORK_ERROR = 0, + TCP_NOTIF_NO_MORE_SOCKETS = 1, + TCP_NOTIF_MEMORY_PROBLEM = 2, + TCP_NOTIF_DNS_ERROR = 3, + TCP_NOTIF_REMOTE_DISCONNECTION = 4, + TCP_NOTIF_CONNECTION_ERROR = 5, + TCP_NOTIF_GENERIC_ERROR = 6, + TCP_NOTIF_ACCEPT_FAILED = 7, + TCP_NOTIF_SEND_MISMATCH = 8, + TCP_NOTIF_BAD_SESSION_ID = 9, + TCP_NOTIF_SESSION_ALREADY_RUNNING = 10, + TCP_NOTIF_ALL_SESSIONS_USED = 11, + TCP_NOTIF_CONNECTION_TIMEOUT = 12, + TCP_NOTIF_SSL_CONNECTION_ERROR = 13, + TCP_NOTIF_SSL_INIT_ERROR = 14, + TCP_NOTIF_SSL_CERT_ERROR = 15 +}; + +enum hl78xx_kedrx_mode { + HL78XX_KEDRX_MODE_DISABLE = 0, + HL78XX_KEDRX_MODE_ENABLE = 1, + HL78XX_KEDRX_MODE_ENABLE_W_URC = 2, + HL78XX_KEDRX_MODE_DISABLE_AND_ERASE_CFG = 3, +}; + +enum hl78xx_kedrx_ack_type { + HL78XX_KEDRX_ACK_TYPE_CATM = 4, + HL78XX_KEDRX_ACK_TYPE_NB = 5, +}; + +struct kselacq_syntax { + bool mode; + enum hl78xx_cell_rat_mode rat1; + enum hl78xx_cell_rat_mode rat2; + enum hl78xx_cell_rat_mode rat3; +}; + +struct kband_syntax { + uint8_t rat; + /* Max 64 digits representation format is supported + * i.e: LTE Band 256 (2000MHz) : + * 80000000 00000000 00000000 00000000 + * 00000000 00000000 00000000 00000000 + * + + * NULL terminate + */ + uint8_t bnd_bitmap[MDM_BAND_HEX_STR_LEN]; +}; + +struct ksleep_syntax { + uint8_t mngt; + uint8_t level; + uint8_t delay; +}; + +struct power_down_syntax { + bool requested_previously; + bool requested_currently; + bool status_currently; + bool status_previously; +}; + +struct cpsms_syntax { + /* Indication to disable or enable the use of PSM in the UE; */ + bool mode; + /* TAU value (T3412) */ + uint8_t periodic_tau; + /* Active Time value (T3324) */ + uint8_t active_time; +}; + +struct kedrxcfg_syntax { + enum hl78xx_kedrx_mode mode; + enum hl78xx_kedrx_ack_type ack_type; + uint8_t requested_edrx; +}; + +struct registration_status { + bool is_registered_currently; + bool is_registered_previously; + enum hl78xx_registration_status network_state_current; + enum hl78xx_registration_status network_state_previous; + enum hl78xx_cell_rat_mode rat_mode; +}; +struct hl78xx_psm_status { + enum hl78xx_psmev_event psmev_current; + enum hl78xx_psmev_event psmev_previous; + bool is_psm_active; +}; +/* driver data */ +struct modem_buffers { + uint8_t uart_rx[CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES]; + uint8_t uart_tx[CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES]; + uint8_t chat_rx[CONFIG_MODEM_HL78XX_CHAT_BUFFER_SIZES]; + uint8_t *delimiter; + uint8_t *filter; + uint8_t *argv[32]; + uint8_t *eof_pattern; + uint8_t eof_pattern_size; +}; + +struct modem_identity { + uint8_t imei[MDM_IMEI_LENGTH]; + uint8_t model_id[MDM_MODEL_LENGTH]; + uint8_t imsi[MDM_IMSI_LENGTH]; + uint8_t iccid[MDM_ICCID_LENGTH]; + uint8_t manufacturer[MDM_MANUFACTURER_LENGTH]; + uint8_t fw_version[MDM_REVISION_LENGTH]; + char apn[MDM_APN_MAX_LENGTH]; +}; + +struct modem_status { + struct registration_status registration; + int16_t rssi; + uint8_t ksrep; + int16_t rsrp; + int16_t rsrq; + uint16_t script_fail_counter; + int variant; + enum hl78xx_state state; + enum hl78xx_phone_functionality phone_functionality; + struct hl78xx_psm_status psm; + struct kband_syntax kbndcfg[HL78XX_RAT_COUNT]; + /* Power Management Control */ + struct ksleep_syntax pmc_sleep; + struct cpsms_syntax pmc_cpsms; + struct kedrxcfg_syntax pmc_kedrxcfg[2]; + struct power_down_syntax pmc_power_down; +}; + +struct modem_gpio_callbacks { + struct gpio_callback vgpio_cb; + struct gpio_callback uart_dsr_cb; + struct gpio_callback gpio6_cb; + struct gpio_callback uart_cts_cb; +}; + +struct modem_event_system { + struct k_work event_dispatch_work; + uint8_t event_buf[8]; + struct ring_buf event_rb; + struct k_mutex event_rb_lock; +}; + +struct hl78xx_data { + struct modem_pipe *uart_pipe; + struct modem_backend_uart uart_backend; + struct modem_chat chat; + + struct k_mutex tx_lock; + struct k_sem script_stopped_sem_tx_int; + struct k_sem script_stopped_sem_rx_int; + struct k_sem suspended_sem; + + struct modem_buffers buffers; + struct modem_identity identity; + struct modem_status status; + struct modem_gpio_callbacks gpio_cbs; + struct modem_event_system events; + + struct k_work_delayable timeout_work; +#ifdef CONFIG_MODEM_HL78XX_POWER_DOWN + struct k_work_delayable hl78xx_pwr_dwn_work; +#endif +#if defined(CONFIG_MODEM_HL78XX_RSSI_WORK) + struct k_work_delayable rssi_query_work; +#endif + + const struct device *dev; + struct kselacq_syntax kselacq_data; +}; + +struct hl78xx_config { + const struct device *uart; + struct gpio_dt_spec mdm_gpio_reset; + struct gpio_dt_spec mdm_gpio_wake; + struct gpio_dt_spec mdm_gpio_pwr_on; + struct gpio_dt_spec mdm_gpio_fast_shtdown; + struct gpio_dt_spec mdm_gpio_uart_dtr; + struct gpio_dt_spec mdm_gpio_uart_dsr; + struct gpio_dt_spec mdm_gpio_uart_cts; + struct gpio_dt_spec mdm_gpio_vgpio; + struct gpio_dt_spec mdm_gpio_gpio6; + struct gpio_dt_spec mdm_gpio_gpio8; + struct gpio_dt_spec mdm_gpio_sim_switch; + uint16_t power_pulse_duration_ms; + uint16_t reset_pulse_duration_ms; + uint16_t startup_time_ms; + uint16_t shutdown_time_ms; + + bool autostarts; + + const struct modem_chat_script *init_chat_script; + const struct modem_chat_script *periodic_chat_script; +}; +/* socket read callback data */ +struct socket_read_data { + char *recv_buf; + size_t recv_buf_len; + struct sockaddr *recv_addr; + uint16_t recv_read_len; +}; + +/** + * @brief Check if the cellular modem is registered on the network. + * + * This function checks the modem's current registration status and + * returns true if the device is registered with a cellular network. + * + * @param data Pointer to the modem HL78xx driver data structure. + * + * @retval true if the modem is registered. + * @retval false otherwise. + */ +bool hl78xx_is_registered(struct hl78xx_data *data); + +/** + * @brief Generate a 32-bit hash from a string. + * + * Useful for generating identifiers (e.g., MAC address suffix) from a given string. + * + * @param str Input string to hash. + * @param len Length of the input string. + * + * @return 32-bit hash value. + */ +uint32_t hash32(const char *str, int len); + +/** + * @brief DNS resolution work callback. + * + * @param reset If true, resets the DNS resolver state. + * Should be used internally to handle DNS resolution events. + */ +void dns_work_cb(bool reset); + +/** + * @brief Callback to update and handle network interface status. + * + * This function is typically scheduled as work to check and respond to changes + * in the modem's network interface state, such as registration, link readiness, + * or disconnection events. + * + * @param data Pointer to the modem HL78xx driver data structure. + */ +void iface_status_work_cb(struct hl78xx_data *data, + modem_chat_script_callback script_user_callback); + +/** + * @brief Convert a string to an integer with error handling. + * + * Similar to atoi, but allows specifying an error fallback and logs errors. + * + * @param s Input string to convert. + * @param err_value Value to return on failure. + * @param desc Description of the value for logging purposes. + * @param func Function name for logging purposes. + * + * @return Converted integer on success, or err_value on failure. + */ +int modem_atoi(const char *s, const int err_value, const char *desc, const char *func); + +/** + * @brief Convert a string to an double with error handling. + * + * Similar to atoi, but allows specifying an error fallback and logs errors. + * + * @param s Input string to convert. + * @param err_value Value to return on failure. + * @param desc Description of the value for logging purposes. + * @param func Function name for logging purposes. + * + * @return Converted double on success, or err_value on failure. + */ +double modem_atod(const char *s, const double err_value, const char *desc, const char *func); + +/** + * @brief Initialize sockets for the modem. + * + * Sets up the socket table and configuration for socket communication. + * + * @param data Pointer to the modem HL78xx driver data structure. + */ +void hl78xx_socket_init(struct hl78xx_data *data); + +/** + * @brief Notify the system of socket data changes. + * + * Typically used when data has been received or transmitted on a socket. + * + * @param socket_id ID of the affected socket. + * @param new_total New data count or buffer level associated with the socket. + */ +void socket_notify_data(int socket_id, int new_total); + +/** + * @brief Notify the system of tcp socket changes. + * + * Typically used when tcp connection failure has been received on a socket. + * + * @param socket_id ID of the affected socket. + * @param tcp_notif Integer type. Indicates the cause of the TCP connection failure. + */ +void tcp_notify_data(int socket_id, int tcp_notif); + +/** + * @brief Send a command to the modem and wait for matching response(s). + * + * This function sends a raw command to the modem and processes its response using + * the provided match patterns. It supports asynchronous notification via callback. + * + * @param data Pointer to the modem HL78xx driver data structure. + * @param script_user_callback Callback function invoked on matched responses or errors. + * @param cmd Pointer to the command buffer to send. + * @param cmd_len Length of the command in bytes. + * @param response_matches Array of expected response match patterns. + * @param matches_size Number of elements in the response_matches array. + * + * @return 0 on success, negative errno code on failure. + */ +int modem_cmd_send_int(struct hl78xx_data *data, modem_chat_script_callback script_user_callback, + const uint8_t *cmd, uint16_t cmd_len, + const struct modem_chat_match *response_matches, uint16_t matches_size, + bool user_cmd); + +/** + * @brief Find a memory block inside another block (C99-compatible version). + * + * Searches for the first occurrence of the byte string needle of length needlelen + * in the memory area haystack of length haystacklen. + * + * @param haystack Pointer to the memory block to search within. + * @param haystacklen Length of the haystack memory block. + * @param needle Pointer to the memory block to search for. + * @param needlelen Length of the needle memory block. + * + * @return Pointer to the beginning of the found needle, or NULL if not found. + */ +const void *c99_memmem(const void *haystack, size_t haystacklen, const void *needle, + size_t needlelen); + +/** + * @brief Generate a pseudo-random MAC address based on the modem's IMEI. + * + * This function creates a MAC address using a fixed prefix and a hash of the IMEI. + * The resulting address is consistent for the same IMEI and suitable for use + * in virtual or emulated network interfaces. + * + * @param mac_addr Pointer to a 6-byte buffer where the generated MAC address will be stored. + * @param imei Null-terminated string containing the modem's IMEI. + * + * @return Pointer to the MAC address buffer. + */ +static inline uint8_t *modem_get_mac(uint8_t *mac_addr, char *imei) +{ + uint32_t hash_value; + /* Define MAC address prefix */ + mac_addr[0] = 0x00; + mac_addr[1] = 0x10; + + /* Generate MAC address based on IMEI */ + hash_value = hash32(imei, strlen(imei)); + UNALIGNED_PUT(hash_value, (uint32_t *)(mac_addr + 2)); + + return mac_addr; +} + +/** + * @brief Handle modem state update from +KSTATE URC (unsolicited result code). + * + * This function is called when a +KSTATE URC is received, indicating a change + * in the modem's internal state. It updates the modem driver's state machine + * accordingly. + * + * @param data Pointer to the HL78xx modem driver data structure. + * @param state Integer value representing the new modem state as reported by the URC. + */ +void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state); + +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) +/** + * @brief Automatically detect and configure the modem's APN setting. + * + * Uses internal logic to determine the correct APN based on the modem's context + * and network registration information. + * + * @param data Pointer to the modem HL78xx driver data structure. + * @param associated_number Identifier (e.g., MCCMNC or IMSI) used for APN detection. + * + * @return 0 on success, negative errno code on failure. + */ +int modem_detect_apn(struct hl78xx_data *data, const char *associated_number); +#endif +/** + * @brief Get the default band configuration in hexadecimal string format for each band. + * + * Retrieves the modem's default band configuration as a hex string, + * used for configuring or restoring band settings. + * + * @param rat The radio access technology mode for which to get the band configuration. + * @param hex_bndcfg Buffer to store the resulting hex band configuration string. + * @param size_in_bytes Size of the buffer in bytes. + * + * @retval 0 on success. + * @retval Negative errno code on failure. + */ +int hl78xx_get_band_default_config_for_rat(enum hl78xx_cell_rat_mode rat, char *hex_bndcfg, + size_t size_in_bytes); +/** + * @brief Trim leading zeros from a hexadecimal string. + * + * Removes any '0' characters from the beginning of the provided hex string, + * returning a pointer to the first non-zero character. + * + * @param hex_str Null-terminated hexadecimal string. + * + * @return Pointer to the first non-zero digit in the string, + * or the last zero if the string is all zeros. + */ +const char *hl78xx_trim_leading_zeros(const char *hex_str); +/** + * @brief Convert a binary bitmap to a trimmed hexadecimal string. + * + * Converts a bitmap into a hex string, removing leading zeros for a + * compact representation. Useful for modem configuration commands. + * + * @param bitmap Pointer to the input binary bitmap. + * @param hex_str Output buffer for the resulting hex string. + * @param hex_str_len Size of the output buffer in bytes. + */ +void hl78xx_bitmap_to_hex_string_trimmed(const uint8_t *bitmap, char *hex_str, size_t hex_str_len); +/** + * @brief Convert a hexadecimal string to a binary bitmap. + * + * Parses a hexadecimal string and converts it into a binary bitmap array. + * + * @param hex_str Null-terminated string containing hexadecimal data. + * @param bitmap_out Output buffer to hold the resulting binary bitmap. + * + * @retval 0 on success. + * @retval Negative errno code on failure (e.g., invalid characters, overflow). + */ +/** + * @brief hl78xx_hex_string_to_bitmap - Brief description of the function. + * @param hex_str Description of hex_str. + * @param bitmap_out Description of bitmap_out. + * @return int Description of return value. + */ +int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out); + +/** + * @brief hl78xx_extract_essential_part_apn - Brief description of the function. + * @param full_apn Description of full_apn. + * @param essential_apn Description of essential_apn. + * @param max_len Description of max_len. + */ +void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len); + +/** + * @brief hl78xx_set_apn_internal - Brief description of the function. + * @param data Description of data. + * @param apn Description of apn. + * @param size Description of size. + * @return int Description of return value. + */ +int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size); + +/** + * @brief hl78xx_api_func_set_phone_functionality - Brief description of the function. + * @param dev Description of dev. + * @param functionality Description of functionality. + * @param reset Description of reset. + * @return int Description of return value. + */ +int hl78xx_api_func_set_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset); + +/** + * @brief hl78xx_api_func_get_phone_functionality - Brief description of the function. + * @param dev Description of dev. + * @param functionality Description of functionality. + * @return int Description of return value. + */ +int hl78xx_api_func_get_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality *functionality); + +/** + * @brief hl78xx_api_func_get_signal - Brief description of the function. + * @param dev Description of dev. + * @param type Description of type. + * @param value Description of value. + * @return int Description of return value. + */ +int hl78xx_api_func_get_signal(const struct device *dev, const enum hl78xx_signal_type type, + int16_t *value); + +/** + * @brief hl78xx_api_func_get_registration_status - Brief description of the function. + * @param dev Description of dev. + * @param tech Description of tech. + * @param status Description of status. + * @return int Description of return value. + */ +int hl78xx_api_func_get_registration_status(const struct device *dev, + enum hl78xx_cell_rat_mode *tech, + enum hl78xx_registration_status *status); + +/** + * @brief hl78xx_api_func_get_modem_info - Brief description of the function. + * @param dev Description of dev. + * @param type Description of type. + * @param info Description of info. + * @param size Description of size. + * @return int Description of return value. + */ +int hl78xx_api_func_get_modem_info(const struct device *dev, enum hl78xx_modem_info_type type, + char *info, size_t size); + +/** + * @brief hl78xx_api_func_set_apn - Brief description of the function. + * @param dev Description of dev. + * @param apn Description of apn. + * @param size Description of size. + * @return int Description of return value. + */ +int hl78xx_api_func_set_apn(const struct device *dev, const char *apn, uint16_t size); + +/** + * @brief hl78xx_api_func_modem_cmd_send_int - Brief description of the function. + * @param dev Description of dev. + * @param cmd Description of cmd. + * @param cmd_size Description of cmd_size. + * @param response_matches Description of response_matches. + * @param matches_size Description of matches_size. + * @return int Description of return value. + */ +int hl78xx_api_func_modem_cmd_send_int(const struct device *dev, const char *cmd, uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size); + +/** + * @brief hl78xx_enter_state - Brief description of the function. + * @param data Description of data. + * @param state Description of state. + */ +void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state); + +/** + * @brief hl78xx_delegate_event - Brief description of the function. + * @param data Description of data. + * @param evt Description of evt. + */ +void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt); + +/** + * @brief notif_carrier_off - Brief description of the function. + * @param void Description of void. + */ +void notif_carrier_off(void); + +/** + * @brief notif_carrier_on - Brief description of the function. + * @param void Description of void. + */ +void notif_carrier_on(void); + +/** + * @brief check_if_any_socket_connected - Brief description of the function. + * @param void Description of void. + * @return int Description of return value. + */ +int check_if_any_socket_connected(void); + +/** + * @brief binary_str_to_byte - Brief description of the function. + * @param bin_str Description of bin_str. + * @return int Description of return value. + */ +int binary_str_to_byte(const char *bin_str); + +/** + * @brief byte_to_binary_str - Brief description of the function. + * @param byte Description of byte. + * @param output Description of output. + */ +void byte_to_binary_str(uint8_t byte, char *output); + +/** + * @brief hl78xx_release_socket_comms - Brief description of the function. + * @param void Description of void. + */ +void hl78xx_release_socket_comms(void); + +/** + * @brief hl78xx_is_in_psm - Brief description of the function. + * @param data Description of data. + * @return bool Description of return value. + */ +bool hl78xx_is_in_psm(struct hl78xx_data *data); + +/** + * @brief hl78xx_is_in_pwr_dwn - Brief description of the function. + * @param data Description of data. + * @return bool Description of return value. + */ +bool hl78xx_is_in_pwr_dwn(struct hl78xx_data *data); + +/** + * @brief hl78xx_is_rsrp_valid - Brief description of the function. + * @param data Description of data. + * @return bool Description of return value. + */ +bool hl78xx_is_rsrp_valid(struct hl78xx_data *data); + +#endif /* HL78XX_H */ diff --git a/drivers/modem/hl78xx/hl78xx_apis.c b/drivers/modem/hl78xx/hl78xx_apis.c new file mode 100644 index 000000000000..8d798a8919ad --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_apis.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include +#include +#include +#include "hl78xx.h" + +LOG_MODULE_REGISTER(hl78xx_apis, CONFIG_MODEM_LOG_LEVEL); + +static void hl78xx_chat_callback_handler(struct modem_chat *chat, + enum modem_chat_script_result result, void *user_data) +{ + if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) { +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d RUN MODEM_CHAT_SCRIPT_RESULT_SUCCESS", __LINE__); +#endif + } else { +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d RUN MODEM_CHAT_SCRIPT_RESULT_FAIL", __LINE__); +#endif + } +} + +static void hl78xx_on_cmerror(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %s", __LINE__, __func__, argv[0]); +#endif +} + +static void hl78xx_on_ok(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %s", __LINE__, __func__, argv[0]); +#endif +} + +MODEM_CHAT_MATCH_DEFINE(ok_match, "OK", "", hl78xx_on_ok); +MODEM_CHAT_MATCHES_DEFINE(allow_match, MODEM_CHAT_MATCH("OK", "", hl78xx_on_ok), + MODEM_CHAT_MATCH("+CME ERROR: ", "", hl78xx_on_cmerror)); + +int hl78xx_api_func_get_signal(const struct device *dev, const enum hl78xx_signal_type type, + int16_t *value) +{ + int ret = -ENOTSUP; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + const char *signal_cmd_csq = "AT+CSQ"; + const char *signal_cmd_cesq = "AT+CESQ"; + + if (data->status.state != MODEM_HL78XX_STATE_CARRIER_ON) { + return -ENODATA; + } + + /* Run chat script */ + switch (type) { + case HL78XX_SIGNAL_RSSI: + ret = modem_cmd_send_int(data, hl78xx_chat_callback_handler, signal_cmd_csq, + strlen(signal_cmd_csq), allow_match, 2, true); + break; + + case HL78XX_SIGNAL_RSRP: + case HL78XX_SIGNAL_RSRQ: + ret = modem_cmd_send_int(data, hl78xx_chat_callback_handler, signal_cmd_cesq, + strlen(signal_cmd_cesq), allow_match, 2, true); + break; + + default: + ret = -ENOTSUP; + break; + } + + /* Verify chat script ran successfully */ + if (ret < 0) { + return ret; + } + + /* Parse received value */ + switch (type) { + case HL78XX_SIGNAL_RSSI: + ret = hl78xx_parse_rssi(data->status.rssi, value); + break; + + case HL78XX_SIGNAL_RSRP: + ret = hl78xx_parse_rsrp(data->status.rsrp, value); + break; + + case HL78XX_SIGNAL_RSRQ: + ret = hl78xx_parse_rsrq(data->status.rsrq, value); + break; + + default: + ret = -ENOTSUP; + break; + } + + return ret; +} + +int hl78xx_api_func_get_registration_status(const struct device *dev, + enum hl78xx_cell_rat_mode *tech, + enum hl78xx_registration_status *status) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + if (tech == NULL || status == NULL) { + return -EINVAL; + } + + *tech = data->status.registration.rat_mode; + *status = data->status.registration.network_state_current; + + return 0; +} + +int hl78xx_api_func_get_modem_info(const struct device *dev, enum hl78xx_modem_info_type type, + char *info, size_t size) +{ + int ret = 0; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + if (info == NULL || size == 0) { + return -EINVAL; + } + + switch (type) { + case HL78XX_MODEM_INFO_IMEI: + strncpy(info, data->identity.imei, MIN(size, sizeof(data->identity.imei))); + break; + case HL78XX_MODEM_INFO_SIM_IMSI: + strncpy(info, data->identity.imsi, MIN(size, sizeof(data->identity.imsi))); + break; + case HL78XX_MODEM_INFO_MANUFACTURER: + strncpy(info, data->identity.manufacturer, + MIN(size, sizeof(data->identity.manufacturer))); + break; + case HL78XX_MODEM_INFO_FW_VERSION: + strncpy(info, data->identity.fw_version, + MIN(size, sizeof(data->identity.fw_version))); + break; + case HL78XX_MODEM_INFO_MODEL_ID: + strncpy(info, data->identity.model_id, MIN(size, sizeof(data->identity.model_id))); + break; + case HL78XX_MODEM_INFO_SIM_ICCID: + strncpy(info, data->identity.iccid, MIN(size, sizeof(data->identity.iccid))); + break; + case HL78XX_MODEM_INFO_APN: + strncpy(info, data->identity.apn, MIN(size, sizeof(data->identity.apn))); + break; + default: + ret = -ENOTSUP; + break; + } + + return ret; +} + +int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size) +{ + int ret = 0; + char cmd_string[sizeof("AT+KCNXCFG=,\"\",\"\"") + sizeof(uint8_t) + + sizeof(MODEM_HL78XX_ADDRESS_FAMILY) + MDM_APN_MAX_LENGTH] = {0}; + int cmd_max_len = sizeof(cmd_string) - 1; + int apn_size = strlen(apn); + + if (apn == NULL || size >= MDM_APN_MAX_LENGTH) { + return -EINVAL; + } + + if (strncmp(data->identity.apn, apn, apn_size) != 0) { + strncpy(data->identity.apn, apn, apn_size); + } + /* check if the pdp is active, if yes, disable it first.*/ + /* Important: Deactivating all PDP contexts (e.g. by using AT+CGACT=0 with no + * parameters) also causes the device to detach from the network (equivalent to + * AT+CGATT=0) + * Theorically it is also equivalent to at+cfun=4 + * to keep sync use SET_AIRPLANE_MODE_CMD_LEGACY to deactivate pdp context if you have + * only one pdp context + */ + snprintk(cmd_string, cmd_max_len, "AT+CGDCONT=1,\"%s\",\"%s\"", MODEM_HL78XX_ADDRESS_FAMILY, + apn); + + ret = modem_cmd_send_int(data, NULL, cmd_string, strlen(cmd_string), &ok_match, 1, true); + if (ret < 0) { + goto error; + } + + snprintk(cmd_string, cmd_max_len, + "AT+KCNXCFG=1,\"GPRS\",\"%s\",,,\"" MODEM_HL78XX_ADDRESS_FAMILY "\"", apn); + + ret = modem_cmd_send_int(data, NULL, cmd_string, strlen(cmd_string), &ok_match, 1, true); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +int hl78xx_api_func_set_apn(const struct device *dev, const char *apn, uint16_t size) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + if (apn == NULL || size > MDM_APN_MAX_LENGTH) { + return -EINVAL; + } + + strncpy(data->identity.apn, apn, sizeof(data->identity.apn)); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_OFF); + return 0; +} + +int hl78xx_api_func_set_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset) +{ + char cmd_string[sizeof(SET_FULLFUNCTIONAL_MODE_CMD) + sizeof(int)] = {0}; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + /* configure modem fully fuctinal without restart */ + + snprintf(cmd_string, sizeof(cmd_string), "AT+CFUN=%d,%d", functionality, reset); + + return modem_cmd_send_int(data, NULL, cmd_string, strlen(cmd_string), &ok_match, 1, true); +} + +int hl78xx_api_func_get_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality *functionality) +{ + const char *cmd_string = GET_FULLFUNCTIONAL_MODE_CMD; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + /* configure modem fully fuctinal without restart */ + return modem_cmd_send_int(data, NULL, cmd_string, strlen(cmd_string), &ok_match, 1, true); +} + +int hl78xx_api_func_modem_cmd_send_int(const struct device *dev, const char *cmd, uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size) +{ + + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + return modem_cmd_send_int(data, NULL, cmd, cmd_size, response_matches, 1, true); +} diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt b/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt new file mode 100644 index 000000000000..ea00a6b9cef7 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# + +zephyr_library() +zephyr_library_sources(hl78xx_evt_monitor.c) +# Event monitors data must be in RAM +zephyr_linker_sources(RWDATA hl78xx_evt_monitor.ld) diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor b/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor new file mode 100644 index 000000000000..26547fe3b579 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor @@ -0,0 +1,29 @@ +# +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig HL78XX_EVT_MONITOR + bool "HL78XX event notification monitor" + +if HL78XX_EVT_MONITOR + +config HL78XX_EVT_MONITOR_HEAP_SIZE + int "Heap size for notifications" + range 64 4096 + default 256 + +config HL78XX_EVT_MONITOR_APP_INIT_PRIORITY + int "Sierra Wireless HL78XX event monitor app init priority" + default 0 + help + Sierra Wireless HL78XX event monitor app initialization priority. + Do not mess with it unless you know what you are doing. + +module=HL78XX_EVT_MONITOR +module-dep=LOG +module-str= Event notification monitor library +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # HL78XX_EVT_MONITOR diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c new file mode 100644 index 000000000000..14f1d2bd8100 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(hl78xx_evt_monitor, CONFIG_HL78XX_EVT_MONITOR_LOG_LEVEL); + +struct evt_notif_fifo { + void *fifo_reserved; + struct hl78xx_evt data; +}; + +static void hl78xx_evt_monitor_task(struct k_work *work); + +static K_FIFO_DEFINE(hl78xx_evt_monitor_fifo); +static K_HEAP_DEFINE(hl78xx_evt_monitor_heap, CONFIG_HL78XX_EVT_MONITOR_HEAP_SIZE); +static K_WORK_DEFINE(hl78xx_evt_monitor_work, hl78xx_evt_monitor_task); + +static bool is_paused(const struct hl78xx_evt_monitor_entry *mon) +{ + return mon->flags.paused; +} + +static bool is_direct(const struct hl78xx_evt_monitor_entry *mon) +{ + return mon->flags.direct; +} + +/* Dispatch EVT notifications immediately, or schedules a workqueue task to do that. + * Keep this function public so that it can be called by tests. + * This function is called from an ISR. + */ +void hl78xx_evt_monitor_dispatch(struct hl78xx_evt *notif) +{ + bool monitored; + struct evt_notif_fifo *evt_notif; + size_t sz_needed; + + __ASSERT_NO_MSG(notif != NULL); + + monitored = false; + STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) { + if (!is_paused(e)) { + if (is_direct(e)) { + LOG_DBG("Dispatching to %p (ISR)", e->handler); + e->handler(notif); + } else { + /* Copy and schedule work-queue task */ + monitored = true; + } + } + } + + if (!monitored) { + /* Only copy monitored notifications to save heap */ + return; + } + + sz_needed = sizeof(struct evt_notif_fifo) + sizeof(notif); + + evt_notif = k_heap_alloc(&hl78xx_evt_monitor_heap, sz_needed, K_NO_WAIT); + if (!evt_notif) { + LOG_WRN("No heap space for incoming notification: %d", notif->type); + __ASSERT(evt_notif, "No heap space for incoming notification: %d", notif->type); + return; + } + + evt_notif->data = *notif; + + k_fifo_put(&hl78xx_evt_monitor_fifo, evt_notif); + k_work_submit(&hl78xx_evt_monitor_work); +} + +static void hl78xx_evt_monitor_task(struct k_work *work) +{ + struct evt_notif_fifo *evt_notif; + + while ((evt_notif = k_fifo_get(&hl78xx_evt_monitor_fifo, K_NO_WAIT))) { + /* Dispatch notification with all monitors */ + LOG_DBG("EVT notif: %d", evt_notif->data.type); + STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) { + if (!is_paused(e) && !is_direct(e)) { + LOG_DBG("Dispatching to %p", e->handler); + e->handler(&evt_notif->data); + } + } + k_heap_free(&hl78xx_evt_monitor_heap, evt_notif); + } +} + +static int hl78xx_evt_monitor_sys_init(void) +{ + int err = 0; + + err = hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatch); + if (err) { + LOG_ERR("Failed to hook the dispatch function, err %d", err); + } + + return 0; +} + +/* Initialize during SYS_INIT */ +SYS_INIT(hl78xx_evt_monitor_sys_init, APPLICATION, CONFIG_HL78XX_EVT_MONITOR_APP_INIT_PRIORITY); diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld new file mode 100644 index 000000000000..e92793a5adc7 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld @@ -0,0 +1,5 @@ +/*HL78XX event monitors */ +. = ALIGN(4); +_hl78xx_evt_monitor_entry_list_start = .; +KEEP(*(SORT_BY_NAME("._hl78xx_evt_monitor_entry.*"))); +_hl78xx_evt_monitor_entry_list_end = .; diff --git a/drivers/modem/hl78xx/hl78xx_sockets.c b/drivers/modem/hl78xx/hl78xx_sockets.c new file mode 100644 index 000000000000..360b73d7ef26 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_sockets.c @@ -0,0 +1,1813 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "hl78xx.h" + +LOG_MODULE_REGISTER(hl78xx_socket, CONFIG_MODEM_LOG_LEVEL); + +/* Helper macros and constants */ +#define MODEM_STREAM_STARTER_WORD "\r\n" CONNECT_STRING "\r\n" +#define MODEM_STREAM_END_WORD "\r\n" OK_STRING "\r\n" + +#define MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT (0) + +#define DNS_SERVERS_COUNT \ + (0 + (IS_ENABLED(CONFIG_NET_IPV6) ? 1 : 0) + (IS_ENABLED(CONFIG_NET_IPV4) ? 1 : 0) + \ + 1 /* for NULL terminator */ \ + ) +#define L4_EVENT_MASK (NET_EVENT_DNS_SERVER_ADD | NET_EVENT_L4_DISCONNECTED) +#define CONN_LAYER_EVENT_MASK (NET_EVENT_CONN_IF_FATAL_ERROR) + +#define HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE 32 +#define HL78XX_MAC_ADDR_SIZE 6 + +RING_BUF_DECLARE(mdm_recv_pool, CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES); + +/* ---------------- Global Data Structures ---------------- */ +struct hl78xx_socket_data { + struct net_if *net_iface; + uint8_t mac_addr[HL78XX_MAC_ADDR_SIZE]; + + /* socket data */ + struct modem_socket_config socket_config; + struct modem_socket sockets[MDM_MAX_SOCKETS]; + int current_sock_fd; + int sizeof_socket_data; + int requested_socket_id; + + char dns_v4_string[NET_IPV4_ADDR_LEN]; + char dns_v6_string[NET_IPV6_ADDR_LEN]; + bool dns_ready; + struct in_addr ipv4Addr; + struct in_addr subnet; + struct in_addr gateway; + struct in_addr dns_v4; + struct in_addr new_ipv4_addr; + struct in6_addr dns_v6; + /* rx net buffer */ + struct ring_buf *buf_pool; + uint32_t expected_buf_len; + uint32_t collected_buf_len; + + struct hl78xx_data *mdata_global; + + struct net_mgmt_event_callback l4_cb; + struct net_mgmt_event_callback conn_cb; + + struct k_sem psm_cntrl_sem; + + bool socket_data_error; +}; +struct work_socket_data { + char buf[HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE]; + uint16_t len; +}; + +struct receive_socket_data { + char buf[MDM_MAX_DATA_LENGTH + ARRAY_SIZE(MODEM_STREAM_STARTER_WORD) + + ARRAY_SIZE(MODEM_STREAM_END_WORD)]; + uint16_t len; +}; + +struct work_socket_data work_buf; +struct receive_socket_data receive_buf; + +uint16_t match_len; + +bool match_connect_found; +bool match_eof_ok_found; +bool match_found; +bool socket_data_received; + +uint16_t start_index_eof; +uint16_t size_of_socketdata; +atomic_t state_leftover; +struct hl78xx_socket_data socket_data; + +static int offload_socket(int family, int type, int protom); +static int socket_close(struct modem_socket *sock); + +void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("KSTATEV: socket %d state %d", socket_data.current_sock_fd, state); +#endif + switch (state) { + case 1: /* CLOSED */ + LOG_DBG("Socket fd: %d closed by modem (KSTATEV: 2 1), resetting socket", + socket_data.current_sock_fd); + /* Mark socket as closed to block future I/O */ + /* Free socket resources */ + /* Set a global flag to indicate reconnect is needed */ + break; + case 3: + LOG_DBG("Socket %d connected", socket_data.current_sock_fd); + break; + case 5: + LOG_DBG("Data ready on socket %d", socket_data.current_sock_fd); + break; + default: + LOG_DBG("Unhandled KSTATEV for socket %d state %d", socket_data.current_sock_fd, + state); + break; + } +} + +static bool parse_ip(bool is_ipv4, const char *ip_str, void *out_addr) +{ + int ret = net_addr_pton(is_ipv4 ? AF_INET : AF_INET6, ip_str, out_addr); + + LOG_DBG("Parsing %s address: %s -> %s", is_ipv4 ? "IPv4" : "IPv6", ip_str, + (ret < 0) ? "FAIL" : "OK"); + if (ret < 0) { + LOG_ERR("Invalid IP address: %s", ip_str); + return false; + } + return true; +} + +static bool update_dns(bool is_ipv4, const char *dns_str) +{ + int ret; + + LOG_DBG("Updating DNS (%s): %s", is_ipv4 ? "IPv4" : "IPv6", dns_str); + + if (is_ipv4) { + ret = strncmp(dns_str, socket_data.dns_v4_string, strlen(dns_str)); + if (ret != 0) { + LOG_DBG("New IPv4 DNS differs from current, marking dns_ready = false"); + socket_data.dns_ready = false; + } + strncpy(socket_data.dns_v4_string, dns_str, sizeof(socket_data.dns_v4_string)); + socket_data.dns_v4_string[sizeof(socket_data.dns_v4_string) - 1] = '\0'; + return parse_ip(true, socket_data.dns_v4_string, &socket_data.dns_v4); + } +#ifdef CONFIG_NET_IPV6 + else { + ret = strncmp(dns_str, socket_data.dns_v6_string, strlen(dns_str)); + if (ret != 0) { + LOG_DBG("New IPv6 DNS differs from current, marking dns_ready = false"); + socket_data.dns_ready = false; + } + strncpy(socket_data.dns_v6_string, dns_str, sizeof(socket_data.dns_v6_string)); + socket_data.dns_v6_string[sizeof(socket_data.dns_v6_string) - 1] = '\0'; + + if (!parse_ip(false, socket_data.dns_v6_string, &socket_data.dns_v6)) { + return false; + } + + net_addr_ntop(AF_INET6, &socket_data.dns_v6, socket_data.dns_v6_string, + sizeof(socket_data.dns_v6_string)); + LOG_DBG("Parsed IPv6 DNS: %s", socket_data.dns_v6_string); + } +#endif + return true; +} + +bool is_valid_ipv4_addr(struct in_addr *addr) +{ + return addr && (addr->s_addr != 0); +} + +static void set_iface(bool is_ipv4, struct in6_addr *new_ipv6_addr, struct in6_addr *ipv6Addr) +{ + if (!socket_data.net_iface) { + LOG_DBG("No network interface set. Skipping iface config."); + return; + } + + LOG_DBG("Setting %s interface address...", is_ipv4 ? "IPv4" : "IPv6"); + + if (is_ipv4) { +#ifdef CONFIG_NET_IPV4 + if (is_valid_ipv4_addr(&socket_data.ipv4Addr)) { + net_if_ipv4_addr_rm(socket_data.net_iface, &socket_data.ipv4Addr); + } + + if (!net_if_ipv4_addr_add(socket_data.net_iface, &socket_data.new_ipv4_addr, + NET_ADDR_DHCP, 0)) { + LOG_ERR("Failed to set IPv4 interface address."); + } + + net_if_ipv4_set_netmask_by_addr(socket_data.net_iface, &socket_data.new_ipv4_addr, + &socket_data.subnet); + net_if_ipv4_set_gw(socket_data.net_iface, &socket_data.gateway); + + net_ipaddr_copy(&socket_data.ipv4Addr, &socket_data.new_ipv4_addr); + LOG_DBG("IPv4 interface configuration complete."); +#endif + } else { +#ifdef CONFIG_NET_IPV6 + net_if_ipv6_addr_rm(socket_data.net_iface, ipv6Addr); + + if (!net_if_ipv6_addr_add(socket_data.net_iface, new_ipv6_addr, NET_ADDR_AUTOCONF, + 0)) { + LOG_ERR("Failed to set IPv6 interface address."); + } else { + LOG_DBG("IPv6 interface configuration complete."); + } +#endif + } +} + +static bool split_ipv4_and_subnet(const char *combined, char *ip_out, size_t ip_out_len, + char *subnet_out, size_t subnet_out_len) +{ + int dot_count = 0; + const char *ptr = combined; + const char *split = NULL; + + while (*ptr && dot_count < 4) { + if (*ptr == '.') { + dot_count++; + if (dot_count == 4) { + split = ptr; + break; + } + } + ptr++; + } + + if (!split) { + LOG_ERR("Invalid IPv4 + subnet format: %s", combined); + return false; + } + + size_t ip_len = split - combined; + + if (ip_len >= ip_out_len) { + ip_len = ip_out_len - 1; + } + + strncpy(ip_out, combined, ip_len); + ip_out[ip_len] = '\0'; + + strncpy(subnet_out, split + 1, subnet_out_len); + subnet_out[subnet_out_len - 1] = '\0'; + + LOG_DBG("Extracted IP: %s, Subnet: %s", ip_out, subnet_out); + return true; +} + +void hl78xx_release_socket_comms(void) +{ + k_sem_give(&socket_data.psm_cntrl_sem); +} +static void hl78xx_send_wakeup_signal(void) +{ + hl78xx_delegate_event(socket_data.mdata_global, MODEM_HL78XX_EVENT_RESUME); +} + +static int validate_socket(const struct modem_socket *sock) +{ + + if (!sock) { + return -EINVAL; + } + + if (!sock->is_connected && sock->ip_proto != IPPROTO_UDP) { + errno = ENOTCONN; + return -1; + } + + return 0; +} + +static void hl78xx_on_kudpsnd(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct modem_socket *sock = NULL; + int id; + + /* look up new socket by special id */ + sock = modem_socket_from_newid(&socket_data.socket_config); + if (sock) { + id = ATOI(argv[1], -1, "socket_id"); + sock->is_connected = true; + + /* on error give up modem socket */ + if (modem_socket_id_assign(&socket_data.socket_config, sock, id) < 0) { + + modem_socket_put(&socket_data.socket_config, sock->sock_fd); + } + } +} + +/* Handler: +USOCR: [0] */ +static void hl78xx_on_cmd_sockcreate(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + struct modem_socket *sock = NULL; + int socket_id; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + /* look up new socket by special id */ + sock = modem_socket_from_newid(&socket_data.socket_config); + if (sock) { + socket_id = ATOI(argv[1], -1, "socket_id"); + /* on error give up modem socket */ + if (modem_socket_id_assign(&socket_data.socket_config, sock, socket_id) < 0) { + modem_socket_put(&socket_data.socket_config, sock->sock_fd); + } + } + /* don't give back semaphore -- OK to follow */ +} + +static void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + if (argc < 8 || !argv[4] || !argv[5] || !argv[6]) { + LOG_ERR("Incomplete CGCONTRDP response: argc = %d", argc); + return; + } + + LOG_INF("CGCONTRDP: apn=%s addr=%s gw=%s dns=%s", argv[3], argv[4], argv[5], argv[6]); + + bool is_ipv4 = (strchr(argv[4], ':') == NULL); + + char ip_addr[NET_IPV6_ADDR_LEN]; + char subnet_mask[NET_IPV6_ADDR_LEN]; + +#ifdef CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK + bool is_apn_exists = (argv[3] && strlen(argv[3]) > 0); + + if (is_apn_exists) { + hl78xx_extract_essential_part_apn(argv[3], socket_data.mdata_global->identity.apn, + sizeof(socket_data.mdata_global->identity.apn)); + } +#endif + if (!split_ipv4_and_subnet(argv[4], ip_addr, sizeof(ip_addr), subnet_mask, + sizeof(subnet_mask))) { + return; + } + + LOG_INF("Extracted IP: %s, Subnet: %s", ip_addr, subnet_mask); + +#ifdef CONFIG_NET_IPV6 + struct in6_addr new_ipv6_addr = {0}; + struct in6_addr ipv6Addr = {0}; +#endif + + if (is_ipv4) { + if (!parse_ip(true, ip_addr, &socket_data.new_ipv4_addr)) { + return; + } + if (!parse_ip(true, subnet_mask, &socket_data.subnet)) { + return; + } + if (!parse_ip(true, argv[5], &socket_data.gateway)) { + return; + } + } else { +#ifdef CONFIG_NET_IPV6 + if (!parse_ip(false, ip_addr, &new_ipv6_addr)) { + return; + } +#endif + } + + if (!update_dns(is_ipv4, argv[6])) { + return; + } + +#ifdef CONFIG_NET_IPV6 + set_iface(is_ipv4, &new_ipv6_addr, &ipv6Addr); +#else + set_iface(is_ipv4, NULL, NULL); +#endif +} + +static void hl78xx_on_ktcpstate(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + if (argc < 4) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %s %s %s %s", __LINE__, argv[1], argv[2], argv[3], argv[4], argv[5]); +#endif + uint8_t tcp_session_id = ATOI(argv[1], 0, "tcp_session_id"); + uint8_t tcp_status = ATOI(argv[2], -1, "tcp_status"); + int8_t tcp_notif = ATOI(argv[3], -1, "tcp_notif"); + uint16_t rcv_data = ATOI(argv[5], 0, "tcp_rcv_data"); + + if (tcp_status != 3 && tcp_notif != -1) { + return; + } + + socket_notify_data(tcp_session_id, rcv_data); +} + +static void hl78xx_on_cme_error(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + if (argc < 2 || !argv[1]) { + LOG_ERR("CME ERROR: Incomplete response"); + return; + } + + int error_code = ATOI(argv[1], -1, "CME error code"); + + if (error_code < 0) { + LOG_ERR("Invalid CME error code: %d", error_code); + return; + } + socket_data.socket_data_error = true; + LOG_ERR("CME ERROR: %d", error_code); +} +MODEM_CHAT_MATCH_DEFINE(ok_match, "OK", "", NULL); +MODEM_CHAT_MATCHES_DEFINE(connect_matches, MODEM_CHAT_MATCH(CONNECT_STRING, "", NULL), + MODEM_CHAT_MATCH("+CME ERROR: ", "", hl78xx_on_cme_error)); +MODEM_CHAT_MATCHES_DEFINE(allow_matches, MODEM_CHAT_MATCH("OK", "", NULL), + MODEM_CHAT_MATCH("+CME ERROR: ", "", NULL)); +MODEM_CHAT_MATCH_DEFINE(kudpind_match, "+KUDP_IND: ", ",", hl78xx_on_kudpsnd); +MODEM_CHAT_MATCH_DEFINE(ktcpind_match, "+KTCP_IND: ", ",", hl78xx_on_kudpsnd); +MODEM_CHAT_MATCH_DEFINE(ktcp_match, "+KTCPCFG: ", "", hl78xx_on_cmd_sockcreate); +MODEM_CHAT_MATCH_DEFINE(cgdcontrdp_match, "+CGCONTRDP: ", ",", hl78xx_on_cgdcontrdp); +MODEM_CHAT_MATCH_DEFINE(ktcp_state_match, "+KTCPSTAT: ", ",", hl78xx_on_ktcpstate); + +static void parser_reset(void) +{ + memset(&receive_buf, 0, sizeof(receive_buf)); + match_found = false; +} + +static void found_reset(void) +{ + match_connect_found = false; + match_eof_ok_found = false; + socket_data_received = false; +} + +static bool modem_chat_parse_end_del_start(struct modem_chat *chat) +{ + if (receive_buf.len == 0) { + return false; + } + + for (uint8_t i = 0; i < chat->delimiter_size; i++) { + if (receive_buf.buf[receive_buf.len - 1] == chat->delimiter[i]) { + return true; + } + } + return false; +} + +static bool modem_chat_parse_end_del_complete(struct modem_chat *chat) +{ + if (receive_buf.len < chat->delimiter_size) { + return false; + } + + return memcmp(&receive_buf.buf[receive_buf.len - chat->delimiter_size], chat->delimiter, + chat->delimiter_size) == 0; +} + +static bool modem_chat_match_matches_received(const char *match, uint16_t match_size) +{ + if (receive_buf.len < match_size) { + return false; + } + + for (uint16_t i = 0; i < match_size; i++) { + if (match[i] != receive_buf.buf[i]) { + return false; + } + } + return true; +} + +static bool is_receive_buffer_full(void) +{ + return receive_buf.len >= ARRAY_SIZE(receive_buf.buf); +} + +static void handle_expected_length_decrement(void) +{ + if (match_connect_found && socket_data.expected_buf_len > 0) { + socket_data.expected_buf_len--; + } +} + +static bool is_end_delimiter_only(void) +{ + return receive_buf.len == socket_data.mdata_global->chat.delimiter_size; +} + +static bool is_valid_eof_index(uint8_t size_match) +{ + start_index_eof = receive_buf.len - size_match - 2; + return start_index_eof < ARRAY_SIZE(receive_buf.buf); +} + +static void try_handle_eof_pattern(void) +{ + uint8_t size_match = strlen(EOF_PATTERN); + + if (receive_buf.len < size_match + 2) { + return; + } + if (!is_valid_eof_index(size_match)) { + return; + } + + if (strncmp(&receive_buf.buf[start_index_eof], EOF_PATTERN, size_match) == 0) { + int ret = ring_buf_put(socket_data.buf_pool, receive_buf.buf, start_index_eof); + + if (ret <= 0) { + LOG_ERR("ring_buf_put failed: %d", ret); + } + socket_data_received = true; + socket_data.collected_buf_len += ret; + LOG_DBG("%d %d bytes received, total collected: %d %d", __LINE__, ret, + socket_data.collected_buf_len, start_index_eof); + } +} + +static bool is_cme_error_match(void) +{ + uint8_t size_match = strlen(CME_ERROR_STRING); + + return receive_buf.len >= size_match && + modem_chat_match_matches_received(CME_ERROR_STRING, size_match); +} + +static bool is_connect_match(void) +{ + uint8_t size_match = strlen(CONNECT_STRING); + + return receive_buf.len == size_match && + modem_chat_match_matches_received(CONNECT_STRING, size_match); +} + +static bool is_ok_match(void) +{ + uint8_t size_match = strlen(OK_STRING); + + return receive_buf.len == size_match && + modem_chat_match_matches_received(OK_STRING, size_match); +} + +static void socket_process_bytes(char byte) +{ + if (is_receive_buffer_full()) { + LOG_WRN("Receive buffer overrun"); + parser_reset(); + return; + } + + receive_buf.buf[receive_buf.len++] = byte; + handle_expected_length_decrement(); + + if (modem_chat_parse_end_del_complete(&socket_data.mdata_global->chat)) { + if (is_end_delimiter_only()) { + parser_reset(); + return; + } + + size_of_socketdata = receive_buf.len; + + if (match_connect_found && !match_eof_ok_found) { + try_handle_eof_pattern(); + } + + parser_reset(); + return; + } + + if (modem_chat_parse_end_del_start(&socket_data.mdata_global->chat)) { + return; + } + + if (!match_found && !match_connect_found) { + if (is_connect_match()) { + match_connect_found = true; + LOG_DBG("CONNECT matched. Expecting %d more bytes.", + socket_data.expected_buf_len); + return; + } else if (is_cme_error_match()) { + match_found = true; /* mark this to prevent further parsing */ + LOG_ERR("CME ERROR received. Connection failed."); + socket_data.expected_buf_len = 0; + socket_data.collected_buf_len = 0; + parser_reset(); + socket_data.socket_data_error = true; + /* Optionally notify script or raise an event here */ + k_sem_give(&socket_data.mdata_global->script_stopped_sem_rx_int); + return; + } + } + if (match_connect_found && !match_eof_ok_found && is_ok_match()) { + match_eof_ok_found = true; + LOG_DBG("OK matched."); + } +} + +static int modem_process_handler(void) +{ + if (socket_data.expected_buf_len == 0) { + LOG_DBG("No more data expected"); + atomic_set_bit(&state_leftover, MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT); + return 0; + } + + int recv_len = modem_pipe_receive(socket_data.mdata_global->uart_pipe, work_buf.buf, + MIN(sizeof(work_buf.buf), socket_data.expected_buf_len)); + if (recv_len <= 0) { + LOG_WRN("modem_pipe_receive returned %d", recv_len); + return recv_len; + } + + work_buf.len = recv_len; + LOG_HEXDUMP_DBG(work_buf.buf, recv_len, "Received bytes:"); + + for (int i = 0; i < recv_len; i++) { + socket_process_bytes(work_buf.buf[i]); + } + + if (match_eof_ok_found && socket_data_received) { + LOG_DBG("All data received: %d bytes", size_of_socketdata); + socket_data.expected_buf_len = 0; + found_reset(); + + k_sem_give(&socket_data.mdata_global->script_stopped_sem_rx_int); + } + + return 0; +} + +static void modem_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d Pipe event received: %d", __LINE__, event); +#endif + switch (event) { + case MODEM_PIPE_EVENT_RECEIVE_READY: + modem_process_handler(); + break; + + case MODEM_PIPE_EVENT_TRANSMIT_IDLE: + k_sem_give(&socket_data.mdata_global->script_stopped_sem_tx_int); + break; + + default: + LOG_DBG("Unhandled event: %d", event); + break; + } +} + +void notif_carrier_off(void) +{ + net_if_carrier_off(socket_data.net_iface); +} + +void notif_carrier_on(void) +{ + net_if_carrier_on(socket_data.net_iface); +} + +void iface_status_work_cb(struct hl78xx_data *data, modem_chat_script_callback script_user_callback) +{ + + char *cmd = "AT+CGCONTRDP=1"; + int ret = 0; + + ret = modem_cmd_send_int(data, script_user_callback, cmd, strlen(cmd), &cgdcontrdp_match, 1, + false); + if (ret < 0) { + LOG_ERR("Failed to send AT+CGCONTRDP command: %d", ret); + return; + } +} + +void dns_work_cb(bool reset) +{ +#if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES) + int ret; + struct dns_resolve_context *dnsCtx; + struct sockaddr temp_addr; + bool valid_address = false; + bool retry = false; + const char *const dns_servers_str[DNS_SERVERS_COUNT] = { +#ifdef CONFIG_NET_IPV6 + socket_data.dns_v6_string, +#endif +#ifdef CONFIG_NET_IPV4 + socket_data.dns_v4_string, +#endif + NULL}; + const char *dns_servers_wrapped[ARRAY_SIZE(dns_servers_str)]; + + if (reset) { + LOG_DBG("Resetting DNS resolver"); + dnsCtx = dns_resolve_get_default(); + if (dnsCtx->state != DNS_RESOLVE_CONTEXT_INACTIVE) { + dns_resolve_close(dnsCtx); + } + socket_data.dns_ready = false; + } + +#ifdef CONFIG_NET_IPV6 + valid_address = net_ipaddr_parse(socket_data.dns_v6_string, + strlen(socket_data.dns_v6_string), &temp_addr); + if (!valid_address && IS_ENABLED(CONFIG_NET_IPV4)) { + /* IPv6 DNS string is not valid, replace it with IPv4 address and recheck */ + strncpy(socket_data.dns_v6_string, socket_data.dns_v4_string, + sizeof(socket_data.dns_v4_string) - 1); + valid_address = net_ipaddr_parse(socket_data.dns_v6_string, + strlen(socket_data.dns_v6_string), &temp_addr); + } +#else + valid_address = net_ipaddr_parse(socket_data.dns_v4_string, + strlen(socket_data.dns_v4_string), &temp_addr); +#endif + if (!valid_address) { + LOG_WRN("No valid DNS address!"); + return; + } + if (!socket_data.net_iface || !net_if_is_up(socket_data.net_iface) || + socket_data.dns_ready) { + return; + } + + memcpy(dns_servers_wrapped, dns_servers_str, sizeof(dns_servers_wrapped)); + + /* set new DNS addr in DNS resolver */ + LOG_DBG("Refresh DNS resolver"); + dnsCtx = dns_resolve_get_default(); + if (dnsCtx->state == DNS_RESOLVE_CONTEXT_INACTIVE) { + LOG_DBG("Initializing DNS resolver"); + ret = dns_resolve_init(dnsCtx, dns_servers_wrapped, NULL); + } else { + LOG_DBG("Reconfiguring DNS resolver"); + ret = dns_resolve_reconfigure(dnsCtx, dns_servers_wrapped, NULL, DNS_SOURCE_MANUAL); + } + if (ret < 0) { + LOG_ERR("dns_resolve_reconfigure fail (%d)", ret); + retry = true; + } else { + LOG_DBG("DNS ready"); + socket_data.dns_ready = true; + } + if (retry) { + LOG_WRN("DNS not ready, scheduling a retry"); + } +#endif +} + +static int on_cmd_sockread_common(int socket_id, uint16_t socket_data_length, uint16_t len, + void *user_data) +{ + struct modem_socket *sock; + struct socket_read_data *sock_data; + int ret = 0; + int packet_size = 0; + + sock = modem_socket_from_fd(&socket_data.socket_config, socket_id); + if (!sock) { + LOG_ERR("Socket not found! (%d)", socket_id); + return -EINVAL; + } + + sock_data = sock->data; + if (!sock_data) { + LOG_ERR("Socket data missing! Ignoring (%d)", socket_id); + return -EINVAL; + } + if (socket_data.socket_data_error && socket_data.collected_buf_len == 0) { + errno = ECONNABORTED; + return -ECONNABORTED; + } + + if ((len <= 0) || socket_data_length <= 0 || socket_data.collected_buf_len < (size_t)len) { + LOG_ERR("%d Invalid data length: %d %d %d Aborting!", __LINE__, socket_data_length, + (int)len, socket_data.collected_buf_len); + return -EAGAIN; + } + + if (len < socket_data_length) { + LOG_DBG("Incomplete data received! Expected: %d, Received: %d", socket_data_length, + len); + return -EAGAIN; + } + + ret = ring_buf_get(socket_data.buf_pool, sock_data->recv_buf, len); + if (ret != len) { + LOG_ERR("Data retrieval mismatch: expected %u, got %d", len, ret); + return -EAGAIN; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_HEXDUMP_DBG(sock_data->recv_buf, ret, "Received Data:"); +#endif + + if (sock_data->recv_buf_len < (size_t)len) { + LOG_ERR("Buffer overflow! Received: %zu vs. Available: %zu", len, + sock_data->recv_buf_len); + return -EINVAL; + } + + if ((size_t)len != (size_t)socket_data_length) { + LOG_ERR("Data mismatch! Copied: %zu vs. Received: %d", len, socket_data_length); + return -EINVAL; + } + + sock_data->recv_read_len = len; + + /* Remove packet from list */ + packet_size = modem_socket_next_packet_size(&socket_data.socket_config, sock); + modem_socket_packet_size_update(&socket_data.socket_config, sock, -socket_data_length); + socket_data.collected_buf_len = 0; + + return len; +} + +int modem_handle_data_capture(size_t target_len, struct hl78xx_data *data) +{ + return on_cmd_sockread_common(socket_data.current_sock_fd, socket_data.sizeof_socket_data, + target_len, data); +} + +/* + * generic socket creation function + * which can be called in bind() or connect() + */ +static int create_socket(struct modem_socket *sock, const struct sockaddr *addr, + struct hl78xx_data *data) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + int ret; + int af; + char cmd_buf[sizeof("AT+KTCPCFG=#,#,\"" MODEM_HL78XX_ADDRESS_FAMILY_FORMAT + "\",#####,,,,#,,#") + + NET_IPV6_ADDR_LEN]; + char ip_str[NET_IPV6_ADDR_LEN]; + uint16_t dst_port = 0U; + bool udp_configproto = false; + uint8_t mode = 0; + + memcpy(&sock->dst, addr, sizeof(*addr)); + if (addr->sa_family == AF_INET6) { + dst_port = ntohs(net_sin6(addr)->sin6_port); + af = MDM_HL78XX_SOCKET_AF_IPV6; + } else if (addr->sa_family == AF_INET) { + dst_port = ntohs(net_sin(addr)->sin_port); + af = MDM_HL78XX_SOCKET_AF_IPV4; + } else { + errno = EAFNOSUPPORT; + return -1; + } + + if (sock->ip_proto == IPPROTO_UDP) { + udp_configproto = true; + } + + ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str)); + if (ret != 0) { + LOG_ERR("Failed to format IP!"); + errno = ENOMEM; + return -1; + } + if (!udp_configproto) { + snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCFG=%d,%d,\"%s\",%u,,,,%d,,%d", 1U, 0U, + ip_str, dst_port, af, 0U); + ret = modem_cmd_send_int(data, NULL, cmd_buf, strlen(cmd_buf), &ktcp_match, 1, + false); + if (ret < 0) { + goto error; + } + } else { + + uint8_t display_data_urc = 2; + +#if defined(CONFIG_MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC) + display_data_urc = CONFIG_MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC; +#endif + snprintk(cmd_buf, sizeof(cmd_buf), "AT+KUDPCFG=1,%u,,%d,,,%d,%d", mode, + display_data_urc, (addr->sa_family - 1), 0); + + ret = modem_cmd_send_int(data, NULL, cmd_buf, strlen(cmd_buf), &kudpind_match, 1, + false); + if (ret < 0) { + goto error; + } + } + errno = 0; + return 0; +error: + LOG_ERR("%s ret:%d", cmd_buf, ret); + modem_socket_put(&socket_data.socket_config, sock->sock_fd); + errno = ret; + return -1; +} + +static int socket_close(struct modem_socket *sock) +{ + char buf[sizeof("AT+KTCPCLOSE=##\r")]; + int ret = 0; + + if (sock->ip_proto == IPPROTO_UDP) { + snprintk(buf, sizeof(buf), "AT+KUDPCLOSE=%d", sock->id); + } else { + snprintk(buf, sizeof(buf), "AT+KTCPCLOSE=%d", sock->id); + } + + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, buf, strlen(buf), allow_matches, 2, + false); + if (ret < 0) { + LOG_ERR("%s ret:%d", buf, ret); + } + + return ret; +} + +static int socket_delete(struct modem_socket *sock) +{ + char buf[sizeof("AT+KTCPDEL=##\r")]; + int ret = 0; + + if (sock->ip_proto == IPPROTO_UDP) { + snprintk(buf, sizeof(buf), "AT+KUDPDEL=%d", sock->id); + } else { + snprintk(buf, sizeof(buf), "AT+KTCPDEL=%d", sock->id); + } + + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, buf, strlen(buf), allow_matches, 2, + false); + if (ret < 0) { + LOG_ERR("%s ret:%d", buf, ret); + } + + return ret; +} + +/* + * Socket Offload OPS + */ + +static int offload_socket(int family, int type, int proto) +{ + int ret; + /* defer modem's socket create call to bind() */ + ret = modem_socket_get(&socket_data.socket_config, family, type, proto); + if (ret < 0) { + errno = ret; + return -1; + } + + errno = 0; + return ret; +} + +static int offload_close(void *obj) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + /* make sure socket is allocated and assigned an id */ + if (modem_socket_id_is_assigned(&socket_data.socket_config, sock) == false) { + return 0; + } + if (validate_socket(sock) == 0) { + LOG_DBG("%d %d %d", __LINE__, + socket_data.mdata_global->status.pmc_power_down.requested_previously, + socket_data.mdata_global->status.pmc_power_down.status_previously); + if (!socket_data.mdata_global->status.pmc_power_down.requested_previously && + !socket_data.mdata_global->status.pmc_power_down.status_previously) { + socket_close(sock); + socket_delete(sock); + } + modem_socket_put(&socket_data.socket_config, sock->sock_fd); + sock->is_connected = false; + } + /* Consider here successfully socket is closed */ + return 0; +} + +static int offload_bind(void *obj, const struct sockaddr *addr, socklen_t addrlen) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + /* Save bind address information */ + memcpy(&sock->src, addr, sizeof(*addr)); + /* Check if socket is allocated */ + if (modem_socket_is_allocated(&socket_data.socket_config, sock)) { + /* Trigger socket creation */ + if (create_socket(sock, addr, socket_data.mdata_global) < 0) { + LOG_ERR("%d %s SOCKET CREATION", __LINE__, __func__); + return -1; + } + } + return 0; +} + +static int offload_connect(void *obj, const struct sockaddr *addr, socklen_t addrlen) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + int ret = 0; + int af; + char buf[sizeof("AT+KTCPCFG=#,#,\"" MODEM_HL78XX_ADDRESS_FAMILY_FORMAT + "\",#####,,,,#,,#\r")]; + char ip_str[NET_IPV6_ADDR_LEN]; + uint16_t dst_port = 0U; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + if (!addr) { + errno = EINVAL; + return -1; + } + if (!hl78xx_is_registered(socket_data.mdata_global)) { + errno = ENETUNREACH; + return -1; + } + + if (validate_socket(sock) == 0) { + LOG_ERR("Socket is already connected! id: %d, fd: %d", sock->id, sock->sock_fd); + errno = EISCONN; + return -1; + } + + /* make sure socket has been allocated */ + if (modem_socket_is_allocated(&socket_data.socket_config, sock) == false) { + LOG_ERR("Invalid socket_id(%d) from fd:%d", sock->id, sock->sock_fd); + errno = EINVAL; + return -1; + } + + /* make sure we've created the socket */ + if (modem_socket_id_is_assigned(&socket_data.socket_config, sock) == false && + sock->ip_proto == IPPROTO_UDP) { + LOG_DBG("%d no socket assigned", __LINE__); + if (create_socket(sock, addr, socket_data.mdata_global) < 0) { + return -1; + } + } + + memcpy(&sock->dst, addr, sizeof(*addr)); + if (addr->sa_family == AF_INET6) { + af = MDM_HL78XX_SOCKET_AF_IPV6; + dst_port = ntohs(net_sin6(addr)->sin6_port); + } else if (addr->sa_family == AF_INET) { + af = MDM_HL78XX_SOCKET_AF_IPV4; + dst_port = ntohs(net_sin(addr)->sin_port); + } else { + errno = EAFNOSUPPORT; + return -1; + } + /* skip socket connect if UDP */ + if (sock->ip_proto == IPPROTO_UDP) { + errno = 0; + return 0; + } + ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str)); + if (ret != 0) { + errno = -ret; + LOG_ERR("Error formatting IP string %d", ret); + return -1; + } + snprintk(buf, sizeof(buf), "AT+KTCPCFG=%d,%d,\"%s\",%u,,,,%d,,%d", 1U, 0U, ip_str, dst_port, + af, 0U); + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, buf, strlen(buf), &ktcp_match, 1, + false); + if (ret < 0) { + LOG_ERR("%s ret:%d", buf, ret); + errno = ret; + return -1; + } + + snprintk(buf, sizeof(buf), "AT+KTCPCNX=%d", sock->id); + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, buf, strlen(buf), &ok_match, 1, + false); + if (ret < 0) { + LOG_ERR("%s ret:%d", buf, ret); + errno = ret; + return -1; + } + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, "", 0, &ktcpind_match, 1, false); + if (ret) { + LOG_ERR("Error sending data %d", ret); + ret = ETIMEDOUT; + LOG_ERR("%d No TCP_IND received, ret: %d", __LINE__, ret); + return -1; + } + sock->is_connected = true; + errno = 0; + return 0; +} + +static bool validate_recv_args(void *buf, size_t len, int flags) +{ + if (!buf || len == 0) { + errno = EINVAL; + return false; + } + if (flags & ZSOCK_MSG_PEEK) { + errno = ENOTSUP; + return false; + } + return true; +} + +static int lock_socket_mutex(void) +{ + int ret = k_mutex_lock(&socket_data.mdata_global->tx_lock, K_SECONDS(1)); + + if (ret < 0) { + LOG_ERR("Failed to acquire TX lock: %d", ret); + errno = ret; + } + return ret; +} + +static int wait_for_data_if_needed(struct modem_socket *sock, int flags) +{ + int size = modem_socket_next_packet_size(&socket_data.socket_config, sock); + + if (size > 0) { + return size; + } + + if (flags & ZSOCK_MSG_DONTWAIT) { + errno = EAGAIN; + return -1; + } + + if (validate_socket(sock) == -1) { + errno = 0; + return 0; + } + + modem_socket_wait_data(&socket_data.socket_config, sock); + return modem_socket_next_packet_size(&socket_data.socket_config, sock); +} + +static void prepare_read_command(char *sendbuf, size_t bufsize, struct modem_socket *sock, + size_t read_size) +{ + snprintk(sendbuf, bufsize, "AT+K%sRCV=%d,%zd%s", + sock->ip_proto == IPPROTO_UDP ? "UDP" : "TCP", sock->id, read_size, + socket_data.mdata_global->chat.delimiter); +} + +static void setup_socket_data(struct modem_socket *sock, struct socket_read_data *sock_data, + void *buf, size_t len, struct sockaddr *from, uint16_t read_size) +{ + memset(sock_data, 0, sizeof(*sock_data)); + sock_data->recv_buf = buf; + sock_data->recv_buf_len = len; + sock_data->recv_addr = from; + sock->data = sock_data; + + socket_data.sizeof_socket_data = read_size; + socket_data.requested_socket_id = sock->id; + socket_data.current_sock_fd = sock->sock_fd; + socket_data.expected_buf_len = read_size + sizeof("\r\n") - 1 + + socket_data.mdata_global->buffers.eof_pattern_size + + sizeof(MODEM_STREAM_END_WORD) - 1; + socket_data.collected_buf_len = 0; + socket_data.socket_data_error = false; +} + +static void restore_socket_state(void) +{ + k_mutex_unlock(&socket_data.mdata_global->tx_lock); + modem_chat_attach(&socket_data.mdata_global->chat, socket_data.mdata_global->uart_pipe); + socket_data.expected_buf_len = 0; +} + +static void check_tcp_state_if_needed(struct modem_socket *sock) +{ + if (atomic_test_and_clear_bit(&state_leftover, MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT) && + sock->ip_proto == IPPROTO_TCP) { + const char *check_ktcp_stat = "AT+KTCPSTAT"; + + modem_cmd_send_int(socket_data.mdata_global, NULL, check_ktcp_stat, + strlen(check_ktcp_stat), &ktcp_state_match, 1, true); + } +} + +static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from, + socklen_t *fromlen) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + int ret; + char sendbuf[sizeof("AT+KUDPRCV=#,##########\r\n")]; + struct socket_read_data sock_data; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + if (!hl78xx_is_registered(socket_data.mdata_global) || validate_socket(sock) == -1) { + errno = EAGAIN; + return -1; + } + if (!validate_recv_args(buf, len, flags)) { + return -1; + } + + ret = lock_socket_mutex(); + if (ret < 0) { + return -1; + } + + int next_packet_size = wait_for_data_if_needed(sock, flags); + + if (next_packet_size <= 0) { + ret = next_packet_size; + goto exit; + } + + uint32_t max_data_length = + MDM_MAX_DATA_LENGTH - (socket_data.mdata_global->buffers.eof_pattern_size + + sizeof(MODEM_STREAM_STARTER_WORD) - 1); + next_packet_size = MIN(next_packet_size, max_data_length); + uint16_t read_size = MIN(next_packet_size, len); + + prepare_read_command(sendbuf, sizeof(sendbuf), sock, read_size); + setup_socket_data(sock, &sock_data, buf, len, from, read_size); + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d socket_fd: %d, socket_id: %d, expected_data_len: %d", __LINE__, + socket_data.current_sock_fd, socket_data.requested_socket_id, + socket_data.expected_buf_len); + LOG_HEXDUMP_DBG(sendbuf, strlen(sendbuf), "sending"); +#endif + + modem_chat_release(&socket_data.mdata_global->chat); + modem_pipe_attach(socket_data.mdata_global->uart_pipe, modem_pipe_callback, + socket_data.mdata_global); + + if (k_sem_take(&socket_data.mdata_global->script_stopped_sem_tx_int, K_FOREVER) < 0 || + modem_pipe_transmit(socket_data.mdata_global->uart_pipe, sendbuf, strlen(sendbuf)) < + 0 || + k_sem_take(&socket_data.mdata_global->script_stopped_sem_rx_int, K_FOREVER) < 0 || + modem_handle_data_capture(read_size, socket_data.mdata_global) < 0) { + ret = -1; + goto exit; + } + + if (from && fromlen) { + *fromlen = sizeof(sock->dst); + memcpy(from, &sock->dst, *fromlen); + } + + errno = 0; + ret = sock_data.recv_read_len; + +exit: +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d %d", __LINE__, errno, ret); +#endif + restore_socket_state(); + check_tcp_state_if_needed(sock); + return ret; +} + +int check_if_any_socket_connected(void) +{ + struct modem_socket_config *cfg = &socket_data.socket_config; + + k_sem_take(&cfg->sem_lock, K_FOREVER); + + for (int i = 0; i < cfg->sockets_len; i++) { + if (cfg->sockets[i].is_connected) { + /* if there is any socket connected*/ + return true; + } + } + + k_sem_give(&cfg->sem_lock); + return false; +} + +static int prepare_udp_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr, + size_t buf_len, char *cmd_buf, size_t cmd_buf_size) +{ + char ip_str[NET_IPV6_ADDR_LEN]; + uint16_t dst_port = 0; + int ret = 0; + + ret = modem_context_sprint_ip_addr(dst_addr, ip_str, sizeof(ip_str)); + if (ret < 0) { + LOG_ERR("Error formatting IP string %d", ret); + return ret; + } + + ret = modem_context_get_addr_port(dst_addr, &dst_port); + if (ret < 0) { + LOG_ERR("Error getting port from IP address %d", ret); + return ret; + } + + snprintk(cmd_buf, cmd_buf_size, "AT+KUDPSND=%d,\"%s\",%u,%zu", sock->id, ip_str, dst_port, + buf_len); + return 0; +} + +static int prepare_tcp_send_cmd(const struct modem_socket *sock, size_t buf_len, char *cmd_buf, + size_t cmd_buf_size) +{ + snprintk(cmd_buf, cmd_buf_size, "AT+KTCPSND=%d,%zu", sock->id, buf_len); + return 0; +} + +static int send_data_buffer(const char *buf, const size_t buf_len, int *sock_written) +{ + uint32_t offset = 0; + int len = buf_len; + int ret = 0; + + if (len == 0) { + LOG_DBG("%d No data to send", __LINE__); + return 0; + } + + while (len > 0) { + if (k_sem_take(&socket_data.mdata_global->script_stopped_sem_tx_int, K_FOREVER) < + 0) { + return -1; + } + + ret = modem_pipe_transmit(socket_data.mdata_global->uart_pipe, + ((const uint8_t *)buf) + offset, len); + if (ret <= 0) { + LOG_ERR("Transmit error %d", ret); + return -1; + } + + offset += ret; + len -= ret; + *sock_written += ret; + } + return 0; +} + +/* send binary data via the +KUDPSND/+KTCPSND commands */ +static ssize_t send_socket_data(void *obj, const struct sockaddr *dst_addr, const char *buf, + size_t buf_len, k_timeout_t timeout) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + char cmd_buf[82] = {0}; /* AT+KUDPSND/KTCP=,IP,PORT,LENGTH */ + int ret; + int sock_written = 0; + + ret = validate_socket(sock); + if (ret < 0) { + return ret; + } + if (!dst_addr && sock->ip_proto == IPPROTO_UDP) { + dst_addr = &sock->dst; + } + if (buf_len > MDM_MAX_DATA_LENGTH) { + if (sock->type == SOCK_DGRAM) { + errno = EMSGSIZE; + return -1; + } + buf_len = MDM_MAX_DATA_LENGTH; + } + if (sock->ip_proto == IPPROTO_UDP) { + ret = prepare_udp_send_cmd(sock, dst_addr, buf_len, cmd_buf, sizeof(cmd_buf)); + } else { + ret = prepare_tcp_send_cmd(sock, buf_len, cmd_buf, sizeof(cmd_buf)); + } + if (ret < 0) { + return -1; + } + + socket_data.socket_data_error = false; + + if (k_mutex_lock(&socket_data.mdata_global->tx_lock, K_SECONDS(1)) < 0) { + errno = ret; + return -1; + } + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, cmd_buf, strlen(cmd_buf), + connect_matches, ARRAY_SIZE(connect_matches), false); + if (ret < 0) { + LOG_ERR("Error sending AT command %d", ret); + } + if (socket_data.socket_data_error) { + ret = -ENODEV; + errno = ENODEV; + goto cleanup; + } + modem_pipe_attach(socket_data.mdata_global->chat.pipe, modem_pipe_callback, + &socket_data.mdata_global->chat); + + ret = send_data_buffer(buf, buf_len, &sock_written); + if (ret < 0) { + goto cleanup; + } + ret = k_sem_take(&socket_data.mdata_global->script_stopped_sem_tx_int, K_FOREVER); + if (ret < 0) { + goto cleanup; + } + ret = modem_pipe_transmit(socket_data.mdata_global->uart_pipe, + (uint8_t *)socket_data.mdata_global->buffers.eof_pattern, + socket_data.mdata_global->buffers.eof_pattern_size); + if (ret < 0) { + LOG_ERR("Error sending EOF pattern: %d", ret); + } + modem_chat_attach(&socket_data.mdata_global->chat, socket_data.mdata_global->uart_pipe); + ret = modem_cmd_send_int(socket_data.mdata_global, NULL, "", 0, &ok_match, 1, false); + if (ret < 0) { + LOG_ERR("Final confirmation failed: %d", ret); + goto cleanup; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d %d", __LINE__, sock_written, ret); +#endif +cleanup: + k_mutex_unlock(&socket_data.mdata_global->tx_lock); + return (ret < 0) ? -1 : sock_written; +} + +static ssize_t offload_sendto(void *obj, const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) +{ + int ret = 0; + struct modem_socket *sock = (struct modem_socket *)obj; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d", __LINE__, len); +#endif + if (!hl78xx_is_registered(socket_data.mdata_global)) { + LOG_ERR("Modem currently not attached to the network!"); + if (hl78xx_is_in_psm(socket_data.mdata_global) || + hl78xx_is_in_pwr_dwn(socket_data.mdata_global)) { + hl78xx_send_wakeup_signal(); + ret = k_sem_take(&socket_data.psm_cntrl_sem, K_SECONDS(55)); + if (ret == 0) { + LOG_DBG("Woke up modem from PSM"); + goto goon; + } else { + LOG_DBG("%d timeout waiting for modem wakeup", __LINE__); + } + } + errno = EAGAIN; + return -1; + } +goon: + /* Do some sanity checks. */ + if (!buf || len == 0) { + errno = EINVAL; + return -1; + } + /* Socket has to be connected. */ + if (!sock->is_connected) { + errno = ENOTCONN; + return -1; + } + /* Only send up to MTU bytes. */ + if (len > MDM_MAX_DATA_LENGTH) { + len = MDM_MAX_DATA_LENGTH; + } + + ret = send_socket_data(obj, to, buf, len, K_SECONDS(MDM_CMD_TIMEOUT)); + if (ret < 0) { + errno = ret; + return -1; + } + + errno = 0; + return ret; +} + +static int offload_ioctl(void *obj, unsigned int request, va_list args) +{ + int ret = 0; + struct modem_socket *sock = (struct modem_socket *)obj; +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %d", __LINE__, request); +#endif + switch (request) { + case ZFD_IOCTL_POLL_PREPARE: { + struct zsock_pollfd *pfd; + struct k_poll_event **pev; + struct k_poll_event *pev_end; + + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + pev_end = va_arg(args, struct k_poll_event *); +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("poll_prepare: fd=%d, events=0x%x", pfd->fd, pfd->events); +#endif + ret = modem_socket_poll_prepare(&socket_data.socket_config, obj, pfd, pev, pev_end); + + if (ret == -1 && errno == ENOTSUP && (pfd->events & ZSOCK_POLLOUT) && + sock->ip_proto == IPPROTO_UDP) { + /* Not Implemented */ + /* + * You can implement this later when needed + * For now, just ignore it + */ + errno = ENOTSUP; + ret = 0; + } + return ret; + } + case ZFD_IOCTL_POLL_UPDATE: { + struct zsock_pollfd *pfd; + struct k_poll_event **pev; + + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + return modem_socket_poll_update(obj, pfd, pev); + } + + case F_GETFL: + return 0; + case F_SETFL: { + int flags = va_arg(args, int); + + LOG_DBG("F_SETFL called with flags=0x%x", flags); + /* You can store flags if you want, but it's safe to just ignore them. */ + return 0; + } + default: + errno = EINVAL; + return -1; + } +} + +static ssize_t offload_read(void *obj, void *buffer, size_t count) +{ + return offload_recvfrom(obj, buffer, count, 0, NULL, 0); +} + +static ssize_t offload_write(void *obj, const void *buffer, size_t count) +{ + return offload_sendto(obj, buffer, count, 0, NULL, 0); +} + +static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags) +{ + ssize_t sent = 0; + struct iovec bkp_iovec = {0}; + struct msghdr crafted_msg = {.msg_name = msg->msg_name, .msg_namelen = msg->msg_namelen}; + size_t full_len = 0; + int ret; + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + /* Compute the full length to send and validate input */ + for (int i = 0; i < msg->msg_iovlen; i++) { + if (!msg->msg_iov[i].iov_base || msg->msg_iov[i].iov_len == 0) { + errno = EINVAL; + return -1; + } + full_len += msg->msg_iov[i].iov_len; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("msg_iovlen:%zd flags:%d, full_len:%zd", msg->msg_iovlen, flags, full_len); +#endif + while (full_len > sent) { + int removed = 0; + int i = 0; + int bkp_iovec_idx = -1; + + crafted_msg.msg_iovlen = msg->msg_iovlen; + crafted_msg.msg_iov = &msg->msg_iov[0]; + + /* Adjust iovec to remove already sent bytes */ + while (removed < sent) { + int to_remove = sent - removed; + + if (to_remove >= msg->msg_iov[i].iov_len) { + crafted_msg.msg_iovlen -= 1; + crafted_msg.msg_iov = &msg->msg_iov[i + 1]; + removed += msg->msg_iov[i].iov_len; + } else { + bkp_iovec_idx = i; + bkp_iovec = msg->msg_iov[i]; + + msg->msg_iov[i].iov_len -= to_remove; + msg->msg_iov[i].iov_base = + ((uint8_t *)msg->msg_iov[i].iov_base) + to_remove; + + removed += to_remove; + } + i++; + } + + ret = send_socket_data(obj, crafted_msg.msg_name, crafted_msg.msg_iov->iov_base, + crafted_msg.msg_iovlen, K_SECONDS(MDM_CMD_TIMEOUT)); + + if (bkp_iovec_idx != -1) { + msg->msg_iov[bkp_iovec_idx] = bkp_iovec; + } + + if (ret < 0) { + errno = ret; + return -1; + } + + sent += ret; + } + + return sent; +} +/* clang-format off */ +static const struct socket_op_vtable offload_socket_fd_op_vtable = { + .fd_vtable = { + .read = offload_read, + .write = offload_write, + .close = offload_close, + .ioctl = offload_ioctl, + }, + .bind = offload_bind, + .connect = offload_connect, + .sendto = offload_sendto, + .recvfrom = offload_recvfrom, + .listen = NULL, + .accept = NULL, + .sendmsg = offload_sendmsg, + .getsockopt = NULL, + .setsockopt = NULL, +}; +/* clang-format on */ +static int hl78xx_init_sockets(const struct device *dev) +{ + int ret; + + socket_data.buf_pool = &mdm_recv_pool; +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + k_sem_init(&socket_data.psm_cntrl_sem, 0, 1); + + /* socket config */ + ret = modem_socket_init(&socket_data.socket_config, &socket_data.sockets[0], + ARRAY_SIZE(socket_data.sockets), MDM_BASE_SOCKET_NUM, false, + &offload_socket_fd_op_vtable); + if (ret) { + goto error; + } + return 0; +error: + return ret; +} + +void socket_notify_data(int socket_id, int new_total) +{ + struct modem_socket *sock; + int ret = 0; + + sock = modem_socket_from_id(&socket_data.socket_config, socket_id); + if (!sock) { + return; + } + LOG_DBG("%d new total: %d sckid%d", __LINE__, new_total, socket_id); + ret = modem_socket_packet_size_update(&socket_data.socket_config, sock, new_total); + if (ret < 0) { + LOG_ERR("socket_id:%d left_bytes:%d err: %d", socket_id, new_total, ret); + } + if (new_total > 0) { + modem_socket_data_ready(&socket_data.socket_config, sock); + } +} + +void tcp_notify_data(int socket_id, int tcp_notif) +{ + enum hl78xx_tcp_notif tcp_notif_received = tcp_notif; + + socket_data.requested_socket_id = socket_id; + + switch (tcp_notif_received) { + case TCP_NOTIF_REMOTE_DISCONNECTION: + socket_notify_data(socket_id, 1); + break; + case TCP_NOTIF_NETWORK_ERROR: + /* Handle network error */ + break; + default: + break; + } +} + +static void l4_event_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, + struct net_if *iface) +{ + struct hl78xx_socket_data *data = CONTAINER_OF(cb, struct hl78xx_socket_data, l4_cb); + + ARG_UNUSED(data); + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d mgmt_event: %lld", __LINE__, mgmt_event); +#endif + + switch (mgmt_event) { + case NET_EVENT_DNS_SERVER_ADD: + /* Do something */ + LOG_INF("Network connectivity established and IP address assigned IPv4: %s IPv6: " + "%s", + data->dns_v4_string, data->dns_v6_string); + break; + case NET_EVENT_L4_CONNECTED: + /* Do something */ + break; + case NET_EVENT_L4_DISCONNECTED: + /* Do something */ + break; + default: + break; + } +} +static void connectivity_event_handler(struct net_mgmt_event_callback *cb, uint64_t event, + struct net_if *iface) +{ + if (event == CONN_LAYER_EVENT_MASK) { + LOG_ERR("Fatal error received from the connectivity layer"); + /* Do something */ + return; + } +} + +void hl78xx_socket_init(struct hl78xx_data *data) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + socket_data.mdata_global = data; + atomic_set(&state_leftover, 0); + + { + /* Setup handler for Zephyr NET Connection Manager events. */ + net_mgmt_init_event_callback(&socket_data.l4_cb, l4_event_handler, L4_EVENT_MASK); + net_mgmt_add_event_callback(&socket_data.l4_cb); + + /* Setup handler for Zephyr NET Connection Manager Connectivity layer. */ + net_mgmt_init_event_callback(&socket_data.conn_cb, connectivity_event_handler, + CONN_LAYER_EVENT_MASK); + net_mgmt_add_event_callback(&socket_data.conn_cb); + } +} + +static void modem_net_iface_init(struct net_if *iface) +{ + const struct device *dev = net_if_get_device(iface); + struct hl78xx_socket_data *data = dev->data; +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d", __LINE__); +#endif + net_if_set_link_addr( + iface, modem_get_mac(socket_data.mac_addr, socket_data.mdata_global->identity.imei), + sizeof(data->mac_addr), NET_LINK_ETHERNET); + data->net_iface = iface; + + hl78xx_init_sockets(dev); + net_if_socket_offload_set(iface, offload_socket); +} + +static struct offloaded_if_api api_funcs = { + .iface_api.init = modem_net_iface_init, +}; + +static bool offload_is_supported(int family, int type, int proto) +{ + return (family == AF_INET || family == AF_INET6) && + (type == SOCK_DGRAM || type == SOCK_STREAM) && + (proto == IPPROTO_TCP || proto == IPPROTO_UDP); +} + +#define MODEM_HL78XX_DEFINE_INSTANCE(inst) \ + NET_DEVICE_OFFLOAD_INIT(inst, "hl78xx_dev", NULL, NULL, &socket_data, NULL, \ + CONFIG_MODEM_HL78XX_OFFLOAD_INIT_PRIORITY, &api_funcs, \ + MDM_MAX_DATA_LENGTH); \ + NET_SOCKET_OFFLOAD_REGISTER(inst, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, AF_UNSPEC, \ + offload_is_supported, offload_socket); + +#define MODEM_DEVICE_SWIR_HL78XX(inst) MODEM_HL78XX_DEFINE_INSTANCE(inst) + +#define DT_DRV_COMPAT swir_hl7812 +DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT + +#define DT_DRV_COMPAT swir_hl7800 +DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT diff --git a/drivers/modem/hl78xx/hl78xx_utility.c b/drivers/modem/hl78xx/hl78xx_utility.c new file mode 100644 index 000000000000..7a9b9b0178d3 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_utility.c @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "hl78xx.h" + +#define ICCID_PREFIX_LEN 7 +#define IMSI_PREFIX_LEN 6 +#define MAX_BANDS 32 + +LOG_MODULE_REGISTER(hl78xx_utility, CONFIG_MODEM_LOG_LEVEL); + +/** + * @brief Convert string to long integer, but handle errors + * + * @param s: string with representation of integer number + * @param err_value: on error return this value instead + * @param desc: name the string being converted + * @param func: function where this is called (typically __func__) + * + * @retval return integer conversion on success, or err_value on error + */ +int modem_atoi(const char *s, const int err_value, const char *desc, const char *func) +{ + int ret; + char *endptr; + + ret = (int)strtol(s, &endptr, 10); + if (!endptr || *endptr != '\0') { + LOG_ERR("bad %s '%s' in %s", s, desc, func); + return err_value; + } + + return ret; +} +double modem_atod(const char *s, const double err_value, const char *desc, const char *func) +{ + double ret; + char *endptr; + + ret = strtod(s, &endptr); + if (!endptr || *endptr != '\0') { + LOG_ERR("bad %s '%s' in %s", s, desc, func); + return err_value; + } + + return ret; +} +bool hl78xx_is_registered(struct hl78xx_data *data) +{ + return (data->status.registration.network_state_current == + HL78XX_REGISTRATION_REGISTERED_HOME) || + (data->status.registration.network_state_current == + HL78XX_REGISTRATION_REGISTERED_ROAMING); +} + +bool hl78xx_is_rsrp_valid(struct hl78xx_data *data) +{ + return (data->status.rsrp >= CONFIG_MODEM_MIN_ALLOWED_SIGNAL_STRENGTH); +} + +bool hl78xx_is_in_psm(struct hl78xx_data *data) +{ + return (data->status.psm.psmev_current == HL78XX_PSM_EVENT_ENTER); +} + +bool hl78xx_is_in_pwr_dwn(struct hl78xx_data *data) +{ + return (data->status.pmc_power_down.requested_currently == true && + data->status.pmc_power_down.status_currently == true); +} + +#define HASH_MULTIPLIER 37 +uint32_t hash32(const char *str, int len) +{ + uint32_t h = 0; + + for (int i = 0; i < len; ++i) { + h = (h * HASH_MULTIPLIER) + str[i]; + } + + return h; +} + +/** + * Portable memmem() replacement for C99. + */ +const void *c99_memmem(const void *haystack, size_t haystacklen, const void *needle, + size_t needlelen) +{ + if (!haystack || !needle || needlelen == 0 || haystacklen < needlelen) { + return NULL; + } + + const uint8_t *h = haystack; + + for (size_t i = 0; i <= haystacklen - needlelen; i++) { + if (memcmp(h + i, needle, needlelen) == 0) { + return (h + i); + } + } + + return NULL; +} + +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) +int find_apn(const char *profile, const char *associated_number, char *apn_buff, uint8_t prefix_len) +{ + char buffer[512]; + char *saveptr; + + if (prefix_len > strlen(associated_number)) { + return -1; + } + + strncpy(buffer, profile, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + char *token = strtok_r(buffer, ",", &saveptr); + + while (token != NULL) { + char *equal_sign = strchr(token, '='); + + if (equal_sign != NULL) { + *equal_sign = '\0'; + char *p_apn = token; + char *associated_number_prefix = equal_sign + 1; + + /* Trim leading whitespace */ + while (*p_apn == ' ') { + p_apn++; + } + while (*associated_number_prefix == ' ') { + associated_number_prefix++; + } + + if (strncmp(associated_number, associated_number_prefix, prefix_len) == 0) { + strncpy(apn_buff, p_apn, MDM_APN_MAX_LENGTH - 1); + apn_buff[MDM_APN_MAX_LENGTH - 1] = '\0'; + return 0; + } + } + + token = strtok_r(NULL, ",", &saveptr); + } + + /* No match found, clear apn_buff */ + apn_buff[0] = '\0'; + return -1; /* not found */ +} + +/* try to detect APN automatically, based on IMSI / ICCID */ +int modem_detect_apn(struct hl78xx_data *data, const char *associated_number) +{ + int rc = -1; + + if (associated_number != NULL && strlen(associated_number) >= 5) { +/* extract MMC and MNC from IMSI */ +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) + /* + * First 5 digits (e.g. 31026) → often sufficient to identify carrier. + * However, in some regions (like the US), MNCs can be 3 digits (e.g. 310260). + */ + char mmcmnc[7] = {0}; /* IMSI */ +#define APN_PREFIX_LEN IMSI_PREFIX_LEN +#else + /* These 7 digits are generally sufficient to identify the SIM provider. + */ + char mmcmnc[8] = {0}; /* ICCID */ +#define APN_PREFIX_LEN ICCID_PREFIX_LEN +#endif + + strncpy(mmcmnc, associated_number, sizeof(mmcmnc) - 1); + mmcmnc[sizeof(mmcmnc) - 1] = '\0'; + /* try to find a matching IMSI/ICCID, and assign the APN */ + rc = find_apn(CONFIG_MODEM_HL78XX_APN_PROFILES, mmcmnc, data->identity.apn, + APN_PREFIX_LEN); + if (rc < 0) { + LOG_ERR("%d %s APN Parser error %d", __LINE__, __func__, rc); + } + } + + if (rc == 0) { + LOG_INF("Assign APN: \"%s\"", data->identity.apn); + } else { + LOG_INF("No assigned APN: \"%d\"", rc); + } + + return rc; +} +#endif + +void set_band_bit(uint8_t *bitmap, uint16_t band_num) +{ + if (band_num < 1 || band_num > 256) { + return; /* Out of range */ + } + + uint16_t bit_pos = band_num - 1; + uint16_t byte_index = bit_pos / 8; + uint8_t bit_index = bit_pos % 8; + /* Big-endian format: band 1 in byte 31, band 256 in byte 0 */ + bitmap[byte_index] |= (1 << bit_index); +} + +static uint8_t hl78xx_generate_band_bitmap(uint8_t *bitmap) +{ + memset(bitmap, 0, MDM_BAND_BITMAP_LEN_BYTES); + /* Index is reversed: Band 1 is LSB of byte 31, Band 256 is MSB of byte 0 */ + +#if CONFIG_MODEM_HL78XX_BAND_1 + set_band_bit(bitmap, 1); +#endif +#if CONFIG_MODEM_HL78XX_BAND_2 + set_band_bit(bitmap, 2); +#endif +#if CONFIG_MODEM_HL78XX_BAND_3 + set_band_bit(bitmap, 3); +#endif +#if CONFIG_MODEM_HL78XX_BAND_4 + set_band_bit(bitmap, 4); +#endif +#if CONFIG_MODEM_HL78XX_BAND_5 + set_band_bit(bitmap, 5); +#endif +#if CONFIG_MODEM_HL78XX_BAND_8 + set_band_bit(bitmap, 8); +#endif +#if CONFIG_MODEM_HL78XX_BAND_9 + set_band_bit(bitmap, 9); +#endif +#if CONFIG_MODEM_HL78XX_BAND_10 + set_band_bit(bitmap, 10); +#endif +#if CONFIG_MODEM_HL78XX_BAND_12 + set_band_bit(bitmap, 12); +#endif +#if CONFIG_MODEM_HL78XX_BAND_13 + set_band_bit(bitmap, 13); +#endif +#if CONFIG_MODEM_HL78XX_BAND_17 + set_band_bit(bitmap, 17); +#endif +#if CONFIG_MODEM_HL78XX_BAND_18 + set_band_bit(bitmap, 18); +#endif +#if CONFIG_MODEM_HL78XX_BAND_19 + set_band_bit(bitmap, 19); +#endif +#if CONFIG_MODEM_HL78XX_BAND_20 + set_band_bit(bitmap, 20); +#endif +#if CONFIG_MODEM_HL78XX_BAND_23 + set_band_bit(bitmap, 23); +#endif +#if CONFIG_MODEM_HL78XX_BAND_25 + set_band_bit(bitmap, 25); +#endif +#if CONFIG_MODEM_HL78XX_BAND_26 + set_band_bit(bitmap, 26); +#endif +#if CONFIG_MODEM_HL78XX_BAND_27 + set_band_bit(bitmap, 27); +#endif +#if CONFIG_MODEM_HL78XX_BAND_28 + set_band_bit(bitmap, 28); +#endif +#if CONFIG_MODEM_HL78XX_BAND_31 + set_band_bit(bitmap, 31); +#endif +#if CONFIG_MODEM_HL78XX_BAND_66 + set_band_bit(bitmap, 66); +#endif +#if CONFIG_MODEM_HL78XX_BAND_72 + set_band_bit(bitmap, 72); +#endif +#if CONFIG_MODEM_HL78XX_BAND_73 + set_band_bit(bitmap, 73); +#endif +#if CONFIG_MODEM_HL78XX_BAND_85 + set_band_bit(bitmap, 85); +#endif +#if CONFIG_MODEM_HL78XX_BAND_87 + set_band_bit(bitmap, 87); +#endif +#if CONFIG_MODEM_HL78XX_BAND_88 + set_band_bit(bitmap, 88); +#endif +#if CONFIG_MODEM_HL78XX_BAND_106 + set_band_bit(bitmap, 106); +#endif +#if CONFIG_MODEM_HL78XX_BAND_107 + set_band_bit(bitmap, 107); +#endif +#if CONFIG_MODEM_HL78XX_BAND_255 + set_band_bit(bitmap, 255); +#endif +#if CONFIG_MODEM_HL78XX_BAND_256 + set_band_bit(bitmap, 256); +#endif + /* Add additional bands similarly... */ + + return 0; +} +#if defined(CONFIG_MODEM_HL78XX_AUTORAT) +/** + * @brief Parse a comma-separated list of bands from a string. + * + * @param band_str The input string containing band numbers. + * @param bands Output array to store parsed band numbers. + * @param max_bands Maximum number of bands that can be stored in the output array. + * + * @return Number of bands parsed, or negative error code on failure. + */ +static int parse_band_list(const char *band_str, int *bands, size_t max_bands) +{ + if (!band_str || !bands || max_bands == 0) { + return -EINVAL; + } + + char buf[128] = {0}; + + strncpy(buf, band_str, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + char *token; + char *rest = buf; + int count = 0; + + while ((token = strtok_r(rest, ",", &rest))) { + int band = atoi(token); + + if (band <= 0) { + printk("Invalid band number: %s\n", token); + continue; + } + if (count >= max_bands) { + printk("Too many bands, max is %d\n", (int)max_bands); + break; + } + bands[count++] = band; + } + + return count; +} +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + +int hl78xx_generate_bitmap_from_config(enum hl78xx_cell_rat_mode rat, uint8_t *bitmap_out) +{ + if (!bitmap_out) { + return -EINVAL; + } + + memset(bitmap_out, 0, MDM_BAND_BITMAP_LEN_BYTES); + +#if defined(CONFIG_MODEM_HL78XX_AUTORAT) + /* Auto-RAT: read bands from string configs */ + const char *band_str = NULL; + + switch (rat) { + case HL78XX_RAT_CAT_M1: +#ifdef CONFIG_MODEM_HL78XX_AUTORAT_M1_BAND_CFG + band_str = CONFIG_MODEM_HL78XX_AUTORAT_M1_BAND_CFG; +#endif + break; + case HL78XX_RAT_NB1: +#ifdef CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG + band_str = CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG; +#endif + break; + default: + return -EINVAL; + } + + if (band_str) { + int bands[MAX_BANDS]; + int count = parse_band_list(band_str, bands, MAX_BANDS); + + if (count < 0) { + return -EINVAL; + } + + for (int i = 0; i < count; i++) { + set_band_bit(bitmap_out, bands[i]); + } + return 0; + } +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + + /* Else: use standalone config */ + return hl78xx_generate_band_bitmap(bitmap_out); +} +void hl78xx_bitmap_to_hex_string_trimmed(const uint8_t *bitmap, char *hex_str, size_t hex_str_len) +{ + + int started = 0; + size_t offset = 0; + + for (int i = MDM_BAND_BITMAP_LEN_BYTES - 1; i >= 0; i--) { + if (!started && bitmap[i] == 0) { + continue; /* Skip leading zero bytes */ + } + + started = 1; + + if (offset + 2 >= hex_str_len) { + break; + } + + offset += snprintk(&hex_str[offset], hex_str_len - offset, "%02X", bitmap[i]); + } + + if (!started) { + strcpy(hex_str, "0"); + } +} + +int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out) +{ + if (strlen(hex_str) >= MDM_BAND_HEX_STR_LEN) { + LOG_ERR("Invalid hex string length: %zu", strlen(hex_str)); + return -EINVAL; + } + + for (int i = 0; i < MDM_BAND_BITMAP_LEN_BYTES; i++) { + unsigned int byte_val; + + if (sscanf(&hex_str[i * 2], "%2x", &byte_val) != 1) { + LOG_ERR("Failed to parse byte at position %d", i); + return -EINVAL; + } + bitmap_out[i] = (uint8_t)byte_val; + } + + return 0; +} + +int hl78xx_get_band_default_config_for_rat(enum hl78xx_cell_rat_mode rat, char *hex_bndcfg, + size_t size_in_bytes) +{ + uint8_t bitmap[MDM_BAND_BITMAP_LEN_BYTES] = {0}; + char hex_str[MDM_BAND_HEX_STR_LEN] = {0}; + + if (size_in_bytes < MDM_BAND_HEX_STR_LEN || hex_bndcfg == NULL) { + return -EINVAL; + } + + if (hl78xx_generate_bitmap_from_config(rat, bitmap) != 0) { + return -EINVAL; + } + + hl78xx_bitmap_to_hex_string_trimmed(bitmap, hex_str, sizeof(hex_str)); + + LOG_INF("Default band config: %s", hex_str); + + strncpy(hex_bndcfg, hex_str, MDM_BAND_HEX_STR_LEN); + return 0; +} + +const char *hl78xx_trim_leading_zeros(const char *hex_str) +{ + while (*hex_str == '0' && *(hex_str + 1) != '\0') { + hex_str++; + } + return hex_str; +} + +static void strip_quotes(char *str) +{ + size_t len = strlen(str); + + if (len >= 2 && str[0] == '"' && str[len - 1] == '"') { + /* Shift string left by 1 and null-terminate earlier */ + memmove(str, str + 1, len - 2); + str[len - 2] = '\0'; + } +} + +void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len) +{ + + char apn_buf[max_len]; + + strncpy(apn_buf, full_apn, sizeof(apn_buf) - 1); + apn_buf[sizeof(apn_buf) - 1] = '\0'; + /* Remove surrounding quotes if any */ + strip_quotes(apn_buf); + + const char *mnc_ptr = strstr(apn_buf, ".mnc"); + size_t len; + + if (mnc_ptr != NULL) { + len = mnc_ptr - apn_buf; + if (len >= max_len) { + len = max_len - 1; + } + strncpy(essential_apn, apn_buf, len); + essential_apn[len] = '\0'; + } else { + /* No ".mnc" found, copy entire string */ + strncpy(essential_apn, apn_buf, max_len - 1); + essential_apn[max_len - 1] = '\0'; + } +} + +/* Convert 8-character binary string to byte (0–255) */ +int binary_str_to_byte(const char *bin_str) +{ + if (strlen(bin_str) != 8) { + return -EINVAL; /* Invalid input length */ + } + + int value = 0; + + for (int i = 0; i < 8; i++) { + if (bin_str[i] == '1') { + value = (value << 1) | 1; + } else if (bin_str[i] == '0') { + value = value << 1; + } else { + return -EINVAL; /* Invalid character */ + } + } + + return value; +} + +/* Convert byte to 8-character binary string */ +void byte_to_binary_str(uint8_t byte, char *output) +{ + if (output == NULL) { + return; /* Invalid output pointer */ + } + + for (int i = 7; i >= 0; i--) { + output[7 - i] = (byte & (1 << i)) ? '1' : '0'; + } + output[8] = '\0'; +} diff --git a/dts/bindings/modem/swir,hl7812.yaml b/dts/bindings/modem/swir,hl7812.yaml new file mode 100644 index 000000000000..a75d0c044b40 --- /dev/null +++ b/dts/bindings/modem/swir,hl7812.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025, Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: Sierra Wireless HL7812 Modem + +compatible: "swir,hl7812" + +include: swir,hl78xx.yaml diff --git a/dts/bindings/modem/swir,hl78xx.yaml b/dts/bindings/modem/swir,hl78xx.yaml new file mode 100644 index 000000000000..ab9c7b98ed1b --- /dev/null +++ b/dts/bindings/modem/swir,hl78xx.yaml @@ -0,0 +1,38 @@ +# Copyright (c) 2025, Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: Sierra Wireless HL78XX Modem + +compatible: "swir,hl78xx" + +include: + - uart-device.yaml + +properties: + mdm-wake-gpios: + type: phandle-array + required: true + + mdm-reset-gpios: + type: phandle-array + required: true + + mdm-pwr-on-gpios: + type: phandle-array + + mdm-fast-shutd-gpios: + type: phandle-array + + mdm-vgpio-gpios: + type: phandle-array + required: true + + mdm-uart-dsr-gpios: + type: phandle-array + + mdm-uart-cts-gpios: + type: phandle-array + required: true + + mdm-gpio6-gpios: + type: phandle-array diff --git a/include/zephyr/drivers/modem/hl78xx_apis.h b/include/zephyr/drivers/modem/hl78xx_apis.h new file mode 100644 index 000000000000..202471bdbec0 --- /dev/null +++ b/include/zephyr/drivers/modem/hl78xx_apis.h @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ +#define ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Magic constants */ +#define CSQ_RSSI_UNKNOWN (99) +#define CESQ_RSRP_UNKNOWN (255) +#define CESQ_RSRQ_UNKNOWN (255) +/* Magic numbers to units conversions */ +#define CSQ_RSSI_TO_DB(v) (-113 + (2 * (rssi))) +#define CESQ_RSRP_TO_DB(v) (-140 + (v)) +#define CESQ_RSRQ_TO_DB(v) (-20 + ((v) / 2)) +/** Monitor is paused. */ +#define PAUSED 1 +/** Monitor is active, default */ +#define ACTIVE 0 + +/** + * @brief Define an Event monitor to receive notifications in the system workqueue thread. + * + * @param name The monitor name. + * @param _handler The monitor callback. + * @param ... Optional monitor initial state (@c PAUSED or @c ACTIVE). + * The default initial state of a monitor is active. + */ +#define HL78XX_EVT_MONITOR(name, _handler, ...) \ + static void _handler(struct hl78xx_evt *); \ + static STRUCT_SECTION_ITERABLE(hl78xx_evt_monitor_entry, name) = { \ + .handler = _handler, \ + .flags.direct = false, \ + COND_CODE_1(__VA_ARGS__, (.flags.paused = __VA_ARGS__,), ()) } + +/** Cellular radio access technologies */ +enum hl78xx_cell_rat_mode { + HL78XX_RAT_CAT_M1 = 0, + HL78XX_RAT_NB1, +#ifdef CONFIG_MODEM_HL7812 + HL78XX_RAT_GSM, +#ifdef CONFIG_MODEM_FW_R6 + HL78XX_RAT_NBNTN, +#endif +#endif +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + HL78XX_RAT_MODE_AUTO, +#endif + HL78XX_RAT_MODE_NONE, + HL78XX_RAT_COUNT = HL78XX_RAT_MODE_NONE +}; +/* */ +enum hl78xx_phone_functionality { + HL78XX_SIM_POWER_OFF, + HL78XX_FULLY_FUNCTIONAL, + HL78XX_AIRPLANE = 4, +}; + +/** Cellular network structure */ +struct hl78xx_network { + /** Cellular access technology */ + enum hl78xx_cell_rat_mode technology; + /** + * List of bands, as defined by the specified cellular access technology, + * to enables. All supported bands are enabled if none are provided. + */ + uint16_t *bands; + /** Size of bands */ + uint16_t size; +}; + +/** Cellular signal type */ +enum hl78xx_signal_type { + HL78XX_SIGNAL_RSSI, + HL78XX_SIGNAL_RSRP, + HL78XX_SIGNAL_RSRQ, +}; + +/** Cellular modem info type */ +enum hl78xx_modem_info_type { + /** International Mobile Equipment Identity */ + HL78XX_MODEM_INFO_IMEI, + /** Modem model ID */ + HL78XX_MODEM_INFO_MODEL_ID, + /** Modem manufacturer */ + HL78XX_MODEM_INFO_MANUFACTURER, + /** Modem fw version */ + HL78XX_MODEM_INFO_FW_VERSION, + /** International Mobile Subscriber Identity */ + HL78XX_MODEM_INFO_SIM_IMSI, + /** Integrated Circuit Card Identification Number (SIM) */ + HL78XX_MODEM_INFO_SIM_ICCID, + /* Access Point Name */ + HL78XX_MODEM_INFO_APN, +}; + +enum hl78xx_registration_status { + HL78XX_REGISTRATION_NOT_REGISTERED = 0, + HL78XX_REGISTRATION_REGISTERED_HOME, + HL78XX_REGISTRATION_SEARCHING, + HL78XX_REGISTRATION_DENIED, + HL78XX_REGISTRATION_UNKNOWN, + HL78XX_REGISTRATION_REGISTERED_ROAMING, +}; + +enum hl78xx_psmev_event { + HL78XX_PSM_EVENT_EXIT = 0, + HL78XX_PSM_EVENT_ENTER, + HL78XX_PSM_EVENT_NONE, +}; + +enum hl78xx_evt_type { + HL78XX_RAT_UPDATE, + HL78XX_LTE_REGISTRATION_STAT_UPDATE, + HL78XX_LTE_SIM_REGISTRATION, + HL78XX_LTE_PSMEV, + HL78XX_LTE_MODEM_STARTUP, +}; + +struct hl78xx_evt { + enum hl78xx_evt_type type; + + union { + enum hl78xx_registration_status reg_status; + enum hl78xx_cell_rat_mode rat_mode; + enum hl78xx_psmev_event psm_event; + bool status; + int value; + + } content; +}; +/** API for configuring networks */ +typedef int (*hl78xx_api_configure_networks)(const struct device *dev, + const struct hl78xx_network *networks, uint8_t size); + +/** API for getting supported networks */ +typedef int (*hl78xx_api_get_supported_networks)(const struct device *dev, + const struct hl78xx_network **networks, + uint8_t *size); + +/** API for getting network signal strength */ +typedef int (*hl78xx_api_get_signal)(const struct device *dev, const enum hl78xx_signal_type type, + int16_t *value); + +/** API for getting modem information */ +typedef int (*hl78xx_api_get_modem_info)(const struct device *dev, + const enum hl78xx_modem_info_type type, char *info, + size_t size); + +/** API for getting registration status */ +typedef int (*hl78xx_api_get_registration_status)(const struct device *dev, + enum hl78xx_cell_rat_mode *tech, + enum hl78xx_registration_status *status); + +/** API for setting apn */ +typedef int (*hl78xx_api_set_apn)(const struct device *dev, const char *apn, uint16_t size); + +/** API for set phone functionality */ +typedef int (*hl78xx_api_set_phone_functionality)(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset); + +/** API for get phone functionality */ +typedef int (*hl78xx_api_get_phone_functionality)(const struct device *dev, + enum hl78xx_phone_functionality *functionality); + +/** API for get phone functionality */ +typedef int (*hl78xx_api_send_at_cmd)(const struct device *dev, const char *cmd, uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size); + +typedef void (*hl78xx_evt_monitor_handler_t)(struct hl78xx_evt *notif); + +struct hl78xx_evt_monitor_entry { + /** Monitor callback. */ + const hl78xx_evt_monitor_handler_t handler; + struct { + uint8_t paused: 1; /* Monitor is paused. */ + uint8_t direct: 1; /* Dispatch in ISR. */ + } flags; +}; + +/** Cellular driver API */ +__subsystem struct hl78xx_driver_api { + hl78xx_api_configure_networks configure_networks; + hl78xx_api_get_supported_networks get_supported_networks; + hl78xx_api_get_signal get_signal; + hl78xx_api_get_modem_info get_modem_info; + hl78xx_api_get_registration_status get_registration_status; + hl78xx_api_set_apn set_apn; + hl78xx_api_set_phone_functionality set_phone_functionality; + hl78xx_api_get_phone_functionality get_phone_functionality; + hl78xx_api_send_at_cmd send_at_cmd; +}; + +/** + * @brief Configure cellular networks for the device + * + * @details Cellular network devices support at least one cellular access technology. + * Each cellular access technology defines a set of bands, of which the cellular device + * will support all or a subset of. + * + * The cellular device can only use one cellular network technology at a time. It must + * exclusively use the cellular network configurations provided, and will prioritize + * the cellular network configurations in the order they are provided in case there are + * multiple (the first cellular network configuration has the highest priority). + * + * @param dev Cellular network device instance. + * @param networks List of cellular network configurations to apply. + * @param size Size of list of cellular network configurations. + * + * @retval 0 if successful. + * @retval -EINVAL if any provided cellular network configuration is invalid or unsupported. + * @retval -ENOTSUP if API is not supported by cellular network device. + * @retval Negative errno-code otherwise. + */ +static inline int hl78xx_configure_networks(const struct device *dev, + const struct hl78xx_network *networks, uint8_t size) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->configure_networks == NULL) { + return -ENOSYS; + } + + return api->configure_networks(dev, networks, size); +} + +/** + * @brief Get supported cellular networks for the device + * + * @param dev Cellular network device instance + * @param networks Pointer to list of supported cellular network configurations. + * @param size Size of list of cellular network configurations. + * + * @retval 0 if successful. + * @retval -ENOTSUP if API is not supported by cellular network device. + * @retval Negative errno-code otherwise. + */ +static inline int hl78xx_get_supported_networks(const struct device *dev, + const struct hl78xx_network **networks, + uint8_t *size) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->get_supported_networks == NULL) { + return -ENOSYS; + } + + return api->get_supported_networks(dev, networks, size); +} + +/** + * @brief Get signal for the device + * + * @param dev Cellular network device instance + * @param type Type of the signal information requested + * @param value Signal strength destination (one of RSSI, RSRP, RSRQ) + * + * @retval 0 if successful. + * @retval -ENOTSUP if API is not supported by cellular network device. + * @retval -ENODATA if device is not in a state where signal can be polled + * @retval Negative errno-code otherwise. + */ +static inline int hl78xx_get_signal(const struct device *dev, const enum hl78xx_signal_type type, + int16_t *value) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->get_signal == NULL) { + return -ENOSYS; + } + + return api->get_signal(dev, type, value); +} + +/** + * @brief Get modem info for the device + * + * @param dev Cellular network device instance + * @param type Type of the modem info requested + * @param info Info string destination + * @param size Info string size + * + * @retval 0 if successful. + * @retval -ENOTSUP if API is not supported by cellular network device. + * @retval -ENODATA if modem does not provide info requested + * @retval Negative errno-code from chat module otherwise. + */ +static inline int hl78xx_get_modem_info(const struct device *dev, + const enum hl78xx_modem_info_type type, char *info, + size_t size) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->get_modem_info == NULL) { + return -ENOSYS; + } + + return api->get_modem_info(dev, type, info, size); +} + +/** + * @brief Get network registration status for the device + * + * @param dev Cellular network device instance + * @param tech Which access technology to get status for + * @param status Registration status for given access technology + * + * @retval 0 if successful. + * @retval -ENOSYS if API is not supported by cellular network device. + * @retval -ENODATA if modem does not provide info requested + * @retval Negative errno-code from chat module otherwise. + */ +static inline int hl78xx_get_registration_status(const struct device *dev, + enum hl78xx_cell_rat_mode *tech, + enum hl78xx_registration_status *status) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->get_registration_status == NULL) { + return -ENOSYS; + } + + return api->get_registration_status(dev, tech, status); +} + +/** + * @brief Set the Access Point Name (APN) for the modem. + * + * Stores the specified APN string in the modem driver context to be used + * during PDP context activation or network registration. + * + * @param dev Pointer to the modem device instance. + * @param apn Null-terminated string representing the APN to be set. + * @param size Length of the APN string, including the null terminator. + * + * @retval 0 on success. + * @retval -EINVAL if input parameters are invalid. + */ +static inline int hl78xx_set_apn(const struct device *dev, const char *apn, uint16_t size) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->set_apn == NULL) { + return -ENOSYS; + } + + return api->set_apn(dev, apn, size); +} +/** + * @brief Set the modem phone functionality mode. + * + * Configures the operational state of the modem (e.g., full, airplane, or minimum functionality). + * Optionally, the modem can be reset during this transition. + * + * @param dev Pointer to the modem device instance. + * @param functionality Desired phone functionality mode to be set. + * (e.g., full, airplane, minimum – see enum hl78xx_phone_functionality) + * @param reset If true, the modem will be reset as part of applying the functionality change. + * + * @retval 0 on success. + * @retval -EINVAL if an invalid parameter is passed. + * @retval -EIO on communication or command failure with the modem. + */ +static inline int hl78xx_set_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->set_phone_functionality == NULL) { + return -ENOSYS; + } + + return api->set_phone_functionality(dev, functionality, reset); +} +/** + * @brief Get the current phone functionality mode of the modem. + * + * Queries the modem to retrieve its current operational mode, such as + * full functionality, airplane mode, or minimum functionality. + * + * @param dev Pointer to the modem device instance. + * @param functionality Pointer to store the retrieved functionality mode. + * (see enum hl78xx_phone_functionality) + * + * @retval 0 on success. + * @retval -EINVAL if the input parameters are invalid. + * @retval -EIO if the modem fails to respond or returns an error. + */ +static inline int hl78xx_get_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality *functionality) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->get_phone_functionality == NULL) { + return -ENOSYS; + } + + return api->get_phone_functionality(dev, functionality); +} +/** + * @brief Send an AT command to the modem and wait for a matched response. + * + * Transmits the specified AT command to the modem and waits for a response that matches + * one of the expected patterns defined in the response match table. + * + * @param dev Pointer to the modem device instance. + * @param cmd Pointer to the AT command string to be sent. + * @param cmd_size Length of the AT command string in bytes. + * @param response_matches Pointer to an array of expected response patterns. + * (see struct modem_chat_match) + * @param matches_size Number of response patterns in the array. + * + * @retval 0 on successful command transmission and response match. + * @retval -EINVAL if any parameter is invalid. + * @retval -ETIMEDOUT if the modem did not respond in the expected time. + * @retval -EIO on communication failure or if response did not match. + */ +static inline int hl78xx_modem_cmd_send(const struct device *dev, const char *cmd, + uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size) +{ + const struct hl78xx_driver_api *api = (const struct hl78xx_driver_api *)dev->api; + + if (api->send_at_cmd == NULL) { + return -ENOSYS; + } + + return api->send_at_cmd(dev, cmd, cmd_size, response_matches, matches_size); +} +/** + * @brief Convert raw RSSI value from the modem to dBm. + * + * Parses the RSSI value reported by the modem (typically from an AT command response) + * and converts it to a corresponding signal strength in dBm, as defined by 3GPP TS 27.007. + * + * @param rssi Raw RSSI value (0–31 or 99 for not known or not detectable). + * @param value Pointer to store the converted RSSI in dBm. + * + * @retval 0 on successful conversion. + * @retval -EINVAL if the RSSI value is out of valid range or unsupported. + */ +static inline int hl78xx_parse_rssi(uint8_t rssi, int16_t *value) +{ + /* AT+CSQ returns a response +CSQ: , where: + * - rssi is a integer from 0 to 31 whose values describes a signal strength + * between -113 dBm for 0 and -51dbM for 31 or unknown for 99 + * - ber is an integer from 0 to 7 that describes the error rate, it can also + * be 99 for an unknown error rate + */ + if (rssi == CSQ_RSSI_UNKNOWN) { + return -EINVAL; + } + + *value = (int16_t)CSQ_RSSI_TO_DB(rssi); + return 0; +} +/** + * @brief Convert raw RSRP value from the modem to dBm. + * + * Parses the Reference Signal Received Power (RSRP) value reported by the modem + * and converts it into a corresponding signal strength in dBm, typically based on + * 3GPP TS 36.133 specifications. + * + * @param rsrp Raw RSRP value (commonly in the range = -140.0 dBm to 0.0 dBm, or 255 if unknown). + * @param value Pointer to store the converted RSRP in dBm. + * + * @retval 0 on successful conversion. + * @retval -EINVAL if the RSRP value is out of range or represents an unknown value. + */ +static inline int hl78xx_parse_rsrp(uint8_t rsrp, int16_t *value) +{ + /* AT+CESQ returns a response + * +CESQ: ,,,,, where: + * rsrq is a integer from 0 to 34 whose values describes the Reference + * Signal Receive Quality between -20 dB for 0 and -3 dB for 34 + * (0.5 dB steps), or unknown for 255 + * rsrp is an integer from 0 to 97 that describes the Reference Signal + * Receive Power between -140 dBm for 0 and -44 dBm for 97 (1 dBm steps), + * or unknown for 255 + */ + if (rsrp == CESQ_RSRP_UNKNOWN) { + return -EINVAL; + } + + *value = (int16_t)CESQ_RSRP_TO_DB(rsrp); + return 0; +} +/** + * @brief Convert raw RSRQ value from the modem to dB. + * + * Parses the Reference Signal Received Quality (RSRQ) value provided by the modem + * and converts it into a signal quality measurement in decibels (dB), as specified + * by 3GPP TS 36.133. + * + * @param rsrq Raw RSRQ value (typically 0–34, or 255 if unknown). + * @param value Pointer to store the converted RSRQ in dB. + * + * @retval 0 on successful conversion. + * @retval -EINVAL if the RSRQ value is out of valid range or indicates unknown. + */ +static inline int hl78xx_parse_rsrq(uint8_t rsrq, int16_t *value) +{ + if (rsrq == CESQ_RSRQ_UNKNOWN) { + return -EINVAL; + } + + *value = (int16_t)CESQ_RSRQ_TO_DB(rsrq); + return 0; +} +/** + * @brief Pause monitor. + * + * Pause monitor @p mon from receiving notifications. + * + * @param mon The monitor to pause. + */ +static inline void hl78xx_evt_monitor_pause(struct hl78xx_evt_monitor_entry *mon) +{ + mon->flags.paused = true; +} + +/** + * @brief Resume monitor. + * + * Resume forwarding notifications to monitor @p mon. + * + * @param mon The monitor to resume. + */ +static inline void hl78xx_evt_monitor_resume(struct hl78xx_evt_monitor_entry *mon) +{ + mon->flags.paused = false; +} + +/** + * @brief Set the event notification handler for HL78xx modem events. + * + * Registers a callback handler to receive asynchronous event notifications + * from the HL78xx modem, such as network registration changes, GNSS updates, + * or other modem-generated events. + * + * @param handler Function pointer to the event monitor callback. + * Pass NULL to clear the existing handler. + * + * @retval 0 on success. + * @retval -EINVAL if the handler parameter is invalid. + */ +int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_handler_t handler); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ */ diff --git a/samples/drivers/modem/hello_hl78xx/.gitignore b/samples/drivers/modem/hello_hl78xx/.gitignore new file mode 100644 index 000000000000..635a99b7955a --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/.gitignore @@ -0,0 +1,6 @@ +# editors +*.swp +*~ + +# build +/build*/ diff --git a/samples/drivers/modem/hello_hl78xx/CMakeLists.txt b/samples/drivers/modem/hello_hl78xx/CMakeLists.txt new file mode 100644 index 000000000000..ab5ef99b1c4b --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/CMakeLists.txt @@ -0,0 +1,13 @@ +# Sierra Wireless HL78XX driver driver options + +# Copyright (c) 2025 Netfeasa +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(hello_hl78xx) + +target_sources(app PRIVATE src/main.c) + +include(${ZEPHYR_BASE}/samples/net/common/common.cmake) diff --git a/samples/drivers/modem/hello_hl78xx/Kconfig b/samples/drivers/modem/hello_hl78xx/Kconfig new file mode 100644 index 000000000000..12cacd7b37a6 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/Kconfig @@ -0,0 +1,7 @@ +# Sierra Wireless HL78XX driver driver options + +# Copyright (c) 2025 Netfeasa +# SPDX-License-Identifier: Apache-2.0 + +source "samples/net/common/Kconfig" +source "Kconfig.zephyr" diff --git a/samples/drivers/modem/hello_hl78xx/README.rst b/samples/drivers/modem/hello_hl78xx/README.rst new file mode 100644 index 000000000000..f7110c8ecafa --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/README.rst @@ -0,0 +1,54 @@ +.. zephyr:code-sample:: hello_hl78xx + :name: Hello hl78xx modem driver + + get & set basic hl78xx modem information & functionality with HL78XX modem APIs + +Overview +******** + +A simple sample that can be used with only Sierra Wireles HL78XX series modems + +Notes +***** + +This sample uses the devicetree alias ``modem`` to identify +the modem instance to use. + +Building and Running +******************** + +This application can be built and executed on QEMU as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/modem/hello_hl78xx + :host-os: all + :goals: build flash + :compact: + +To build for another board, change "qemu_x86" above to that board's name. + +Sample Output +============= + +.. code-block:: console + + ********************************************************** + ********* Hello HL78XX Modem Sample Application ********** + ********************************************************** + [00:00:16.881,000] main: Manufacturer: Sierra Wireless + [00:00:16.881,000] main: Firmware Version: HL7812.5.5.17.0 + [00:00:16.881,000] main: APN: netfeasavod + [00:00:16.881,000] main: Imei: 352244440111111 + [00:00:16.881,000] main: RAT: NB1 + [00:00:16.881,000] main: Connection status: Roaming Network + [00:00:16.881,000] main: RSRP : -90 + ********************************************************** + +After startup, code performs: + +#. Modem readiness check and power-on +#. Network interface setup via Zephyr's Connection Manager +#. Modem queries (manufacturer, firmware, APN, IMEI, signal strength, etc.) +#. Network registration and signal strength checks +#. Setting and verifying a new APN +#. Sending an AT command to validate communication diff --git a/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-low-power.conf b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-low-power.conf new file mode 100644 index 000000000000..f1ada5a528e4 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-low-power.conf @@ -0,0 +1,21 @@ +# Sierra Wireless HL78XX driver driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network + +#PM +CONFIG_PM_DEVICE=y + +#Modem low power configuration +CONFIG_MODEM_HL78XX_LOW_POWER_MODE=y +#enable PSM +CONFIG_MODEM_HL78XX_PSM=y +#enable eDRX +CONFIG_MODEM_HL78XX_EDRX=n +#enable power down mode +#default: MODEM_HL78XX_USE_DELAY_BASED_POWER_DOWN +# CONFIG_MODEM_HL78XX_POWER_DOWN=y +#enable active time based power down +# CONFIG_MODEM_HL78XX_USE_ACTIVE_TIME_BASED_POWER_DOWN=y diff --git a/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-verbose-logging.conf b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-verbose-logging.conf new file mode 100644 index 000000000000..ebd3a7e9967a --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-verbose-logging.conf @@ -0,0 +1,10 @@ +# Logging +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=85536 +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=8192 + +# For extra verbosity +CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +CONFIG_MODEM_LOG_LEVEL_DBG=y +CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y diff --git a/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx_ev_kit.conf b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx_ev_kit.conf new file mode 100644 index 000000000000..156088d8dd67 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx_ev_kit.conf @@ -0,0 +1,71 @@ +# Sierra Wireless HL78XX driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network +CONFIG_NET_CONFIG_SETTINGS=n +CONFIG_NET_DHCPV4=n +CONFIG_DNS_SERVER_IP_ADDRESSES=n + +#uart +CONFIG_UART_ASYNC_API=y +CONFIG_UART_INTERRUPT_DRIVEN=n + +# Generic networking options +CONFIG_NET_IPV6=n + +# SNTP +CONFIG_NET_CONFIG_SNTP_INIT_SERVER="time.google.com" + +# DNS +CONFIG_NET_SOCKETS_DNS_TIMEOUT=15000 + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_CONNECTION_MANAGER=y + +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y +CONFIG_MODEM_HL7812=y +CONFIG_MODEM_FW_R6=y + +# CONFIG_MODEM_HL78XX_RX_WORKQ_STACK_SIZE=4096 + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y +# Don't require device to have time/date +CONFIG_MBEDTLS_HAVE_TIME_DATE=n + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" + +CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE=y + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y + +CONFIG_MODEM_BACKEND_UART_ASYNC=y +CONFIG_MODEM_BACKEND_UART_ISR=n diff --git a/samples/drivers/modem/hello_hl78xx/prj.conf b/samples/drivers/modem/hello_hl78xx/prj.conf new file mode 100644 index 000000000000..2f2b0ea5f504 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/prj.conf @@ -0,0 +1,76 @@ +# Sierra Wireless HL78XX driver driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network + +#system +CONFIG_HEAP_MEM_POOL_SIZE=4096 +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_POSIX_API=y + +#PM +# CONFIG_PM_DEVICE=y + +#uart +CONFIG_UART_ASYNC_API=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=n +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_NET_SOCKETS_DNS_TIMEOUT=5000 + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 +CONFIG_NET_CONNECTION_MANAGER=y + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y +CONFIG_MODEM_HL7812=y + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" + +CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE=y + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y + +# Logging +CONFIG_LOG=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=32768 diff --git a/samples/drivers/modem/hello_hl78xx/sample.yaml b/samples/drivers/modem/hello_hl78xx/sample.yaml new file mode 100644 index 000000000000..625cf998cd74 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: Sample for HL78XX modem + name: Hello HL78XX sample +common: + tags: + - modem + - hl78xx + filter: dt_alias_exists("modem") +tests: + sample.driver.modem.hello_hl78xx: + platform_allow: + - nucleo_u575zi_q + integration_platforms: + - nucleo_u575zi_q + extra_args: + - SHIELD=swir_hl78xx_ev_kit diff --git a/samples/drivers/modem/hello_hl78xx/src/main.c b/samples/drivers/modem/hello_hl78xx/src/main.c new file mode 100644 index 000000000000..1dd997776c73 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/src/main.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2025 Netfeasa + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Macros used to subscribe to specific Zephyr NET management events. */ +#if defined(CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION) +#define L4_EVENT_MASK (NET_EVENT_DNS_SERVER_ADD | NET_EVENT_L4_DISCONNECTED) +#else +#define L4_EVENT_MASK (NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED) +#endif +#define CONN_LAYER_EVENT_MASK (NET_EVENT_CONN_IF_FATAL_ERROR) + +LOG_MODULE_REGISTER(main, CONFIG_MODEM_LOG_LEVEL); + +static K_SEM_DEFINE(network_connected_sem, 0, 1); +const struct device *modem = DEVICE_DT_GET(DT_ALIAS(modem)); + +/* Zephyr NET management event callback structures. */ +static struct net_mgmt_event_callback l4_cb; +static struct net_mgmt_event_callback conn_cb; + +static const char *rat_get_in_string(enum hl78xx_cell_rat_mode rat) +{ + switch (rat) { + case HL78XX_RAT_CAT_M1: + return "CAT-M1"; + case HL78XX_RAT_NB1: + return "NB1"; + case HL78XX_RAT_GSM: + return "GSM"; +#ifdef CONFIG_MODEM_FW_R6 + case HL78XX_RAT_NBNTN: + return "NTN"; +#endif + default: + return "Not ready"; + } +} + +static const char *reg_status_get_in_string(enum hl78xx_registration_status rat) +{ + switch (rat) { + case HL78XX_REGISTRATION_NOT_REGISTERED: + return "Not Registered"; + case HL78XX_REGISTRATION_REGISTERED_HOME: + return "Home Network"; + case HL78XX_REGISTRATION_SEARCHING: + return "Network Searching"; + case HL78XX_REGISTRATION_DENIED: + return "Registiration Denied"; + case HL78XX_REGISTRATION_UNKNOWN: + return "Out of covarege or Unknown"; + case HL78XX_REGISTRATION_REGISTERED_ROAMING: + return "Roaming Network"; + default: + return "Not ready"; + } +} +/* Zephyr NET management event callback structures. */ +static void on_net_event_l4_disconnected(void) +{ + LOG_INF("Disconnected from network"); +} + +static void on_net_event_l4_connected(void) +{ + LOG_INF("Connected to network"); + k_sem_give(&network_connected_sem); +} + +static void connectivity_event_handler(struct net_mgmt_event_callback *cb, uint64_t event, + struct net_if *iface) +{ + if (event == NET_EVENT_CONN_IF_FATAL_ERROR) { + LOG_ERR("Fatal error received from the connectivity layer"); + return; + } +} + +static void l4_event_handler(struct net_mgmt_event_callback *cb, uint64_t event, + struct net_if *iface) +{ + switch (event) { +#if defined(CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION) + case NET_EVENT_DNS_SERVER_ADD: +#else + case NET_EVENT_L4_CONNECTED: +#endif + LOG_INF("IP Up"); + on_net_event_l4_connected(); + break; + case NET_EVENT_L4_DISCONNECTED: + LOG_INF("IP down"); + on_net_event_l4_disconnected(); + break; + default: + break; + } +} + +static void evnt_listener(struct hl78xx_evt *event) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d HL78XX modem Event Received: %d", __LINE__, event->type); +#endif + switch (event->type) { + /* Do something */ + case HL78XX_RAT_UPDATE: + LOG_DBG("%d HL78XX modem rat mode changed: %d", __LINE__, event->content.rat_mode); + break; + case HL78XX_LTE_REGISTRATION_STAT_UPDATE: + LOG_DBG("%d HL78XX modem registration status: %d", __LINE__, + event->content.reg_status); + break; + case HL78XX_LTE_SIM_REGISTRATION: + break; + case HL78XX_LTE_PSMEV: + break; + default: + break; + } +} + +static void hl78xx_on_ok(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %s", __LINE__, __func__, argv[0]); +#endif +} + +MODEM_CHAT_MATCH_DEFINE(ok_match, "OK", "", hl78xx_on_ok); + +HL78XX_EVT_MONITOR(listner_evt, evnt_listener); + +int main(void) +{ + int ret = 0; + + if (device_is_ready(modem) == false) { + LOG_ERR("%d, %s Device %s is not ready", __LINE__, __func__, modem->name); + } +#ifdef CONFIG_PM_DEVICE + LOG_INF("Powering on modem\n"); + pm_device_action_run(modem, PM_DEVICE_ACTION_RESUME); +#endif + +#ifdef CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + if (IS_ENABLED(CONFIG_NET_CONNECTION_MANAGER)) { + struct net_if *iface = net_if_get_default(); + + if (!iface) { + LOG_ERR("No network interface found!"); + return -ENODEV; + } + + /* Setup handler for Zephyr NET Connection Manager events. */ + net_mgmt_init_event_callback(&l4_cb, l4_event_handler, L4_EVENT_MASK); + net_mgmt_add_event_callback(&l4_cb); + + /* Setup handler for Zephyr NET Connection Manager Connectivity layer. */ + net_mgmt_init_event_callback(&conn_cb, connectivity_event_handler, + CONN_LAYER_EVENT_MASK); + net_mgmt_add_event_callback(&conn_cb); + + ret = net_if_up(iface); + + if (ret < 0 && ret != -EALREADY) { + LOG_ERR("net_if_up, error: %d", ret); + return ret; + } + + (void)conn_mgr_if_connect(iface); + + LOG_INF("Waiting for network connection..."); + k_sem_take(&network_connected_sem, K_FOREVER); + } +#endif + /* Start pleacing your modem based code here */ + char manufacturer[20] = {0}; + char fw_ver[17] = {0}; + char apn[64] = {0}; + char imei[17] = {0}; + enum hl78xx_cell_rat_mode tech; + enum hl78xx_registration_status status; + int16_t rsrp; + const char *newapn = ""; + const char *sample_cmd = "AT"; + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_MANUFACTURER, manufacturer, + sizeof(manufacturer)); + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_FW_VERSION, fw_ver, sizeof(fw_ver)); + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_APN, apn, sizeof(apn)); + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_IMEI, imei, sizeof(imei)); + + hl78xx_get_registration_status(modem, &tech, &status); + + hl78xx_get_signal(modem, HL78XX_SIGNAL_RSRP, &rsrp); + + LOG_RAW("\n**********************************************************\n"); + LOG_RAW("********* Hello HL78XX Modem Sample Application **********\n"); + LOG_RAW("**********************************************************\n"); + LOG_INF("Manufacturer: %s", manufacturer); + LOG_INF("Firmware Version: %s", fw_ver); + LOG_INF("APN: %s", apn); + LOG_INF("Imei: %s", imei); + LOG_INF("RAT: %s", rat_get_in_string(tech)); + LOG_INF("Connection status: %s", reg_status_get_in_string(status)); + LOG_INF("RSRP : %d", rsrp); + LOG_RAW("**********************************************************\n\n"); + + LOG_INF("Setting new APN: %s", newapn); + hl78xx_set_apn(modem, newapn, 0); + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_APN, apn, sizeof(apn)); + + hl78xx_modem_cmd_send(modem, sample_cmd, strlen(sample_cmd), &ok_match, 1); + LOG_INF("New APN: %s", (strlen(apn) > 0) ? apn : "\"\""); + return 0; +} diff --git a/samples/drivers/modem/index.rst b/samples/drivers/modem/index.rst new file mode 100644 index 000000000000..5b7a92c1018f --- /dev/null +++ b/samples/drivers/modem/index.rst @@ -0,0 +1,5 @@ +.. zephyr:code-sample-category:: modem + :name: Modem + :show-listing: + + These samples demonstrate how to use the custom modem driver APIs. diff --git a/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx_ev_kit.conf b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx_ev_kit.conf new file mode 100644 index 000000000000..83a447ac900b --- /dev/null +++ b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx_ev_kit.conf @@ -0,0 +1,77 @@ +# Sierra Wireless HL78XX driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network +CONFIG_NET_CONFIG_SETTINGS=n +CONFIG_NET_DHCPV4=n +CONFIG_DNS_SERVER_IP_ADDRESSES=n + +#PM +# CONFIG_PM_DEVICE=y + +#uart +CONFIG_UART_ASYNC_API=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +# Generic networking options +CONFIG_NET_IPV6=n + +# SNTP +CONFIG_NET_CONFIG_SNTP_INIT_SERVER="time.google.com" + +# DNS +CONFIG_NET_SOCKETS_DNS_TIMEOUT=15000 + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_CONNECTION_MANAGER=y + +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y +CONFIG_MODEM_HL7812=y + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y +# Don't require device to have time/date +CONFIG_MBEDTLS_HAVE_TIME_DATE=n + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" + +CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE=y + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y + +# Logging +# CONFIG_LOG_MODE_DEFERRED=y +# CONFIG_LOG_BUFFER_SIZE=50000 +# # For extra verbosity +# CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +# CONFIG_MODEM_LOG_LEVEL_DBG=y +# CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +# CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y diff --git a/samples/net/common/Kconfig b/samples/net/common/Kconfig index afaab763ac58..ceed53efa869 100644 --- a/samples/net/common/Kconfig +++ b/samples/net/common/Kconfig @@ -6,7 +6,7 @@ config NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION bool "Wait DNS server addition before considering connection to be up" - depends on MODEM_HL7800 && !DNS_SERVER_IP_ADDRESSES + depends on (MODEM_HL7800 || MODEM_HL78XX) && !DNS_SERVER_IP_ADDRESSES help Make sure we get DNS server addresses from the network before considering the connection to be up. diff --git a/samples/net/lwm2m_client/overlay-swir_hl78xx_ev_kit.conf b/samples/net/lwm2m_client/overlay-swir_hl78xx_ev_kit.conf new file mode 100644 index 000000000000..6be4db978bd4 --- /dev/null +++ b/samples/net/lwm2m_client/overlay-swir_hl78xx_ev_kit.conf @@ -0,0 +1,75 @@ +# Sierra Wireless HL78XX driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network +CONFIG_NET_CONFIG_SETTINGS=n +CONFIG_NET_DHCPV4=n +CONFIG_DNS_SERVER_IP_ADDRESSES=n + +#PM +# CONFIG_PM_DEVICE=y + +#uart +CONFIG_UART_ASYNC_API=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +# Generic networking options +CONFIG_NET_IPV6=n + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_NET_SOCKETS_DNS_TIMEOUT=15000 + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_LWM2M_WAIT_DNS=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_CONNECTION_MANAGER=y + +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y +CONFIG_MODEM_HL7812=y + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y +# Don't require device to have time/date +CONFIG_MBEDTLS_HAVE_TIME_DATE=n + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" + +CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE=y + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y + +# Logging +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=50000 +# For extra verbosity +CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +CONFIG_MODEM_LOG_LEVEL_DBG=y +CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y