drivers: modem: HL78XX Modem Driver
Adding HL78XX Modem Driver Implementation Using Modem Chat Framework
Signed-off-by: Zafer SEN <zafersn93@gmail.com>
diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt
index 5553cb2..7666aa6 100644
--- a/drivers/modem/CMakeLists.txt
+++ b/drivers/modem/CMakeLists.txt
@@ -33,6 +33,8 @@
add_subdirectory(simcom)
+add_subdirectory_ifdef(CONFIG_MODEM_HL78XX hl78xx)
+
zephyr_library_sources_ifdef(CONFIG_MODEM_CELLULAR modem_cellular.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_AT_USER_PIPE modem_at_user_pipe.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_AT_SHELL modem_at_shell.c)
diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig
index 02de18f..6fc60cb 100644
--- a/drivers/modem/Kconfig
+++ b/drivers/modem/Kconfig
@@ -193,7 +193,7 @@
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/simcom/Kconfig"
diff --git a/drivers/modem/hl78xx/CMakeLists.txt b/drivers/modem/hl78xx/CMakeLists.txt
new file mode 100644
index 0000000..1320925
--- /dev/null
+++ b/drivers/modem/hl78xx/CMakeLists.txt
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2025 Netfeasa Ltd.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+zephyr_library()
+
+zephyr_library_sources(
+ hl78xx.c
+ hl78xx_sockets.c
+ hl78xx_cfg.c
+ hl78xx_chat.c
+ hl78xx_apis.c
+)
+
+add_subdirectory_ifdef(CONFIG_HL78XX_EVT_MONITOR hl78xx_evt_monitor)
+
+zephyr_library_include_directories(
+ ./
+ # IP headers
+ ${ZEPHYR_BASE}/subsys/net/ip
+ ${ZEPHYR_BASE}/subsys/net/lib/sockets
+)
+
+zephyr_library_include_directories_ifdef(
+ CONFIG_NET_SOCKETS_SOCKOPT_TLS
+ ${ZEPHYR_BASE}/subsys/net/lib/tls_credentials
+)
diff --git a/drivers/modem/hl78xx/Kconfig.hl78xx b/drivers/modem/hl78xx/Kconfig.hl78xx
new file mode 100644
index 0000000..bed24a0
--- /dev/null
+++ b/drivers/modem/hl78xx/Kconfig.hl78xx
@@ -0,0 +1,822 @@
+# 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_HL78XX_12 if DT_HAS_SWIR_HL7812_ENABLED
+ default MODEM_HL78XX_00 if DT_HAS_SWIR_HL7800_ENABLED
+ default MODEM_HL78XX_AUTODETECT_VARIANT
+
+config MODEM_HL78XX_12
+ bool "Sierra Wireless hl7812"
+ help
+ Support for hl7812 modem
+
+config MODEM_HL78XX_00
+ bool "Sierra Wireless hl7800"
+ help
+ Support for hl7800 modem
+
+config MODEM_HL78XX_AUTODETECT_VARIANT
+ bool "detect automatically"
+ help
+ Automatic detection of modem variant (HL7812 or HL7800)
+
+endchoice
+
+if MODEM_HL78XX_12
+
+config MODEM_HL78XX_12_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_HL78XX_12
+
+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_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_FULLY_FUNCTIONAL_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 <fun> power level.
+ 1 — Reset the MT before setting it to <fun> 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_HL78XX_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:
+ <apn>=<IMSI_1> ... <IMSI_n>
+ <apn>=<ICCID_1> ... <ICCID_n>
+
+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"
+ 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"
+ default "1,2,3" if MODEM_HL78XX_12
+ default "1,2,1" if MODEM_HL78XX_00
+ help
+ AT+KSELACQ=0,1,2,3 , MODEM HL7812,CAT-M1, NB-IoT, GSM
+ AT+KSELACQ=0,1,2,1 , MODEM HL7800,CAT-M1, NB-IoT, CAT-M1
+
+config MODEM_HL78XX_AUTORAT_NB_BAND_CFG
+ string "NB-IoT band configuration (comma-separated list)"
+ default "1,2,3,4,5,8,12,13,20,28"
+ 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 "1,2,3,4,5,8,12,13,20,28"
+ 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_HL78XX_12
+ help
+ Enable GSM mode during modem init.
+ 2 — GSM (for HL7802/HL7812 only)
+
+config MODEM_HL78XX_RAT_NBNTN
+ bool "NB-NTN"
+ depends on MODEM_HL78XX_12_FW_R6
+ help
+ Enable NBNTN mode during modem init.
+ 3 — NBNTN (for HL7810/HL7812 only), It does not support <reboot> = 1
+
+endchoice
+
+menuconfig MODEM_HL78XX_CONFIGURE_BANDS
+ bool "Configure modem bands"
+ depends on !MODEM_HL78XX_AUTORAT
+ 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
+if !MODEM_HL78XX_RAT_NBNTN
+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)
+endif # !MODEM_HL78XX_RAT_NBNTN
+config MODEM_HL78XX_BAND_23
+ bool "Band 23 (2000MHz)"
+ default y if MODEM_HL78XX_RAT_NBNTN
+ help
+ Enable Band 23 (2000MHz)
+if !MODEM_HL78XX_RAT_NBNTN
+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)
+endif # !MODEM_HL78XX_RAT_NBNTN
+config MODEM_HL78XX_BAND_255
+ bool "Band 255 (1500MHz)"
+ default y if MODEM_HL78XX_RAT_NBNTN
+ help
+ Enable Band 255 (1500MHz)
+
+config MODEM_HL78XX_BAND_256
+ bool "Band 256 (2000MHz)"
+ default y if MODEM_HL78XX_RAT_NBNTN
+ help
+ Enable Band 256 (2000MHz)
+
+endif # MODEM_HL78XX_CONFIGURE_BAND
+
+# NB-IoT NTN Position Configuration
+if MODEM_HL78XX_RAT_NBNTN
+menuconfig MODEM_HL78XX_NBNTN_POSITIONING
+ bool "NB-IoT NTN Position Configuration"
+ depends on !MODEM_HL78XX_AUTORAT
+ default y if !MODEM_HL78XX_AUTORAT
+ help
+ Choose this setting to configure NB-IoT NTN Positioning parameters.
+ Only applicable if NB-NTN mode is selected (MODEM_HL78XX_RAT_NBNTN).
+
+if MODEM_HL78XX_NBNTN_POSITIONING
+
+choice
+ prompt "Position Source"
+ default NTN_POSITION_SOURCE_IGNSS
+ help
+ Select the source of UE (User Equipment) position for NTN TA calculation.
+ IGNSS: Use HL781x internal GNSS to acquire position automatically.
+ MANUAL: Manually enter UE position using +KNTNCMD AT command.
+
+config NTN_POSITION_SOURCE_IGNSS
+ bool "IGNSS (internal GNSS)"
+ help
+ Use HL781x internal GNSS to acquire position automatically.
+
+config NTN_POSITION_SOURCE_MANUAL
+ bool "MANUAL (manual entry)"
+ help
+ Manually enter UE position using +KNTNCMD AT command.
+
+endchoice
+
+choice
+ prompt "Mobility Type"
+ default NTN_MOBILITY_TYPE_STATIC
+ help
+ Specify the UE's mobility type.
+ STATIC: Position remains fixed (within 600 meters of initial fix).
+ DYNAMIC: Position may change, requires update for each network operation.
+
+config NTN_MOBILITY_TYPE_STATIC
+ bool "STATIC (fixed position)"
+ help
+ Position remains fixed (within 600 meters of initial fix).
+
+config NTN_MOBILITY_TYPE_DYNAMIC
+ bool "DYNAMIC (moving position)"
+ help
+ Position may change, requires update for each network operation.
+
+endchoice
+
+config NTN_STATIC_THRESHOLD
+ int "Static Mode Threshold (meters)"
+ default 600
+ help
+ Distance between current position and initial fix that determines static/dynamic mode.
+ If position variation exceeds this threshold, dynamic mode is recommended.
+ For documentation/reference only.
+
+config NTN_DYNAMIC_POSREQ_UPDATE
+ bool "Require position update on POSREQ in Dynamic Mode"
+ default y
+ help
+ If enabled, the UE position must be updated after receiving unsolicited +KNTNEV:
+ 'POSREQ' indication, before any outgoing network operation in dynamic mode.
+endif # MODEM_HL78XX_NBNTN_POSITIONING
+endif # MODEM_HL78XX_RAT_NBNTN
+
+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"
+ help
+ Enable LTE eDRX
+
+config MODEM_HL78XX_PSM
+ bool "PSM"
+ default y
+ help
+ Enable Power Save Mode (PSM)
+
+if MODEM_HL78XX_EDRX
+
+config MODEM_HL78XX_EDRX_VALUE
+ string "Requested eDRX timer"
+ default "0101"
+ 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 81.92 seconds.
+
+endif # MODEM_HL78XX_EDRX
+
+if MODEM_HL78XX_PSM
+
+config MODEM_HL78XX_PSM_PERIODIC_TAU
+ string "Requested extended periodic TAU timer"
+ default "10000010"
+ 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 1 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
+
+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_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: <stat>
+ · 2 — Enable network registration and location information unsolicited result
+ code:
+ +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>]]
+ · 3 — Enable network registration, location information and EMM cause value
+ information unsolicited result code:
+ +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,<cause_type>, <reject_cause>]]
+ · 4 — For a UE that wants to apply PSM, enable network registration and
+ location information unsolicited result code:
+ +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,,[,[<Active- Time>],[<PeriodicTAU>]]]]
+ · 5 — For a UE that wants to apply PSM, enable network registration, location
+ information and EMM cause value information unsolicited result code:
+ +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,[<cause_type>],[<reject_-
+ cause>][,[<Active-Time>] [<Periodic-TAU>]]]]
+
+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: <stat>
+
+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: <stat>[,[<tac>],[<ci>],[<AcT>]]
+
+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: <stat>[,[<tac>],[<ci>],[<AcT>][,<cause_type>, <reject_cause>]]
+
+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: <stat>[,[<tac>],[<ci>],[<AcT>][,,[,[<Active- Time>],[<PeriodicTAU>]]]]
+
+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: <stat>[,[<tac>],[<ci>],[<AcT>][,[<cause_type>],[<reject_-
+ cause>][,[<Active-Time>] [<Periodic-TAU>]]]]
+
+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_SOCKET_UDP_DISPLAY_DATA_URC
+ int "display data in URC"
+ default 0
+ 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_NUM_SOCKETS
+ int "Maximum number of sockets"
+ default 6
+ range 6 6 if !MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+ range 1 6 if MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+ help
+ Maximum number of sockets that can be opened at the same time
+
+config MODEM_HL78XX_SOCKETS_SOCKOPT_TLS
+ bool "TLS for sockets"
+ depends on NET_SOCKETS_SOCKOPT_TLS
+ help
+ This option enables TLS (Transport Layer Security) for sockets
+ on the HL78xx modem.
+
+config MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+ bool "Verbose debug output in the HL78xx"
+ depends on MODEM_MODULES_LOG_LEVEL_DBG
+ help
+ Enabling this setting will turn on VERY heavy debugging from the
+ modem. Do NOT leave on for production.
+ This setting is depends on global log level debug.
+
+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 81
+ 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 0000000..92542a4
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx.c
@@ -0,0 +1,1860 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/modem/chat.h>
+#include <zephyr/modem/backend/uart.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/net/net_if.h>
+#include <zephyr/net/net_offload.h>
+#include <zephyr/net/offloaded_netdev.h>
+#include <zephyr/net/socket_offload.h>
+#include <zephyr/posix/fcntl.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/pm/device_runtime.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+#include "hl78xx_cfg.h"
+
+#define MAX_SCRIPT_AT_CMD_RETRY 3
+
+#define MDM_NODE DT_ALIAS(modem)
+/* Check phandle target status for a specific phandle index */
+#define HAS_GPIO_IDX(node_id, prop, idx) DT_PROP_HAS_IDX(node_id, prop, idx)
+
+/* GPIO availability macros */
+#define HAS_RESET_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_reset_gpios, 0)
+#define HAS_WAKE_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_wake_gpios, 0)
+#define HAS_VGPIO_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_vgpio_gpios, 0)
+#define HAS_UART_CTS_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_cts_gpios, 0)
+#define HAS_GPIO6_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_gpio6_gpios, 0)
+#define HAS_PWR_ON_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_pwr_on_gpios, 0)
+#define HAS_FAST_SHUTD_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_fast_shutd_gpios, 0)
+#define HAS_UART_DSR_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_dsr_gpios, 0)
+#define HAS_UART_DTR_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_dtr_gpios, 0)
+#define HAS_GPIO8_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_gpio8_gpios, 0)
+#define HAS_SIM_SWITCH_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_sim_switch_gpios, 0)
+
+/* GPIO count macro */
+#define GPIO_CONFIG_LEN \
+ (HAS_RESET_GPIO + HAS_WAKE_GPIO + HAS_VGPIO_GPIO + HAS_UART_CTS_GPIO + HAS_GPIO6_GPIO + \
+ HAS_PWR_ON_GPIO + HAS_FAST_SHUTD_GPIO + HAS_UART_DSR_GPIO + HAS_UART_DTR_GPIO + \
+ HAS_GPIO8_GPIO + HAS_SIM_SWITCH_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_dispatcher_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);
+
+struct hl78xx_state_handlers {
+ int (*on_enter)(struct hl78xx_data *data);
+ int (*on_leave)(struct hl78xx_data *data);
+ void (*on_event)(struct hl78xx_data *data, enum hl78xx_event evt);
+};
+
+/* Forward declare the table so functions earlier in this file can reference
+ * it. The table itself is defined later in the file (without 'static').
+ */
+const static struct hl78xx_state_handlers hl78xx_state_table[];
+/** Dispatch an event to the registered event dispatcher, if any.
+ *
+ */
+static void event_dispatcher_dispatch(struct hl78xx_evt *notif)
+{
+ if (event_dispatcher != NULL) {
+ event_dispatcher(notif);
+ }
+}
+/* -------------------------------------------------------------------------
+ * Utilities
+ * - small helpers and local utility functions
+ * -------------------------------------------------------------------------
+ */
+
+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_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_REGISTERED:
+ return "await registered";
+ case MODEM_HL78XX_STATE_CARRIER_ON:
+ return "carrier on";
+ case MODEM_HL78XX_STATE_CARRIER_OFF:
+ return "carrier off";
+ case MODEM_HL78XX_STATE_SIM_POWER_OFF:
+ return "sim power off";
+ case MODEM_HL78XX_STATE_AIRPLANE:
+ return "airplane mode";
+ 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_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";
+ 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_INF("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);
+ LOG_DBG("dequeued %d events", events_cnt);
+
+ 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);
+}
+/* -------------------------------------------------------------------------
+ * Chat callbacks / URC handlers
+ * - unsolicited response handlers and chat-related parsers
+ * -------------------------------------------------------------------------
+ */
+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 cellular_registration_status registration_status = 0;
+ struct hl78xx_evt event = {.type = HL78XX_LTE_REGISTRATION_STAT_UPDATE};
+#ifndef CONFIG_MODEM_HL78XX_12
+ enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE;
+ struct hl78xx_evt rat_event;
+ bool rat_mode_updated = false;
+ int act_value = -1;
+#endif /* CONFIG_MODEM_HL78XX_12 */
+ if (argc < 2) {
+ return;
+ }
+ /* +CXREG: <stat>[,<tac>[...]] */
+ 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], 0, "registration_status");
+#ifndef CONFIG_MODEM_HL78XX_12
+ if (argc > 4 && strlen(argv[5]) == 1) {
+ act_value = ATOI(argv[5], -1, "act_value");
+ LOG_DBG("act_value: %d, argc: %d, argv[5]: %s", act_value, argc, argv[5]);
+ switch (act_value) {
+ case 7:
+ rat_mode = HL78XX_RAT_CAT_M1;
+ break;
+ case 9:
+ rat_mode = HL78XX_RAT_NB1;
+ break;
+ default:
+ rat_mode = HL78XX_RAT_MODE_NONE;
+ break;
+ }
+ rat_mode_updated = true;
+ LOG_DBG("RAT mode from response: %d", rat_mode);
+ }
+#endif /* CONFIG_MODEM_HL78XX_12 */
+ } else {
+ registration_status = ATOI(argv[1], 0, "registration_status");
+#ifndef CONFIG_MODEM_HL78XX_12
+ if (argc > 3 && strlen(argv[4]) == 1) {
+ act_value = ATOI(argv[4], -1, "act_value");
+ LOG_DBG("act_value: %d, argc: %d, argv[4]: %s", act_value, argc, argv[4]);
+ switch (act_value) {
+ case 7:
+ rat_mode = HL78XX_RAT_CAT_M1;
+ break;
+ case 9:
+ rat_mode = HL78XX_RAT_NB1;
+ break;
+ default:
+ rat_mode = HL78XX_RAT_MODE_NONE;
+ break;
+ }
+ rat_mode_updated = true;
+ LOG_DBG("RAT mode from URC: %d", rat_mode);
+ }
+#endif /* CONFIG_MODEM_HL78XX_12 */
+ }
+ HL78XX_LOG_DBG("%s: %d", argv[0], registration_status);
+ if (registration_status == data->status.registration.network_state_current) {
+#ifndef CONFIG_MODEM_HL78XX_12
+ /* Check if RAT mode changed even if registration status didn't */
+ if (rat_mode_updated && rat_mode != -1 &&
+ rat_mode != data->status.registration.rat_mode) {
+ data->status.registration.rat_mode = rat_mode;
+ rat_event.type = HL78XX_LTE_RAT_UPDATE;
+ rat_event.content.rat_mode = rat_mode;
+ event_dispatcher_dispatch(&rat_event);
+ }
+#endif /* CONFIG_MODEM_HL78XX_12 */
+ return;
+ }
+ /* Update the previous registration state */
+ 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;
+ event.content.reg_status = data->status.registration.network_state_current;
+
+ data->status.registration.is_registered_previously =
+ data->status.registration.is_registered_currently;
+#ifndef CONFIG_MODEM_HL78XX_12
+ /* Update RAT mode if parsed */
+ if (rat_mode_updated && rat_mode != -1 && rat_mode != data->status.registration.rat_mode) {
+ data->status.registration.rat_mode = rat_mode;
+ rat_event.type = HL78XX_LTE_RAT_UPDATE;
+ rat_event.content.rat_mode = rat_mode;
+ event_dispatcher_dispatch(&rat_event);
+ }
+#endif /* CONFIG_MODEM_HL78XX_12 */
+ /* Update current registration flag */
+ if (hl78xx_is_registered(data)) {
+ data->status.registration.is_registered_currently = true;
+ hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_REGISTERED);
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+ k_sem_give(&data->stay_in_boot_mode_sem);
+#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
+ } else {
+ data->status.registration.is_registered_currently = false;
+ hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEREGISTERED);
+ }
+ event_dispatcher_dispatch(&event);
+}
+
+void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ int module_status;
+ struct hl78xx_evt event = {.type = HL78XX_LTE_MODEM_STARTUP};
+
+ if (argc != 2) {
+ return;
+ }
+ module_status = ATOI(argv[1], 0, "module_status");
+ event.content.value = module_status;
+ event_dispatcher_dispatch(&event);
+ HL78XX_LOG_DBG("Module status: %d", module_status);
+}
+
+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;
+ }
+ HL78XX_LOG_DBG("IMEI: %s %s", argv[0], argv[1]);
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy((char *)data->identity.imei, argv[1], sizeof(data->identity.imei));
+ k_mutex_unlock(&data->api_lock);
+}
+
+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;
+ }
+ HL78XX_LOG_DBG("cgmm: %s %s", argv[0], argv[1]);
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy((char *)data->identity.model_id, argv[1], sizeof(data->identity.model_id));
+ k_mutex_unlock(&data->api_lock);
+}
+
+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;
+ }
+ HL78XX_LOG_DBG("IMSI: %s %s", argv[0], argv[1]);
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy((char *)data->identity.imsi, argv[1], sizeof(data->identity.imsi));
+ k_mutex_unlock(&data->api_lock);
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
+ /* set the APN automatically */
+ modem_detect_apn(data, argv[1]);
+#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI */
+}
+
+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;
+ }
+ HL78XX_LOG_DBG("cgmi: %s %s", argv[0], argv[1]);
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy((char *)data->identity.manufacturer, argv[1],
+ sizeof(data->identity.manufacturer));
+ k_mutex_unlock(&data->api_lock);
+}
+
+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;
+ }
+ HL78XX_LOG_DBG("cgmr: %s %s", argv[0], argv[1]);
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy((char *)data->identity.fw_version, argv[1], sizeof(data->identity.fw_version));
+ k_mutex_unlock(&data->api_lock);
+}
+
+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;
+ }
+ HL78XX_LOG_DBG("ICCID: %s %s", argv[0], argv[1]);
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy((char *)data->identity.iccid, argv[1], sizeof(data->identity.iccid));
+ k_mutex_unlock(&data->api_lock);
+
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID)
+ /* set the APN automatically */
+ modem_detect_apn(data, argv[1]);
+#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID */
+}
+
+#if defined(CONFIG_MODEM_HL78XX_12)
+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;
+ struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE};
+
+ if (argc != 3) {
+ return;
+ }
+ rat_mode = ATOI(argv[2], 0, "rat_mode");
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+ hl78xx_on_kstatev_parser(data, (enum hl78xx_info_transfer_event)ATOI(argv[1], 0, "status"),
+ rat_mode);
+#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
+ if (rat_mode != data->status.registration.rat_mode) {
+ data->status.registration.rat_mode = rat_mode;
+ event.content.rat_mode = data->status.registration.rat_mode;
+ event_dispatcher_dispatch(&event);
+ }
+}
+#endif
+
+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 = ATOI(argv[1], 0, "ksrep");
+ HL78XX_LOG_DBG("KSREP: %s %s", argv[0], argv[1]);
+}
+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;
+ struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE};
+
+ if (argc < 2) {
+ return;
+ }
+ data->status.registration.rat_mode = (uint8_t)ATOI(argv[1], 0, "rat_mode");
+ event.content.rat_mode = data->status.registration.rat_mode;
+ event_dispatcher_dispatch(&event);
+ HL78XX_LOG_DBG("KSRAT: %s %s", argv[0], argv[1]);
+}
+
+void hl78xx_on_kselacq(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;
+ }
+ 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;
+ }
+}
+
+void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ uint8_t rat_id;
+ uint8_t kbnd_bitmap_size;
+
+ if (argc < 3) {
+ return;
+ }
+
+ rat_id = ATOI(argv[1], 0, "rat");
+ kbnd_bitmap_size = strlen(argv[2]);
+ HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], 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';
+}
+
+void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+ if (argc < 3) {
+ return;
+ }
+ data->status.rssi = ATOI(argv[1], 0, "rssi");
+}
+
+void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+ if (argc < 7) {
+ return;
+ }
+ data->status.rsrq = ATOI(argv[5], 0, "rsrq");
+ data->status.rsrp = ATOI(argv[6], 0, "rsrp");
+}
+
+void hl78xx_on_cfun(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.phone_functionality.functionality = ATOI(argv[1], 0, "phone_func");
+ data->status.phone_functionality.in_progress = false;
+}
+
+void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+ if (argc < 3) {
+ return;
+ }
+ safe_strncpy((char *)data->status.network_operator.operator, argv[3],
+ sizeof(data->status.network_operator.operator));
+ data->status.network_operator.format = ATOI(argv[2], 0, "network_operator_format");
+}
+
+/* -------------------------------------------------------------------------
+ * Pipe & chat initialization
+ * - modem backend pipe setup and chat initialisation helpers
+ * -------------------------------------------------------------------------
+ */
+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);
+}
+
+/* Initialize the modem chat subsystem using wrappers from hl78xx_chat.c */
+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 = (uint16_t)ARRAY_SIZE(data->buffers.argv),
+ .unsol_matches = hl78xx_get_unsol_matches(),
+ .unsol_matches_size = (uint16_t)hl78xx_get_unsol_matches_size(),
+ };
+
+ return modem_chat_init(&data->chat, &chat_config);
+}
+
+/* clang-format off */
+int modem_dynamic_cmd_send(
+ 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;
+ int script_ret = 0;
+ /* validate input parameters */
+ if (data == NULL) {
+ LOG_ERR("%d %s Invalid parameter", __LINE__, __func__);
+ errno = EINVAL;
+ return -1;
+ }
+ 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 = hl78xx_get_abort_matches(),
+ .abort_matches_size = hl78xx_get_abort_matches_size(),
+ .callback = script_user_callback,
+ .timeout = 1000
+ };
+
+ ret = k_mutex_lock(&data->tx_lock, K_NO_WAIT);
+ if (ret < 0) {
+ if (user_cmd == false) {
+ errno = -ret;
+ }
+ return -1;
+ }
+ /* run the chat script */
+ script_ret = modem_chat_run_script(&data->chat, &chat_script);
+ if (script_ret < 0) {
+ LOG_ERR("%d %s Failed to run at command: %d", __LINE__, __func__, script_ret);
+ } else {
+ LOG_DBG("Chat script executed successfully.");
+ }
+ ret = k_mutex_unlock(&data->tx_lock);
+ if (ret < 0) {
+ if (user_cmd == false) {
+ errno = -ret;
+ }
+ /* we still return the script result if available, prioritize script_ret */
+ return script_ret < 0 ? -1 : script_ret;
+ }
+ return script_ret;
+}
+/* clang-format on */
+
+/* -------------------------------------------------------------------------
+ * GPIO ISR callbacks
+ * - lightweight wrappers for GPIO interrupts (logging & event dispatch)
+ * -------------------------------------------------------------------------
+ */
+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;
+
+ 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 */
+ }
+ LOG_DBG("GPIO6 ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec));
+}
+
+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));
+}
+
+bool hl78xx_is_registered(struct hl78xx_data *data)
+{
+ return (data->status.registration.network_state_current ==
+ CELLULAR_REGISTRATION_REGISTERED_HOME) ||
+ (data->status.registration.network_state_current ==
+ CELLULAR_REGISTRATION_REGISTERED_ROAMING);
+}
+
+/*
+ * hl78xx_is_registered - convenience helper
+ *
+ * Simple predicate to test if the modem reports a registered state.
+ */
+
+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;
+}
+
+/* -------------------------------------------------------------------------
+ * State machine handlers
+ * - state enter/leave and per-state event handlers
+ * -------------------------------------------------------------------------
+ */
+
+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)
+{
+ switch (evt) {
+ case MODEM_HL78XX_EVENT_BUS_OPENED:
+ modem_chat_attach(&data->chat, data->uart_pipe);
+ /* Run init script via chat TU wrapper (script symbols live in hl78xx_chat.c) */
+ hl78xx_run_init_script_async(data);
+ break;
+
+ case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+ hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_RAT_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;
+ }
+}
+
+static int hl78xx_on_run_init_diagnose_script_state_enter(struct hl78xx_data *data)
+{
+ hl78xx_run_init_fail_script_async(data);
+ 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) {
+ hl78xx_run_enable_ksup_urc_script_async(data);
+ 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_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;
+ const char *cmd_restart = (const char *)SET_AIRPLANE_MODE_CMD;
+
+ 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) {
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_restart, strlen(cmd_restart),
+ hl78xx_get_ok_match(), 1, false);
+ if (ret < 0) {
+ goto error;
+ }
+ hl78xx_start_timer(data,
+ K_MSEC(config->shutdown_time_ms + config->startup_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;
+
+ switch (evt) {
+ case MODEM_HL78XX_EVENT_TIMEOUT:
+ LOG_DBG("Rebooting modem to apply new RAT settings");
+ ret = hl78xx_run_post_restart_script_async(data);
+ if (ret < 0) {
+ hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND);
+ }
+ 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_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;
+ /* Apply the APN if not configured yet */
+ if (data->status.apn.state == APN_STATE_REFRESH_REQUESTED) {
+ /* APN has been updated by the user, apply the new APN */
+ HL78XX_LOG_DBG("APN refresh requested, applying new APN: \"%s\"",
+ data->identity.apn);
+ data->status.apn.state = APN_STATE_NOT_CONFIGURED;
+ } else {
+#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_registered_state_enter(struct hl78xx_data *data)
+{
+ 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:
+ hl78xx_start_timer(data, K_SECONDS(MDM_REGISTRATION_TIMEOUT));
+ break;
+
+ case MODEM_HL78XX_EVENT_TIMEOUT:
+ /**
+ * No need to run periodic script to check registration status because URC is used
+ * to notify the status change.
+ *
+ * If the modem is not registered within the timeout period, it will stay in this
+ * state forever.
+ *
+ * @attention MDM_REGISTRATION_TIMEOUT should be long enough to allow the modem to
+ * register to the network, especially for the first time registration. And also
+ * consider the network conditions / number of bands etc.. that may affect
+ * the registration process.
+ *
+ * TODO: add a mechanism to exit this state and retry the registration process
+ *
+ */
+ LOG_WRN("Modem failed to register to the network within %d seconds",
+ MDM_REGISTRATION_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_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)
+{
+ notif_carrier_on(data->dev);
+ iface_status_work_cb(data, hl78xx_chat_callback_handler);
+ 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:
+ hl78xx_start_timer(data, K_SECONDS(2));
+
+ break;
+ case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+ break;
+
+ case MODEM_HL78XX_EVENT_TIMEOUT:
+ dns_work_cb(data->dev, true);
+ 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_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)
+{
+ notif_carrier_off(data->dev);
+ /* Check whether or not there is any sockets are connected,
+ * if true, wait until sockets are closed properly
+ */
+ if (check_if_any_socket_connected(data->dev) == false) {
+ hl78xx_start_timer(data, K_MSEC(100));
+ } else {
+ /* There are still active sockets, wait until they are closed */
+ hl78xx_start_timer(data, K_MSEC(5000));
+ }
+ 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:
+ hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT);
+ 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;
+}
+
+/* pwroff script moved to hl78xx_chat.c */
+static int hl78xx_on_init_power_off_state_enter(struct hl78xx_data *data)
+{
+ /**
+ * Eventhough you have power switch or etc.., start the power off script first
+ * to gracefully disconnect from the network
+ *
+ * IMSI detach before powering down IS recommended by the AT command manual
+ *
+ */
+ return hl78xx_run_pwroff_script_async(data);
+}
+
+static void hl78xx_init_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+ switch (evt) {
+ case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+ 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)
+{
+ 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)) {
+ gpio_pin_set_dt(&config->mdm_gpio_wake, 0);
+ }
+
+ if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+ gpio_pin_set_dt(&config->mdm_gpio_reset, 1);
+ }
+ 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;
+
+ switch (evt) {
+ case MODEM_HL78XX_EVENT_BUS_CLOSED:
+ break;
+ case MODEM_HL78XX_EVENT_RESUME:
+ 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_FAIL_DIAGNOSTIC_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;
+
+ 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);
+ }
+
+ return 0;
+}
+
+static int hl78xx_on_state_enter(struct hl78xx_data *data)
+{
+ int ret = 0;
+ enum hl78xx_state s = data->status.state;
+
+ /* Use an explicit bounds check against the last enum value so this
+ * code can reference the table even though the table is defined later
+ * in the file. MODEM_HL78XX_STATE_AWAIT_POWER_OFF is the last value in
+ * the `enum hl78xx_state`.
+ */
+ if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_enter) {
+ ret = hl78xx_state_table[s].on_enter(data);
+ }
+
+ return ret;
+}
+
+static int hl78xx_on_state_leave(struct hl78xx_data *data)
+{
+ int ret = 0;
+ enum hl78xx_state s = data->status.state;
+
+ if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_leave) {
+ ret = hl78xx_state_table[s].on_leave(data);
+ }
+
+ 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;
+ enum hl78xx_state s;
+
+ hl78xx_log_event(evt);
+ s = data->status.state;
+ state = data->status.state;
+ if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_event) {
+ hl78xx_state_table[s].on_event(data, evt);
+ } else {
+ LOG_ERR("%d %s unknown event", __LINE__, __func__);
+ }
+ if (state != s) {
+ hl78xx_log_state_changed(state, s);
+ }
+}
+
+#ifdef CONFIG_PM_DEVICE
+
+/* -------------------------------------------------------------------------
+ * Power management
+ * -------------------------------------------------------------------------
+ */
+
+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 PM_DEVICE_ACTION_SUSPEND", __LINE__);
+ 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 PM_DEVICE_ACTION_RESUME", __LINE__);
+ /* 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 PM_DEVICE_ACTION_TURN_ON", __LINE__);
+ break;
+ case PM_DEVICE_ACTION_TURN_OFF:
+ /*
+ * power off the device, used when the power
+ * domain this device belongs is suspended.
+ */
+ LOG_DBG("%d PM_DEVICE_ACTION_TURN_OFF", __LINE__);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ return ret;
+}
+#endif /* CONFIG_PM_DEVICE */
+
+/* -------------------------------------------------------------------------
+ * Initialization
+ * -------------------------------------------------------------------------
+ */
+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;
+
+ k_mutex_init(&data->api_lock);
+ k_mutex_init(&data->tx_lock);
+ /* 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);
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+ k_sem_init(&data->stay_in_boot_mode_sem, 0, 1);
+#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
+ k_sem_init(&data->script_stopped_sem_tx_int, 0, 1);
+ k_sem_init(&data->script_stopped_sem_rx_int, 0, 1);
+ data->dev = dev;
+ /* reset to default */
+ data->buffers.eof_pattern_size = strlen(data->buffers.eof_pattern);
+ data->buffers.termination_pattern_size = strlen(data->buffers.termination_pattern);
+ memset(data->identity.apn, 0, MDM_APN_MAX_LENGTH);
+ /* GPIO validation */
+ const struct gpio_dt_spec *gpio_pins[GPIO_CONFIG_LEN] = {
+#if HAS_RESET_GPIO
+ &config->mdm_gpio_reset,
+#endif
+#if HAS_WAKE_GPIO
+ &config->mdm_gpio_wake,
+#endif
+#if HAS_VGPIO_GPIO
+ &config->mdm_gpio_vgpio,
+#endif
+#if HAS_UART_CTS_GPIO
+ &config->mdm_gpio_uart_cts,
+#endif
+#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_shutdown,
+#endif
+#if HAS_UART_DSR_GPIO
+ &config->mdm_gpio_uart_dsr,
+#endif
+#if HAS_UART_DTR_GPIO
+ &config->mdm_gpio_uart_dtr,
+#endif
+#if HAS_GPIO8_GPIO
+ &config->mdm_gpio_gpio8,
+#endif
+#if HAS_SIM_SWITCH_GPIO
+ &config->mdm_gpio_sim_switch,
+#endif
+ };
+ for (int i = 0; i < ARRAY_SIZE(gpio_pins); i++) {
+ if (gpio_pins[i] == NULL || !gpio_is_ready_dt(gpio_pins[i])) {
+ const char *port_name = "unknown";
+
+ if (gpio_pins[i] != NULL && gpio_pins[i]->port != NULL) {
+ port_name = gpio_pins[i]->port->name;
+ }
+ LOG_ERR("GPIO port (%s) not ready!", 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] = {
+#if HAS_RESET_GPIO
+ {&config->mdm_gpio_reset, GPIO_OUTPUT, "reset"},
+#endif
+#if HAS_WAKE_GPIO
+ {&config->mdm_gpio_wake, GPIO_OUTPUT, "wake"},
+#endif
+#if HAS_VGPIO_GPIO
+ {&config->mdm_gpio_vgpio, GPIO_INPUT, "VGPIO"},
+#endif
+#if HAS_UART_CTS_GPIO
+ {&config->mdm_gpio_uart_cts, GPIO_INPUT, "CTS"},
+#endif
+#if HAS_GPIO6_GPIO
+ {&config->mdm_gpio_gpio6, GPIO_INPUT, "GPIO6"},
+#endif
+#if HAS_PWR_ON_GPIO
+ {&config->mdm_gpio_pwr_on, GPIO_OUTPUT, "pwr_on"},
+#endif
+#if HAS_FAST_SHUTD_GPIO
+ {&config->mdm_gpio_fast_shutdown, GPIO_OUTPUT, "fast_shutdown"},
+#endif
+#if HAS_UART_DSR_GPIO
+ {&config->mdm_gpio_uart_dsr, GPIO_INPUT, "DSR"},
+#endif
+#if HAS_UART_DTR_GPIO
+ {&config->mdm_gpio_uart_dtr, GPIO_OUTPUT, "DTR"},
+#endif
+#if HAS_GPIO8_GPIO
+ {&config->mdm_gpio_gpio8, GPIO_INPUT, "GPIO8"},
+#endif
+#if HAS_SIM_SWITCH_GPIO
+ {&config->mdm_gpio_sim_switch, GPIO_INPUT, "SIM_SWITCH"},
+#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;
+ }
+ }
+#if HAS_VGPIO_GPIO
+ /* 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;
+ }
+#endif /* HAS_VGPIO_GPIO */
+#if HAS_GPIO6_GPIO
+ /* 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 */
+ /* UART pipe initialization */
+ (void)hl78xx_init_pipe(dev);
+
+ ret = modem_init_chat(dev);
+ if (ret < 0) {
+ goto error;
+ }
+
+#ifndef CONFIG_PM_DEVICE
+ hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME);
+#else
+ pm_device_init_suspended(dev);
+#endif /* CONFIG_PM_DEVICE */
+
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+ k_sem_take(&data->stay_in_boot_mode_sem, K_FOREVER);
+#endif
+ return 0;
+error:
+ return ret;
+}
+
+int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatcher_t handler)
+{
+ event_dispatcher = handler;
+ return 0;
+}
+
+/*
+ * State handler table
+ * Maps each hl78xx_state to optional enter/leave/event handlers. NULL
+ * entries mean the state has no action for that phase.
+ */
+
+/* clang-format off */
+const static struct hl78xx_state_handlers hl78xx_state_table[] = {
+ [MODEM_HL78XX_STATE_IDLE] = {
+ hl78xx_on_idle_state_enter,
+ hl78xx_on_idle_state_leave,
+ hl78xx_idle_event_handler
+ },
+ [MODEM_HL78XX_STATE_RESET_PULSE] = {
+ hl78xx_on_reset_pulse_state_enter,
+ hl78xx_on_reset_pulse_state_leave,
+ hl78xx_reset_pulse_event_handler
+ },
+ [MODEM_HL78XX_STATE_POWER_ON_PULSE] = {
+ hl78xx_on_power_on_pulse_state_enter,
+ hl78xx_on_power_on_pulse_state_leave,
+ hl78xx_power_on_pulse_event_handler
+ },
+ [MODEM_HL78XX_STATE_AWAIT_POWER_ON] = {
+ hl78xx_on_await_power_on_state_enter,
+ NULL,
+ hl78xx_await_power_on_event_handler
+ },
+ [MODEM_HL78XX_STATE_SET_BAUDRATE] = {
+ NULL,
+ NULL,
+ NULL
+ },
+ [MODEM_HL78XX_STATE_RUN_INIT_SCRIPT] = {
+ hl78xx_on_run_init_script_state_enter,
+ NULL,
+ hl78xx_run_init_script_event_handler
+ },
+ [MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT] = {
+ hl78xx_on_run_init_diagnose_script_state_enter,
+ NULL,
+ hl78xx_run_init_fail_script_event_handler
+ },
+ [MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT] = {
+ hl78xx_on_rat_cfg_script_state_enter,
+ NULL,
+ hl78xx_run_rat_cfg_script_event_handler
+ },
+ [MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT] = {
+ hl78xx_on_enable_gprs_state_enter,
+ NULL,
+ hl78xx_enable_gprs_event_handler
+ },
+ [MODEM_HL78XX_STATE_AWAIT_REGISTERED] = {
+ hl78xx_on_await_registered_state_enter,
+ hl78xx_on_await_registered_state_leave,
+ hl78xx_await_registered_event_handler
+ },
+ [MODEM_HL78XX_STATE_CARRIER_ON] = {
+ hl78xx_on_carrier_on_state_enter,
+ hl78xx_on_carrier_on_state_leave,
+ hl78xx_carrier_on_event_handler
+ },
+ [MODEM_HL78XX_STATE_CARRIER_OFF] = {
+ hl78xx_on_carrier_off_state_enter,
+ hl78xx_on_carrier_off_state_leave,
+ hl78xx_carrier_off_event_handler
+ },
+ [MODEM_HL78XX_STATE_SIM_POWER_OFF] = {
+ NULL,
+ NULL,
+ NULL
+ },
+ [MODEM_HL78XX_STATE_AIRPLANE] = {
+ NULL,
+ NULL,
+ NULL
+ },
+ [MODEM_HL78XX_STATE_INIT_POWER_OFF] = {
+ hl78xx_on_init_power_off_state_enter,
+ hl78xx_on_init_power_off_state_leave,
+ hl78xx_init_power_off_event_handler
+ },
+ [MODEM_HL78XX_STATE_POWER_OFF_PULSE] = {
+ hl78xx_on_power_off_pulse_state_enter,
+ hl78xx_on_power_off_pulse_state_leave,
+ hl78xx_power_off_pulse_event_handler
+ },
+ [MODEM_HL78XX_STATE_AWAIT_POWER_OFF] = {
+ hl78xx_on_await_power_off_state_enter,
+ NULL,
+ hl78xx_await_power_off_event_handler
+ },
+};
+/* clang-format on */
+static DEVICE_API(cellular, hl78xx_api) = {
+ .get_signal = hl78xx_api_func_get_signal,
+ .get_modem_info = hl78xx_api_func_get_modem_info_standard,
+ .get_registration_status = hl78xx_api_func_get_registration_status,
+ .set_apn = hl78xx_api_func_set_apn,
+ .set_callback = NULL,
+};
+/* -------------------------------------------------------------------------
+ * Device API and DT registration
+ * -------------------------------------------------------------------------
+ */
+#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_shutdown = \
+ 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, \
+ .buffers.termination_pattern = TERMINATION_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, NULL, NULL)
+
+#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 0000000..d48f5b2
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx.h
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef HL78XX_H
+#define HL78XX_H
+
+#include <zephyr/kernel.h>
+#include <zephyr/device.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/modem/chat.h>
+#include <zephyr/modem/pipe.h>
+#include <zephyr/modem/pipelink.h>
+#include <zephyr/modem/backend/uart.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/sys/atomic.h>
+#include <zephyr/net/net_if.h>
+#include <zephyr/net/net_offload.h>
+#include <zephyr/net/offloaded_netdev.h>
+#include <zephyr/net/socket_offload.h>
+#include <zephyr/drivers/modem/hl78xx_apis.h>
+
+#include "../modem_context.h"
+#include "../modem_socket.h"
+#include <stdint.h>
+
+#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 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 CONFIG_MODEM_HL78XX_NUM_SOCKETS
+#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 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
+
+/* Modem Communication Patterns */
+#define EOF_PATTERN "--EOF--Pattern--"
+#define TERMINATION_PATTERN "+++"
+#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 SET_SIM_PWR_OFF_MODE_CMD "AT+CFUN=0"
+#define GET_FULLFUNCTIONAL_MODE_CMD "AT+CFUN?"
+#define MDM_POWER_OFF_CMD_LEGACY "AT+CPWROFF"
+#define MDM_POWER_FAST_OFF_CMD_LEGACY "AT+CPWROFF=1"
+/* 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__)
+
+#define HL78XX_LOG_DBG(str, ...) \
+ COND_CODE_1(CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG, \
+ (LOG_DBG(str, ##__VA_ARGS__)), \
+ ((void)0))
+
+/* Enums */
+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_ENABLE_GPRS_SCRIPT,
+ /* Full functionality, searching
+ * CFUN=1
+ */
+ MODEM_HL78XX_STATE_AWAIT_REGISTERED,
+ 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,
+};
+
+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 representing information transfer capability events */
+enum hl78xx_info_transfer_event {
+ EVENT_START_SCAN = 0,
+ EVENT_FAIL_SCAN,
+ EVENT_ENTER_CAMPED,
+ EVENT_CONNECTION_ESTABLISHMENT,
+ EVENT_START_RESCAN,
+ EVENT_RRC_CONNECTED,
+ EVENT_NO_SUITABLE_CELLS,
+ EVENT_ALL_REGISTRATION_FAILED
+};
+
+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];
+};
+
+enum apn_state_enum_t {
+ APN_STATE_NOT_CONFIGURED = 0,
+ APN_STATE_CONFIGURED,
+ APN_STATE_REFRESH_REQUESTED,
+ APN_STATE_REFRESH_IN_PROGRESS,
+ APN_STATE_REFRESH_COMPLETED,
+};
+
+struct apn_state {
+ enum apn_state_enum_t state;
+};
+struct registration_status {
+ bool is_registered_currently;
+ bool is_registered_previously;
+ enum cellular_registration_status network_state_current;
+ enum cellular_registration_status network_state_previous;
+ enum hl78xx_cell_rat_mode rat_mode;
+};
+/* 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;
+ uint8_t *termination_pattern;
+ uint8_t termination_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 hl78xx_phone_functionality_work {
+ enum hl78xx_phone_functionality functionality;
+ bool in_progress;
+};
+
+struct hl78xx_network_operator {
+ char operator[MDM_MODEL_LENGTH];
+ uint8_t format;
+};
+
+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;
+ struct kband_syntax kbndcfg[HL78XX_RAT_COUNT];
+ struct hl78xx_phone_functionality_work phone_functionality;
+ struct apn_state apn;
+ struct hl78xx_network_operator network_operator;
+};
+
+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_mutex api_lock;
+ struct k_sem script_stopped_sem_tx_int;
+ struct k_sem script_stopped_sem_rx_int;
+ struct k_sem suspended_sem;
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+ struct k_sem stay_in_boot_mode_sem;
+#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
+
+ 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;
+ /* Track leftover socket data state previously stored as a TU-global.
+ * Moving this into the per-modem data reduces global BSS and keeps
+ * state colocated with the modem instance.
+ */
+ atomic_t state_leftover;
+#if defined(CONFIG_MODEM_HL78XX_RSSI_WORK)
+ struct k_work_delayable rssi_query_work;
+#endif
+
+ const struct device *dev;
+ /* GNSS device */
+ const struct device *gnss_dev;
+ /* Offload device */
+ const struct device *offload_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_vgpio;
+ struct gpio_dt_spec mdm_gpio_uart_cts;
+ struct gpio_dt_spec mdm_gpio_gpio6;
+ struct gpio_dt_spec mdm_gpio_fast_shutdown;
+ struct gpio_dt_spec mdm_gpio_uart_dtr;
+ struct gpio_dt_spec mdm_gpio_uart_dsr;
+ 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 DNS resolution work callback.
+ *
+ * @param dev Pointer to the device structure.
+ * @param hard_reset Boolean indicating if a hard reset is required.
+ * Should be used internally to handle DNS resolution events.
+ */
+void dns_work_cb(const struct device *dev, bool hard_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 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_dynamic_cmd_send(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);
+
+#define HASH_MULTIPLIER 37
+/**
+ * @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.
+ */
+static inline 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;
+}
+
+/**
+ * @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 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
+ */
+static inline 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') {
+ return err_value;
+ }
+ return ret;
+}
+
+/**
+ * @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.
+ */
+static inline 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') {
+ return err_value;
+ }
+ return ret;
+}
+/**
+ * @brief Small utility: safe strncpy that always NUL-terminates the destination.
+ * This function copies a string from src to dst, ensuring that the destination
+ * buffer is always NUL-terminated, even if the source string is longer than
+ * the destination buffer.
+ * @param dst Destination buffer.
+ * @param src Source string.
+ * @param dst_size Size of the destination buffer.
+ */
+static inline void safe_strncpy(char *dst, const char *src, size_t dst_size)
+{
+ size_t len = 0;
+
+ if (dst == NULL || dst_size == 0) {
+ return;
+ }
+ if (src == NULL) {
+ dst[0] = '\0';
+ return;
+ }
+ len = strlen(src);
+ if (len >= dst_size) {
+ len = dst_size - 1;
+ }
+ memcpy(dst, src, len);
+ dst[len] = '\0';
+}
+
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+/**
+ * @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, int rat_mode);
+#endif
+
+#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 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).
+ */
+int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out);
+
+/**
+ * @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 cellular_access_technology tech,
+ enum cellular_registration_status *status);
+
+/**
+ * @brief hl78xx_api_func_set_apn - Brief description of the function.
+ * @param dev Description of dev.
+ * @param apn Description of apn.
+ * @return int Description of return value.
+ */
+int hl78xx_api_func_set_apn(const struct device *dev, const char *apn);
+
+/**
+ * @brief hl78xx_api_func_get_modem_info_standard - 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_standard(const struct device *dev,
+ enum cellular_modem_info_type type, char *info,
+ size_t 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 dev Description of dev.
+ */
+void notif_carrier_off(const struct device *dev);
+
+/**
+ * @brief notif_carrier_on - Brief description of the function.
+ * @param dev Description of dev.
+ */
+void notif_carrier_on(const struct device *dev);
+
+/**
+ * @brief check_if_any_socket_connected - Brief description of the function.
+ * @param dev Description of dev.
+ * @return int Description of return value.
+ */
+int check_if_any_socket_connected(const struct device *dev);
+
+#endif /* HL78XX_H */
diff --git a/drivers/modem/hl78xx/hl78xx_apis.c b/drivers/modem/hl78xx/hl78xx_apis.c
new file mode 100644
index 0000000..e1af412
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_apis.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/kernel.h>
+#include <zephyr/logging/log.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+
+LOG_MODULE_REGISTER(hl78xx_apis, CONFIG_MODEM_LOG_LEVEL);
+
+/* Wrapper to centralize modem_dynamic_cmd_send calls and reduce repetition.
+ * returns negative errno on failure or the value returned by modem_dynamic_cmd_send.
+ */
+static int hl78xx_send_cmd(struct hl78xx_data *data, const char *cmd,
+ void (*chat_cb)(struct modem_chat *, enum modem_chat_script_result,
+ void *),
+ const struct modem_chat_match *matches, uint16_t match_count)
+{
+ if (data == NULL || cmd == NULL) {
+ return -EINVAL;
+ }
+ return modem_dynamic_cmd_send(data, chat_cb, cmd, (uint16_t)strlen(cmd), matches,
+ match_count, true);
+}
+
+int hl78xx_api_func_get_signal(const struct device *dev, const enum cellular_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";
+
+ /* quick check of state under api_lock */
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ if (data->status.state != MODEM_HL78XX_STATE_CARRIER_ON) {
+ k_mutex_unlock(&data->api_lock);
+ return -ENODATA;
+ }
+ k_mutex_unlock(&data->api_lock);
+
+ /* Run chat script */
+ switch (type) {
+ case CELLULAR_SIGNAL_RSSI:
+ ret = hl78xx_send_cmd(data, signal_cmd_csq, NULL, hl78xx_get_allow_match(),
+ hl78xx_get_allow_match_size());
+ break;
+
+ case CELLULAR_SIGNAL_RSRP:
+ case CELLULAR_SIGNAL_RSRQ:
+ ret = hl78xx_send_cmd(data, signal_cmd_cesq, NULL, hl78xx_get_allow_match(),
+ hl78xx_get_allow_match_size());
+ break;
+
+ default:
+ ret = -ENOTSUP;
+ break;
+ }
+ /* Verify chat script ran successfully */
+ if (ret < 0) {
+ return ret;
+ }
+ /* Parse received value */
+ switch (type) {
+ case CELLULAR_SIGNAL_RSSI:
+ ret = hl78xx_parse_rssi(data->status.rssi, value);
+ break;
+
+ case CELLULAR_SIGNAL_RSRP:
+ ret = hl78xx_parse_rsrp(data->status.rsrp, value);
+ break;
+
+ case CELLULAR_SIGNAL_RSRQ:
+ ret = hl78xx_parse_rsrq(data->status.rsrq, value);
+ break;
+
+ default:
+ ret = -ENOTSUP;
+ break;
+ }
+ return ret;
+}
+
+/** Convert hl78xx RAT mode to cellular access technology */
+enum cellular_access_technology hl78xx_rat_to_access_tech(enum hl78xx_cell_rat_mode rat_mode)
+{
+ switch (rat_mode) {
+ case HL78XX_RAT_CAT_M1:
+ return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN;
+ case HL78XX_RAT_NB1:
+ return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN_NB_S1;
+#ifdef CONFIG_MODEM_HL78XX_12
+ case HL78XX_RAT_GSM:
+ return CELLULAR_ACCESS_TECHNOLOGY_GSM;
+#ifdef CONFIG_MODEM_HL78XX_12_FW_R6
+ case HL78XX_RAT_NBNTN:
+ /** NBNTN might not have a direct mapping; choose closest or define new */
+ return CELLULAR_ACCESS_TECHNOLOGY_NG_RAN_SAT;
+#endif
+#endif
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT
+ case HL78XX_RAT_MODE_AUTO:
+ /** AUTO mode doesn't map directly; return LTE as default or NONE */
+ return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN;
+#endif
+ case HL78XX_RAT_MODE_NONE:
+ default:
+ return -ENODATA;
+ }
+}
+
+int hl78xx_api_func_get_registration_status(const struct device *dev,
+ enum cellular_access_technology tech,
+ enum cellular_registration_status *status)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+
+ if (status == NULL) {
+ return -EINVAL;
+ }
+ LOG_DBG("Requested tech: %d, current rat mode: %d REG: %d %d", tech,
+ data->status.registration.rat_mode, data->status.registration.network_state_current,
+ hl78xx_rat_to_access_tech(data->status.registration.rat_mode));
+ if (tech != hl78xx_rat_to_access_tech(data->status.registration.rat_mode)) {
+ return -ENODATA;
+ }
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ *status = data->status.registration.network_state_current;
+ k_mutex_unlock(&data->api_lock);
+ return 0;
+}
+
+int hl78xx_api_func_get_modem_info_vendor(const struct device *dev,
+ enum hl78xx_modem_info_type type, void *info, size_t size)
+{
+ int ret = 0;
+ struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+ const char *network_operator = "AT+COPS?";
+
+ if (info == NULL || size == 0) {
+ return -EINVAL;
+ }
+ /* copy identity under api lock to a local buffer then write to caller
+ * prevents holding lock during the return/caller access
+ */
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ switch (type) {
+ case HL78XX_MODEM_INFO_APN:
+ if (data->status.apn.state != APN_STATE_CONFIGURED) {
+ ret = -ENODATA;
+ break;
+ }
+ safe_strncpy(info, (const char *)data->identity.apn, size);
+ break;
+
+ case HL78XX_MODEM_INFO_CURRENT_RAT:
+ *(enum hl78xx_cell_rat_mode *)info = data->status.registration.rat_mode;
+ break;
+
+ case HL78XX_MODEM_INFO_NETWORK_OPERATOR:
+ /* Network operator not currently tracked; return empty or implement tracking */
+ ret = hl78xx_send_cmd(data, network_operator, NULL, hl78xx_get_allow_match(),
+ hl78xx_get_allow_match_size());
+ if (ret < 0) {
+ LOG_ERR("Failed to get network operator");
+ }
+ safe_strncpy(info, (const char *)data->status.network_operator.operator,
+ MIN(size, sizeof(data->status.network_operator.operator)));
+ break;
+
+ default:
+ break;
+ }
+ k_mutex_unlock(&data->api_lock);
+ return ret;
+}
+
+int hl78xx_api_func_get_modem_info_standard(const struct device *dev,
+ enum cellular_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;
+ }
+ /* copy identity under api lock to a local buffer then write to caller
+ * prevents holding lock during the return/caller access
+ */
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ switch (type) {
+ case CELLULAR_MODEM_INFO_IMEI:
+ safe_strncpy(info, (const char *)data->identity.imei,
+ MIN(size, sizeof(data->identity.imei)));
+ break;
+ case CELLULAR_MODEM_INFO_SIM_IMSI:
+ safe_strncpy(info, (const char *)data->identity.imsi,
+ MIN(size, sizeof(data->identity.imsi)));
+ break;
+ case CELLULAR_MODEM_INFO_MANUFACTURER:
+ safe_strncpy(info, (const char *)data->identity.manufacturer,
+ MIN(size, sizeof(data->identity.manufacturer)));
+ break;
+ case CELLULAR_MODEM_INFO_FW_VERSION:
+ safe_strncpy(info, (const char *)data->identity.fw_version,
+ MIN(size, sizeof(data->identity.fw_version)));
+ break;
+ case CELLULAR_MODEM_INFO_MODEL_ID:
+ safe_strncpy(info, (const char *)data->identity.model_id,
+ MIN(size, sizeof(data->identity.model_id)));
+ break;
+ case CELLULAR_MODEM_INFO_SIM_ICCID:
+ safe_strncpy(info, (const char *)data->identity.iccid,
+ MIN(size, sizeof(data->identity.iccid)));
+ break;
+ default:
+ ret = -ENOTSUP;
+ break;
+ }
+ k_mutex_unlock(&data->api_lock);
+ return ret;
+}
+
+int hl78xx_api_func_set_apn(const struct device *dev, const char *apn)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+ /**
+ * Validate APN
+ * APN can be empty string to clear it
+ * to request it from network
+ * If the value is null or omitted, then the subscription
+ * value will be requested
+ */
+ if (apn == NULL) {
+ return -EINVAL;
+ }
+ if (strlen(apn) >= MDM_APN_MAX_LENGTH) {
+ return -EINVAL;
+ }
+ /* Update in-memory APN under api lock */
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ safe_strncpy(data->identity.apn, apn, sizeof(data->identity.apn));
+ data->status.apn.state = APN_STATE_REFRESH_REQUESTED;
+ k_mutex_unlock(&data->api_lock);
+ 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 functionality with/without restart */
+ snprintf(cmd_string, sizeof(cmd_string), "AT+CFUN=%d,%d", functionality, reset);
+ return hl78xx_send_cmd(data, cmd_string, NULL, hl78xx_get_ok_match(), 1);
+}
+
+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;
+ /* get modem phone functionality */
+ return hl78xx_send_cmd(data, cmd_string, NULL, hl78xx_get_ok_match(), 1);
+}
+
+int hl78xx_api_func_modem_dynamic_cmd_send(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;
+
+ if (cmd == NULL) {
+ return -EINVAL;
+ }
+ /* respect provided matches_size and serialize modem access */
+ return modem_dynamic_cmd_send(data, NULL, cmd, cmd_size, response_matches, matches_size,
+ true);
+}
diff --git a/drivers/modem/hl78xx/hl78xx_cfg.c b/drivers/modem/hl78xx/hl78xx_cfg.c
new file mode 100644
index 0000000..461d066
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_cfg.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * hl78xx_cfg.c
+ *
+ * Extracted helper implementations for RAT, band and APN configuration to
+ * keep the main state-machine TU small and maintainable.
+ */
+#include "hl78xx.h"
+#include "hl78xx_cfg.h"
+#include "hl78xx_chat.h"
+#include <zephyr/logging/log.h>
+
+LOG_MODULE_DECLARE(hl78xx_dev);
+
+#define ICCID_PREFIX_LEN 7
+#define IMSI_PREFIX_LEN 6
+#define MAX_BANDS 32
+#define MDM_APN_FULL_STRING_MAX_LEN 256
+
+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;
+
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_kselq, strlen(cmd_kselq),
+ hl78xx_get_ok_match(), 1, false);
+ if (ret < 0) {
+ goto error;
+ } else {
+ *modem_require_restart = true;
+ }
+ }
+
+ *rat_request = HL78XX_RAT_MODE_AUTO;
+#else
+ char const *cmd_ksrat_query = (const char *)KSRAT_QUERY;
+ char const *cmd_kselq_disable = (const char *)DISABLE_RAT_AUTO;
+ const char *cmd_set_rat = NULL;
+ /* Check if auto rat are disabled */
+ if (data->kselacq_data.rat1 != 0 && data->kselacq_data.rat2 != 0 &&
+ data->kselacq_data.rat3 != 0) {
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_kselq_disable,
+ strlen(cmd_kselq_disable), hl78xx_get_ok_match(), 1,
+ false);
+ if (ret < 0) {
+ goto error;
+ }
+ }
+ /* Query current rat */
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_ksrat_query, strlen(cmd_ksrat_query),
+ hl78xx_get_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
+
+ 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_HL78XX_12
+ 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_HL78XX_12_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
+#endif
+
+ 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_dynamic_cmd_send(data, NULL, cmd_set_rat, strlen(cmd_set_rat),
+ hl78xx_get_ok_match(), 1, false);
+ if (ret < 0) {
+ goto error;
+ } else {
+ *modem_require_restart = true;
+ }
+ }
+#endif
+
+error:
+ return ret;
+}
+
+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};
+ const char *modem_trimmed;
+ const char *expected_trimmed;
+
+ if (rat_config_request == HL78XX_RAT_MODE_NONE) {
+ return -EINVAL;
+ }
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT
+ for (int rat = HL78XX_RAT_CAT_M1; rat <= HL78XX_RAT_NB1; rat++) {
+#else
+ 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;
+ }
+ modem_trimmed = hl78xx_trim_leading_zeros(data->status.kbndcfg[rat].bnd_bitmap);
+ 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);
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_bnd, strlen(cmd_bnd),
+ hl78xx_get_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;
+}
+
+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) +
+ MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN + 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;
+ }
+
+ k_mutex_lock(&data->api_lock, K_FOREVER);
+ if (strncmp(data->identity.apn, apn, apn_size) != 0) {
+ safe_strncpy(data->identity.apn, apn, sizeof(data->identity.apn));
+ }
+ k_mutex_unlock(&data->api_lock);
+
+ snprintk(cmd_string, cmd_max_len, "AT+CGDCONT=1,\"%s\",\"%s\"", MODEM_HL78XX_ADDRESS_FAMILY,
+ apn);
+
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_string, strlen(cmd_string),
+ hl78xx_get_ok_match(), 1, false);
+ if (ret < 0) {
+ goto error;
+ }
+ snprintk(cmd_string, cmd_max_len,
+ "AT+KCNXCFG=1,\"GPRS\",\"%s\",,,\"" MODEM_HL78XX_ADDRESS_FAMILY "\"", apn);
+ ret = modem_dynamic_cmd_send(data, NULL, cmd_string, strlen(cmd_string),
+ hl78xx_get_ok_match(), 1, false);
+ if (ret < 0) {
+ goto error;
+ }
+ data->status.apn.state = APN_STATE_CONFIGURED;
+ return 0;
+error:
+ LOG_ERR("Set APN to %s, result: %d", apn, ret);
+ return ret;
+}
+
+#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)
+{
+ uint16_t bit_pos;
+ uint16_t byte_index;
+ uint8_t bit_index;
+
+ if (band_num < 1 || band_num > 256) {
+ return; /* Out of range */
+ }
+ /* Calculate byte and bit positions */
+ bit_pos = band_num - 1;
+ byte_index = bit_pos / 8;
+ bit_index = bit_pos % 8;
+ /* Big-endian format: band 1 in byte 31, band 256 in byte 0 */
+ bitmap[byte_index] |= (1 << bit_index);
+}
+
+#ifdef CONFIG_MODEM_HL78XX_CONFIGURE_BANDS
+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;
+}
+#endif /* CONFIG_MODEM_HL78XX_CONFIGURE_BANDS */
+
+#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)
+{
+ char buf[128] = {0};
+ char *token;
+ char *rest;
+ int count = 0;
+ int band = 0;
+
+ if (!band_str || !bands || max_bands == 0) {
+ return -EINVAL;
+ }
+ strncpy(buf, band_str, sizeof(buf) - 1);
+ buf[sizeof(buf) - 1] = '\0';
+ rest = buf;
+ while ((token = strtok_r(rest, ",", &rest))) {
+ band = ATOI(token, -1, "band");
+ 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;
+ }
+#else
+ /* Else: use standalone config */
+ return hl78xx_generate_band_bitmap(bitmap_out);
+#endif /* CONFIG_MODEM_HL78XX_AUTORAT */
+ return -EINVAL;
+}
+
+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[MDM_APN_FULL_STRING_MAX_LEN] = {0};
+ size_t len;
+ const char *mnc_ptr;
+
+ if (full_apn == NULL || essential_apn == NULL || max_len == 0) {
+ return;
+ }
+ 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);
+ mnc_ptr = strstr(apn_buf, ".mnc");
+ 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';
+ }
+}
diff --git a/drivers/modem/hl78xx/hl78xx_cfg.h b/drivers/modem/hl78xx/hl78xx_cfg.h
new file mode 100644
index 0000000..5d8be2a
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_cfg.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * hl78xx_cfg.h
+ *
+ * Helper APIs for RAT, band and APN configuration extracted from hl78xx.c
+ * to keep the state machine file smaller and easier to read.
+ */
+#ifndef ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_
+#define ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_
+
+#include <zephyr/types.h>
+#include <stdbool.h>
+#include "hl78xx.h"
+
+int hl78xx_rat_cfg(struct hl78xx_data *data, bool *modem_require_restart,
+ enum hl78xx_cell_rat_mode *rat_request);
+
+int hl78xx_band_cfg(struct hl78xx_data *data, bool *modem_require_restart,
+ enum hl78xx_cell_rat_mode rat_config_request);
+
+int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size);
+
+/**
+ * @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 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 hl78xx_extract_essential_part_apn - Extract the essential part of the APN.
+ * @param full_apn Full APN string.
+ * @param essential_apn Buffer to store the essential part of the APN.
+ * @param max_len Maximum length of the essential APN buffer.
+ */
+void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len);
+
+#endif /* ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_ */
diff --git a/drivers/modem/hl78xx/hl78xx_chat.c b/drivers/modem/hl78xx/hl78xx_chat.c
new file mode 100644
index 0000000..5b94791
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_chat.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ *****************************************************************************
+ * hl78xx_chat.c
+ *
+ * Centralized translation unit for MODEM_CHAT_* macro-generated objects and
+ * chat scripts for the HL78xx driver. This file contains the MODEM_CHAT
+ * matches and script definitions and exposes runtime wrapper functions
+ * declared in hl78xx_chat.h.
+ *
+ * Contract:
+ * - Other translation units MUST NOT take addresses of the MODEM_CHAT_*
+ * symbols or use ARRAY_SIZE() on them at file scope. Use the getters
+ * (hl78xx_get_*) and runners (hl78xx_ run_*_script[_async]) instead.
+ *****************************************************************************
+ */
+
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+#include <zephyr/modem/chat.h>
+#include <zephyr/logging/log.h>
+
+LOG_MODULE_DECLARE(hl78xx_dev);
+
+/* Forward declarations of handlers implemented in hl78xx.c (extern linkage) */
+void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+/* +CGCONTRDP handler implemented in hl78xx_sockets.c - declared here so the
+ * chat match may reference it. This handler parses PDP context response and
+ * updates DNS / interface state for the driver instance.
+ */
+void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+#if defined(CONFIG_MODEM_HL78XX_12)
+void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+#endif
+void hl78xx_on_socknotifydata(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+/* Handler implemented to assign modem-provided udp socket ids */
+void hl78xx_on_kudpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+ void *user_data);
+void hl78xx_on_ktcpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+ void *user_data);
+/* Handler implemented to assign modem-provided tcp socket ids */
+void hl78xx_on_ktcpind(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+/*
+ * Chat script and URC match definitions - extracted from hl78xx.c
+ */
+void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+
+MODEM_CHAT_MATCH_DEFINE(hl78xx_ok_match, "OK", "", NULL);
+MODEM_CHAT_MATCHES_DEFINE(hl78xx_allow_match, MODEM_CHAT_MATCH("OK", "", NULL),
+ MODEM_CHAT_MATCH(CME_ERROR_STRING, "", NULL));
+
+MODEM_CHAT_MATCHES_DEFINE(hl78xx_unsol_matches, MODEM_CHAT_MATCH("+CREG: ", ",", hl78xx_on_cxreg),
+ MODEM_CHAT_MATCH("+CEREG: ", ",", hl78xx_on_cxreg),
+#if defined(CONFIG_MODEM_HL78XX_12)
+ MODEM_CHAT_MATCH("+KSTATEV: ", ",", hl78xx_on_kstatev),
+#endif
+ MODEM_CHAT_MATCH("+KUDP_DATA: ", ",", hl78xx_on_socknotifydata),
+ MODEM_CHAT_MATCH("+KTCP_DATA: ", ",", hl78xx_on_socknotifydata),
+ MODEM_CHAT_MATCH("+KTCP_NOTIF: ", ",", hl78xx_on_ktcpnotif),
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+ MODEM_CHAT_MATCH("+KUDP_RCV: ", ",", hl78xx_on_udprcv),
+#endif
+ MODEM_CHAT_MATCH("+KBNDCFG: ", ",", hl78xx_on_kbndcfg),
+ MODEM_CHAT_MATCH("+CSQ: ", ",", hl78xx_on_csq),
+ MODEM_CHAT_MATCH("+CESQ: ", ",", hl78xx_on_cesq),
+ MODEM_CHAT_MATCH("+CFUN: ", "", hl78xx_on_cfun),
+ MODEM_CHAT_MATCH("+COPS: ", ",", hl78xx_on_cops));
+
+MODEM_CHAT_MATCHES_DEFINE(hl78xx_abort_matches, MODEM_CHAT_MATCH("+CME ERROR: ", "", NULL));
+MODEM_CHAT_MATCH_DEFINE(hl78xx_at_ready_match, "+KSUP: ", "", hl78xx_on_ksup);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_imei_match, "", "", hl78xx_on_imei);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmm_match, "", "", hl78xx_on_cgmm);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cimi_match, "", "", hl78xx_on_imsi);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmi_match, "", "", hl78xx_on_cgmi);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmr_match, "", "", hl78xx_on_cgmr);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_iccid_match, "+CCID: ", "", hl78xx_on_iccid);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_ksrep_match, "+KSREP: ", ",", hl78xx_on_ksrep);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_ksrat_match, "+KSRAT: ", "", hl78xx_on_ksrat);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_kselacq_match, "+KSELACQ: ", ",", hl78xx_on_kselacq);
+
+/* Chat script matches / definitions */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_periodic_chat_script_cmds,
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG?", hl78xx_ok_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_periodic_chat_script, hl78xx_periodic_chat_script_cmds,
+ hl78xx_abort_matches, hl78xx_chat_callback_handler, 4);
+
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_init_chat_script_cmds,
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_at_ready_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KHWIOCFG=3,1,6", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=4,0", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSLEEP=2", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPSMS=0", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEDRXS=0", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KPATTERN=\"--EOF--Pattern--\"",
+ hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CCID", hl78xx_iccid_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CMEE=1", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGSN", hl78xx_imei_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMM", hl78xx_cgmm_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMI", hl78xx_cgmi_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMR", hl78xx_cgmr_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CIMI", hl78xx_cimi_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+#if defined(CONFIG_MODEM_HL78XX_12)
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", hl78xx_ok_match),
+#endif
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGEREP=2", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSELACQ?", hl78xx_kselacq_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", hl78xx_ksrat_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KBNDCFG?", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGACT?", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CREG=0", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG=5", hl78xx_ok_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_init_chat_script, hl78xx_init_chat_script_cmds,
+ hl78xx_abort_matches, hl78xx_chat_callback_handler, 10);
+
+/* Post-restart script (moved from hl78xx.c) */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_post_restart_chat_script_cmds,
+ MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_at_ready_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", hl78xx_ksrat_match),
+#if defined(CONFIG_MODEM_HL78XX_12)
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", hl78xx_ok_match)
+#endif
+);
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_post_restart_chat_script, hl78xx_post_restart_chat_script_cmds,
+ hl78xx_abort_matches, hl78xx_chat_callback_handler, 1000);
+
+/* init_fail_script moved from hl78xx.c */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(init_fail_script_cmds,
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", hl78xx_ksrep_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(init_fail_script, init_fail_script_cmds, hl78xx_abort_matches,
+ hl78xx_chat_callback_handler, 10);
+
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_enable_ksup_urc_cmds,
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP=1", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", hl78xx_ksrep_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_enable_ksup_urc_script, hl78xx_enable_ksup_urc_cmds,
+ hl78xx_abort_matches, hl78xx_chat_callback_handler, 4);
+
+/* power-off script moved from hl78xx.c */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_pwroff_cmds,
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=0", hl78xx_ok_match),
+ MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPWROFF", hl78xx_ok_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_pwroff_script, hl78xx_pwroff_cmds, hl78xx_abort_matches,
+ hl78xx_chat_callback_handler, 4);
+
+/* Socket-specific matches and wrappers exposed for the sockets translation
+ * unit. These were extracted from hl78xx_sockets.c to centralize chat
+ * definitions.
+ */
+MODEM_CHAT_MATCHES_DEFINE(connect_matches, MODEM_CHAT_MATCH(CONNECT_STRING, "", NULL),
+ MODEM_CHAT_MATCH(CME_ERROR_STRING, "", NULL));
+MODEM_CHAT_MATCH_DEFINE(kudpind_match, "+KUDP_IND: ", ",", hl78xx_on_kudpsocket_create);
+MODEM_CHAT_MATCH_DEFINE(ktcpind_match, "+KTCP_IND: ", ",", hl78xx_on_ktcpind);
+MODEM_CHAT_MATCH_DEFINE(ktcpcfg_match, "+KTCPCFG: ", "", hl78xx_on_ktcpsocket_create);
+MODEM_CHAT_MATCH_DEFINE(cgdcontrdp_match, "+CGCONTRDP: ", ",", hl78xx_on_cgdcontrdp);
+MODEM_CHAT_MATCH_DEFINE(ktcp_state_match, "+KTCPSTAT: ", ",", NULL);
+
+const struct modem_chat_match *hl78xx_get_sockets_ok_match(void)
+{
+ return &hl78xx_ok_match;
+}
+
+const struct modem_chat_match *hl78xx_get_connect_matches(void)
+{
+ return connect_matches;
+}
+
+size_t hl78xx_get_connect_matches_size(void)
+{
+ return (size_t)ARRAY_SIZE(connect_matches);
+}
+
+const struct modem_chat_match *hl78xx_get_sockets_allow_matches(void)
+{
+ return hl78xx_allow_match;
+}
+
+size_t hl78xx_get_sockets_allow_matches_size(void)
+{
+ return (size_t)ARRAY_SIZE(hl78xx_allow_match);
+}
+
+const struct modem_chat_match *hl78xx_get_kudpind_match(void)
+{
+ return &kudpind_match;
+}
+
+const struct modem_chat_match *hl78xx_get_ktcpind_match(void)
+{
+ return &ktcpind_match;
+}
+
+const struct modem_chat_match *hl78xx_get_ktcpcfg_match(void)
+{
+ return &ktcpcfg_match;
+}
+
+const struct modem_chat_match *hl78xx_get_cgdcontrdp_match(void)
+{
+ return &cgdcontrdp_match;
+}
+
+const struct modem_chat_match *hl78xx_get_ktcp_state_match(void)
+{
+ return &ktcp_state_match;
+}
+
+/* modem_init_chat is implemented in hl78xx.c so it can construct the
+ * modem_chat_config with device-local buffer sizes (argv_size) without
+ * relying on ARRAY_SIZE at file scope inside this translation unit.
+ */
+
+/* Bridge function - modem_chat callback */
+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);
+ }
+}
+
+/* --- Wrapper helpers -------------------------------------------------- */
+const struct modem_chat_match *hl78xx_get_ok_match(void)
+{
+ return &hl78xx_ok_match;
+}
+
+const struct modem_chat_match *hl78xx_get_abort_matches(void)
+{
+ return hl78xx_abort_matches;
+}
+
+const struct modem_chat_match *hl78xx_get_unsol_matches(void)
+{
+ return hl78xx_unsol_matches;
+}
+
+size_t hl78xx_get_unsol_matches_size(void)
+{
+ /* Return size as a runtime value to avoid constant-expression errors
+ * in translation units that include this header.
+ */
+ return (size_t)(ARRAY_SIZE(hl78xx_unsol_matches));
+}
+
+size_t hl78xx_get_abort_matches_size(void)
+{
+ return (size_t)(ARRAY_SIZE(hl78xx_abort_matches));
+}
+
+const struct modem_chat_match *hl78xx_get_allow_match(void)
+{
+ return hl78xx_allow_match;
+}
+
+size_t hl78xx_get_allow_match_size(void)
+{
+ return (size_t)(ARRAY_SIZE(hl78xx_allow_match));
+}
+
+/* Run the predefined init script for the given device */
+int hl78xx_run_init_script(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script(&data->chat, &hl78xx_init_chat_script);
+}
+
+/* Run the periodic script */
+int hl78xx_run_periodic_script(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script(&data->chat, &hl78xx_periodic_chat_script);
+}
+
+int hl78xx_run_init_script_async(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script_async(&data->chat, &hl78xx_init_chat_script);
+}
+
+int hl78xx_run_periodic_script_async(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script_async(&data->chat, &hl78xx_periodic_chat_script);
+}
+
+const struct modem_chat_match *hl78xx_get_ksrat_match(void)
+{
+ return &hl78xx_ksrat_match;
+}
+
+int hl78xx_run_post_restart_script(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script(&data->chat, &hl78xx_post_restart_chat_script);
+}
+
+int hl78xx_run_post_restart_script_async(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script_async(&data->chat, &hl78xx_post_restart_chat_script);
+}
+
+int hl78xx_run_init_fail_script_async(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script_async(&data->chat, &init_fail_script);
+}
+
+int hl78xx_run_enable_ksup_urc_script_async(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script_async(&data->chat, &hl78xx_enable_ksup_urc_script);
+}
+
+int hl78xx_run_pwroff_script_async(struct hl78xx_data *data)
+{
+ if (!data) {
+ return -EINVAL;
+ }
+ return modem_chat_run_script_async(&data->chat, &hl78xx_pwroff_script);
+}
diff --git a/drivers/modem/hl78xx/hl78xx_chat.h b/drivers/modem/hl78xx/hl78xx_chat.h
new file mode 100644
index 0000000..eb3a1de
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_chat.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * hl78xx_chat.h
+ *
+ * Wrapper accessors for MODEM_CHAT_* objects that live in a dedicated
+ * translation unit (hl78xx_chat.c). Other driver TUs should only call
+ * these functions instead of taking addresses or using sizeof/ARRAY_SIZE
+ * on the macro-generated objects.
+ */
+#ifndef ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_
+#define ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_
+
+#include <stddef.h>
+#include <zephyr/modem/chat.h>
+
+/* Forward declare driver data type to keep this header lightweight and avoid
+ * circular includes. The implementation file (hl78xx_chat.c) includes
+ * hl78xx.h for full driver visibility.
+ */
+struct hl78xx_data;
+
+/* Chat callback bridge used by driver TUs to receive script results. */
+void hl78xx_chat_callback_handler(struct modem_chat *chat, enum modem_chat_script_result result,
+ void *user_data);
+
+/* Wrapper helpers so other translation units don't need compile-time
+ * visibility of the MODEM_CHAT_* macro-generated symbols.
+ */
+const struct modem_chat_match *hl78xx_get_ok_match(void);
+const struct modem_chat_match *hl78xx_get_abort_matches(void);
+const struct modem_chat_match *hl78xx_get_unsol_matches(void);
+size_t hl78xx_get_unsol_matches_size(void);
+size_t hl78xx_get_abort_matches_size(void);
+const struct modem_chat_match *hl78xx_get_allow_match(void);
+size_t hl78xx_get_allow_match_size(void);
+
+/* Run predefined scripts from other units */
+int hl78xx_run_init_script(struct hl78xx_data *data);
+int hl78xx_run_periodic_script(struct hl78xx_data *data);
+int hl78xx_run_post_restart_script(struct hl78xx_data *data);
+int hl78xx_run_init_fail_script_async(struct hl78xx_data *data);
+int hl78xx_run_enable_ksup_urc_script_async(struct hl78xx_data *data);
+int hl78xx_run_pwroff_script_async(struct hl78xx_data *data);
+int hl78xx_run_post_restart_script_async(struct hl78xx_data *data);
+/* Async runners for init/periodic scripts */
+int hl78xx_run_init_script_async(struct hl78xx_data *data);
+int hl78xx_run_periodic_script_async(struct hl78xx_data *data);
+
+/* Getter for ksrat match (moved into chat TU) */
+const struct modem_chat_match *hl78xx_get_ksrat_match(void);
+
+/* Socket-related chat matches used by the sockets TU */
+const struct modem_chat_match *hl78xx_get_sockets_ok_match(void);
+const struct modem_chat_match *hl78xx_get_connect_matches(void);
+size_t hl78xx_get_connect_matches_size(void);
+const struct modem_chat_match *hl78xx_get_sockets_allow_matches(void);
+size_t hl78xx_get_sockets_allow_matches_size(void);
+const struct modem_chat_match *hl78xx_get_kudpind_match(void);
+const struct modem_chat_match *hl78xx_get_ktcpind_match(void);
+const struct modem_chat_match *hl78xx_get_ktcpcfg_match(void);
+const struct modem_chat_match *hl78xx_get_cgdcontrdp_match(void);
+const struct modem_chat_match *hl78xx_get_ktcp_state_match(void);
+
+#endif /* ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_ */
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 0000000..ea00a6b
--- /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 0000000..e002f24
--- /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 AT 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 0000000..7bb2e68
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/kernel.h>
+#include <zephyr/init.h>
+#include <zephyr/device.h>
+#include <zephyr/toolchain.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/spinlock.h>
+#include <zephyr/drivers/modem/hl78xx_apis.h>
+
+LOG_MODULE_REGISTER(hl78xx_evt_monitor, CONFIG_HL78XX_EVT_MONITOR_LOG_LEVEL);
+
+struct evt_notif_fifo {
+ void *fifo_reserved;
+ struct hl78xx_evt data;
+};
+
+static struct hl78xx_evt_monitor_entry *monitor_list_head;
+static struct k_spinlock monitor_list_lock;
+
+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;
+}
+
+/* Register an event monitor */
+int hl78xx_evt_monitor_register(struct hl78xx_evt_monitor_entry *mon)
+{
+ k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+
+ mon->next = monitor_list_head;
+ monitor_list_head = mon;
+ k_spin_unlock(&monitor_list_lock, key);
+ return 0;
+}
+
+/* Unregister an event monitor */
+int hl78xx_evt_monitor_unregister(struct hl78xx_evt_monitor_entry *mon)
+{
+ k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+ struct hl78xx_evt_monitor_entry **pp = &monitor_list_head;
+
+ while (*pp) {
+ if (*pp == mon) {
+ *pp = mon->next;
+ mon->next = NULL;
+ k_spin_unlock(&monitor_list_lock, key);
+ return 0;
+ }
+ pp = &(*pp)->next;
+ }
+
+ k_spin_unlock(&monitor_list_lock, key);
+ return -ENOENT;
+}
+/* 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;
+ /* Global monitors: SECTION_ITERABLE */
+ STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) {
+ if (!is_paused(e)) {
+ if (is_direct(e)) {
+ LOG_DBG("calling direct global handler %p",
+ e->handler);
+ e->handler(notif, NULL); /* NULL context for global listeners */
+ } else {
+ monitored = true;
+ }
+ }
+ }
+
+ k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+
+ for (struct hl78xx_evt_monitor_entry *e = monitor_list_head; e; e = e->next) {
+ if (!is_paused(e)) {
+ if (is_direct(e)) {
+ LOG_DBG("calling direct instance handler %p "
+ "(ctx=%p)",
+ e->handler, e);
+ e->handler(notif, e);
+ } else {
+ monitored = true;
+ }
+ }
+ }
+ k_spin_unlock(&monitor_list_lock, key);
+
+ 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, e);
+ }
+ }
+ /* Instance/context monitors */
+ k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+
+ for (struct hl78xx_evt_monitor_entry *e = monitor_list_head; e; e = e->next) {
+ if (!is_paused(e) && !is_direct(e)) {
+ e->handler(&evt_notif->data, e);
+ }
+ }
+ k_spin_unlock(&monitor_list_lock, key);
+
+ 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 0000000..c6c3294
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*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 0000000..9840ea9
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_sockets.c
@@ -0,0 +1,2608 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/modem/chat.h>
+#include <zephyr/modem/backend/uart.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/net/net_if.h>
+#include <zephyr/net/net_offload.h>
+#include <zephyr/net/offloaded_netdev.h>
+#include <zephyr/net/socket_offload.h>
+#include <zephyr/posix/fcntl.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/pm/device_runtime.h>
+
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+#include "tls_internal.h"
+#include <zephyr/net/tls_credentials.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+#include "hl78xx_cfg.h"
+
+LOG_MODULE_REGISTER(hl78xx_socket, CONFIG_MODEM_LOG_LEVEL);
+
+/*
+ * hl78xx_sockets.c
+ *
+ * Responsibilities:
+ * - Provide the socket offload integration for the HL78xx modem.
+ * - Parse modem URC/chat replies used to transfer payloads over the UART pipe.
+ * - Format and send AT commands for socket lifecycle (create, connect, send, recv,
+ * close, delete) and handle their confirmation/URC callbacks.
+ * - Provide TLS credential handling when enabled.
+ */
+
+/* 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 HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE 32
+/* modem socket id is 1-based */
+#define HL78XX_TCP_STATUS_ID(x) ((x > 1 ? (x) - 1 : 0))
+/* modem socket id is 1-based */
+#define HL78XX_UDP_STATUS_ID(x) ((x > 1 ? (x) - 1 : 0))
+
+#define DNS_SERVERS_COUNT \
+ (0 + (IS_ENABLED(CONFIG_NET_IPV6) ? 1 : 0) + (IS_ENABLED(CONFIG_NET_IPV4) ? 1 : 0) + \
+ 1 /* for NULL terminator */ \
+ )
+RING_BUF_DECLARE(mdm_recv_pool, CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES);
+
+struct hl78xx_dns_info {
+#ifdef CONFIG_NET_IPV4
+ char v4_string[NET_IPV4_ADDR_LEN];
+ struct in_addr v4;
+#endif
+#ifdef CONFIG_NET_IPV6
+ char v6_string[NET_IPV6_ADDR_LEN];
+ struct in6_addr v6;
+#endif
+ bool ready;
+};
+
+/* IPv4 information is optional and only present when IPv4 is enabled */
+#ifdef CONFIG_NET_IPV4
+struct hl78xx_ipv4_info {
+ struct in_addr addr;
+ struct in_addr subnet;
+ struct in_addr gateway;
+ struct in_addr new_addr;
+};
+#endif
+/* IPv6 information is optional and only present when IPv6 is enabled */
+#ifdef CONFIG_NET_IPV6
+struct hl78xx_ipv6_info {
+ struct in6_addr addr;
+ struct in6_addr subnet;
+ struct in6_addr gateway;
+ struct in6_addr new_addr;
+};
+#endif
+/* TLS information is optional and only present when TLS is enabled */
+struct hl78xx_tls_info {
+ char hostname[MDM_MAX_HOSTNAME_LEN];
+ bool hostname_set;
+};
+
+enum hl78xx_tcp_socket_status_code {
+ /** Error occurred, socket is not usable */
+ TCP_SOCKET_ERROR = 0,
+ /** Connection is up, socket can be used to send/receive data */
+ TCP_SOCKET_CONNECTED,
+};
+
+enum hl78xx_udp_socket_status_code {
+ UDP_SOCKET_ERROR = 0, /* Error occurred, socket is not usable */
+ /** Connection is up, socket can be used to send/receive data */
+ UDP_SOCKET_CREATED,
+};
+struct hl78xx_tcp_status {
+ enum hl78xx_tcp_socket_status_code err_code;
+ bool is_connected;
+ bool is_created;
+};
+struct hl78xx_udp_status {
+ enum hl78xx_udp_socket_status_code err_code;
+ bool is_created;
+};
+
+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 hl78xx_socket_data {
+ struct net_if *net_iface;
+ uint8_t mac_addr[6];
+ /* 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;
+ bool socket_data_error;
+#if defined(CONFIG_NET_IPV4) || defined(CONFIG_NET_IPV6)
+ struct hl78xx_dns_info dns;
+#endif
+#ifdef CONFIG_NET_IPV4
+ struct hl78xx_ipv4_info ipv4;
+#endif
+#ifdef CONFIG_NET_IPV6
+ struct hl78xx_ipv6_info ipv6;
+#endif
+ /* rx net buffer */
+ struct ring_buf *buf_pool;
+ uint32_t expected_buf_len;
+ uint32_t collected_buf_len;
+ struct receive_socket_data receive_buf;
+ /* device information */
+ const struct device *modem_dev;
+ const struct device *offload_dev;
+ struct hl78xx_data *mdata_global;
+ /* socket state */
+ struct hl78xx_tls_info tls;
+ struct hl78xx_tcp_status tcp_conn_status[MDM_MAX_SOCKETS];
+ struct hl78xx_udp_status udp_conn_status[MDM_MAX_SOCKETS];
+ /* per-socket parser state (migrated from globals) - use a small enum to
+ * make the parser's intent explicit and easier to read.
+ */
+ enum {
+ HL78XX_PARSER_IDLE = 0,
+ HL78XX_PARSER_CONNECT_MATCHED,
+ HL78XX_PARSER_EOF_OK_MATCHED,
+ HL78XX_PARSER_ERROR_MATCHED,
+ } parser_state;
+ /* transient: prevents further parsing until parser_reset clears it */
+ bool parser_match_found;
+ uint16_t parser_start_index_eof;
+ uint16_t parser_size_of_socketdata;
+ /* true once payload has been pushed into ring_buf */
+ bool parser_socket_data_received;
+ /* set when EOF pattern was found and payload pushed */
+ bool parser_eof_detected;
+ /* set when OK token was matched after payload */
+ bool parser_ok_detected;
+};
+
+static struct hl78xx_socket_data *socket_data_global;
+
+/* ===== Utils ==========================================================
+ * Small, stateless utility helpers used across this file.
+ * Grouping here reduces cognitive load when navigating the file.
+ */
+static inline void hl78xx_set_socket_global(struct hl78xx_socket_data *d)
+{
+ socket_data_global = d;
+}
+
+static inline struct hl78xx_socket_data *hl78xx_get_socket_global(void)
+{
+ return socket_data_global;
+}
+
+/* Helper: map an internal return code into POSIX errno and set errno.
+ * - negative values are assumed to be negative errno semantics -> map to positive
+ * - positive values are assumed already POSIX errno -> pass through
+ * - zero or unknown -> fallback to EIO
+ */
+static inline void hl78xx_set_errno_from_code(int code)
+{
+ if (code < 0) {
+ errno = -code;
+ } else if (code > 0) {
+ errno = code;
+ } else {
+ errno = EIO;
+ }
+}
+/* ===== Forward declarations ==========================================
+ * Group commonly used static helper prototypes here so callers can be
+ * reordered without implicit-declaration warnings. Keep this section
+ * compact. When moving functions into groups, add any new prototypes
+ * here first.
+ */
+static void check_tcp_state_if_needed(struct hl78xx_socket_data *socket_data,
+ struct modem_socket *sock);
+/* Parser helpers */
+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);
+static bool parse_ip(bool is_ipv4, const char *ip_str, void *out_addr);
+static bool update_dns(struct hl78xx_socket_data *socket_data, bool is_ipv4, const char *dns_str);
+static void set_iface(struct hl78xx_socket_data *socket_data, bool is_ipv4);
+static void parser_reset(struct hl78xx_socket_data *socket_data);
+static void found_reset(struct hl78xx_socket_data *socket_data);
+static bool modem_chat_parse_end_del_start(struct hl78xx_socket_data *socket_data,
+ struct modem_chat *chat);
+static bool modem_chat_parse_end_del_complete(struct hl78xx_socket_data *socket_data,
+ struct modem_chat *chat);
+static bool modem_chat_match_matches_received(struct hl78xx_socket_data *socket_data,
+ const char *match, uint16_t match_size);
+
+/* Receive / parser entrypoints */
+static void socket_process_bytes(struct hl78xx_socket_data *socket_data, char byte);
+static int modem_process_handler(struct hl78xx_data *data);
+static void modem_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
+ void *user_data);
+
+/* Socket I/O helpers */
+static int on_cmd_sockread_common(int socket_id, uint16_t socket_data_length, uint16_t len,
+ void *user_data);
+static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from,
+ socklen_t *fromlen);
+static int prepare_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr,
+ size_t buf_len, char *cmd_buf, size_t cmd_buf_size);
+static int send_data_buffer(struct hl78xx_socket_data *socket_data, const char *buf,
+ const size_t buf_len, int *sock_written);
+
+/* Socket lifecycle */
+static int create_socket(struct modem_socket *sock, const struct sockaddr *addr,
+ struct hl78xx_socket_data *data);
+static int socket_close(struct hl78xx_socket_data *socket_data, struct modem_socket *sock);
+static int socket_delete(struct hl78xx_socket_data *socket_data, struct modem_socket *sock);
+static void socket_notify_data(int socket_id, int new_total, void *user_data);
+/* ===== TLS prototypes (conditional) ==================================
+ * Forward declarations for TLS-related helpers. Grouped separately so
+ * TLS-specific code paths are easy to find.
+ */
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+static int map_credentials(struct hl78xx_socket_data *socket_data, const void *optval,
+ socklen_t optlen);
+static int hl78xx_configure_chipper_suit(struct hl78xx_socket_data *socket_data);
+#endif /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */
+
+/* ===== Container helpers =============================================
+ * Small helpers used to map between container structures and their
+ * member pointers (eg. `modem_socket` -> `hl78xx_socket_data`).
+ */
+static inline struct hl78xx_socket_data *hl78xx_socket_data_from_sock(struct modem_socket *sock)
+{
+ /* Robustly recover the parent `hl78xx_socket_data` for any element
+ * address within the `sockets[]` array. Using CONTAINER_OF with
+ * `sockets[0]` is not safe when `sock` points to `sockets[i]` (i>0),
+ * because CONTAINER_OF assumes the pointer is to the member named
+ * in the macro (sockets[0]). That yields a pointer offset by
+ * i * sizeof(sockets[0]).
+ *
+ * Strategy: for each possible index i, compute the candidate parent
+ * base address so that &candidate->sockets[i] == sock. If the math
+ * yields a candidate that looks like a valid container, return it.
+ */
+ if (!sock) {
+ return NULL;
+ }
+
+ const size_t elem_size = sizeof(((struct hl78xx_socket_data *)0)->sockets[0]);
+ const size_t sockets_off = offsetof(struct hl78xx_socket_data, sockets);
+ struct hl78xx_socket_data *result = NULL;
+
+ for (int i = 0; i < MDM_MAX_SOCKETS; i++) {
+ struct hl78xx_socket_data *candidate =
+ (struct hl78xx_socket_data *)((char *)sock -
+ (ptrdiff_t)(sockets_off +
+ (size_t)i * elem_size));
+ /* Quick sanity: does candidate->sockets[i] point back to sock? */
+ if ((struct modem_socket *)&candidate->sockets[i] != sock) {
+ continue;
+ }
+ if (candidate->offload_dev && candidate->mdata_global) {
+ return candidate;
+ }
+
+ /* Remember the first match as a fallback */
+ if (!result) {
+ result = candidate;
+ }
+ }
+ return result;
+}
+
+/* ===== Chat callbacks (grouped) =====================================
+ * Group all chat/URC handlers together to make the socket TU easier to
+ * scan. These handlers are registered via hl78xx_chat getters in
+ * `hl78xx_chat.c` and forward URC context into the socket layer.
+ */
+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");
+ if (socket_id < 0 || new_total < 0) {
+ return;
+ }
+ HL78XX_LOG_DBG("%d %d %d", __LINE__, socket_id, new_total);
+ /* Notify the socket layer that data is available */
+ socket_notify_data(socket_id, new_total, user_data);
+}
+
+/** +KTCP_NOTIF: <session_id>, <tcp_notif> */
+void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ enum hl78xx_tcp_notif tcp_notif_received;
+ int socket_id = -1;
+ int tcp_notif = -1;
+
+ if (!data || !socket_data) {
+ LOG_ERR("%s: invalid user_data", __func__);
+ return;
+ }
+ if (argc < 2) {
+ return;
+ }
+ socket_id = ATOI(argv[1], -1, "socket_id");
+ tcp_notif = ATOI(argv[2], -1, "tcp_notif");
+ if (tcp_notif == -1) {
+ return;
+ }
+ tcp_notif_received = (enum hl78xx_tcp_notif)tcp_notif;
+ /* Store the socket id for the notification */
+ socket_data->requested_socket_id = socket_id;
+ switch (tcp_notif_received) {
+ case TCP_NOTIF_REMOTE_DISCONNECTION:
+ /**
+ * To Handle remote disconnection
+ * give a dummy packet size of 1
+ *
+ */
+ socket_notify_data(socket_id, 1, user_data);
+ break;
+ case TCP_NOTIF_NETWORK_ERROR:
+ /* Handle network error */
+ break;
+ default:
+ break;
+ }
+}
+
+void hl78xx_on_ktcpind(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ struct modem_socket *sock = NULL;
+ int socket_id = -1;
+ int tcp_conn_stat = -1;
+
+ if (!data || !socket_data) {
+ LOG_ERR("%s: invalid user_data", __func__);
+ return;
+ }
+ if (argc < 3 || !argv[1] || !argv[2]) {
+ LOG_ERR("TCP_IND: Incomplete response");
+ goto exit;
+ }
+ socket_id = ATOI(argv[1], -1, "socket_id");
+ if (socket_id == -1) {
+ goto exit;
+ }
+ sock = modem_socket_from_id(&socket_data->socket_config, socket_id);
+ tcp_conn_stat = ATOI(argv[2], -1, "tcp_status");
+ if (tcp_conn_stat == TCP_SOCKET_CONNECTED) {
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code =
+ tcp_conn_stat;
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_connected = true;
+ return;
+ }
+exit:
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = tcp_conn_stat;
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_connected = false;
+ if (socket_id != -1) {
+ modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+ }
+}
+
+/* Chat/URC handler for socket-create/indication responses
+ * Matches +KTCPCFG: <id>
+ */
+void hl78xx_on_ktcpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+ void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ struct modem_socket *sock = NULL;
+ int socket_id = -1;
+
+ if (!data || !socket_data) {
+ LOG_ERR("%s: invalid user_data", __func__);
+ return;
+ }
+ if (argc < 2 || !argv[1]) {
+ LOG_ERR("%s: Incomplete response", __func__);
+ goto exit;
+ }
+ /* argv[0] may contain extra CSV fields; parse leading integer */
+ socket_id = ATOI(argv[1], -1, "socket_id");
+ if (socket_id <= 0) {
+ LOG_DBG("unable to parse socket id from '%s'", argv[1]);
+ goto exit;
+ }
+ /* Try to find a reserved/new socket slot and assign the modem-provided id. */
+ sock = modem_socket_from_newid(&socket_data->socket_config);
+ if (!sock) {
+ goto exit;
+ }
+
+ if (modem_socket_id_assign(&socket_data->socket_config, sock, socket_id) < 0) {
+ LOG_ERR("Failed to assign modem socket id %d to fd %d", socket_id, sock->sock_fd);
+ goto exit;
+ } else {
+ LOG_DBG("Assigned modem socket id %d to fd %d", socket_id, sock->sock_fd);
+ }
+
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_created = true;
+ return;
+
+exit:
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = TCP_SOCKET_ERROR;
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_created = false;
+ if (socket_id != -1 && sock) {
+ modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+ }
+}
+/* Chat/URC handler for socket-create/indication responses
+ * Matches +KUDPCFG: <id>
+ * +KUDP_IND: <id>,... (or +KTCP_IND)
+ */
+void hl78xx_on_kudpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+ void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ struct modem_socket *sock = NULL;
+ int socket_id = -1;
+ int udp_create_stat = -1;
+
+ if (!data || !socket_data) {
+ LOG_ERR("%s: invalid user_data", __func__);
+ return;
+ }
+ if (argc < 2 || !argv[1]) {
+ LOG_ERR("%s: Incomplete response", __func__);
+ goto exit;
+ }
+ /* argv[0] may contain extra CSV fields; parse leading integer */
+ socket_id = ATOI(argv[1], -1, "socket_id");
+ if (socket_id <= 0) {
+ LOG_DBG("unable to parse socket id from '%s'", argv[1]);
+ goto exit;
+ }
+ /* Try to find a reserved/new socket slot and assign the modem-provided id. */
+ sock = modem_socket_from_newid(&socket_data->socket_config);
+ if (!sock) {
+ goto exit;
+ }
+
+ if (modem_socket_id_assign(&socket_data->socket_config, sock, socket_id) < 0) {
+ LOG_ERR("Failed to assign modem socket id %d to fd %d", socket_id, sock->sock_fd);
+ goto exit;
+ } else {
+ LOG_DBG("Assigned modem socket id %d to fd %d", socket_id, sock->sock_fd);
+ }
+ /* Parse connection status: 1=created, otherwise=error */
+ udp_create_stat = ATOI(argv[2], 0, "udp_status");
+ if (udp_create_stat == UDP_SOCKET_CREATED) {
+ socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].err_code =
+ udp_create_stat;
+ socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].is_created = true;
+ return;
+ }
+exit:
+ socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].err_code = UDP_SOCKET_ERROR;
+ socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].is_created = false;
+ if (socket_id != -1 && sock) {
+ modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+ }
+}
+
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+#ifdef CONFIG_MODEM_HL78XX_12
+/**
+ * @brief Handle modem state update from +KSTATE URC of RAT Scan Finish.
+ * This command is intended to report events for different important state transitions and system
+ * occurrences.
+ * Actually this eventc'state is really important functionality to understand networks
+ * searching phase of the modem.
+ * Verbose debug logging for KSTATEV events
+ */
+void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state, int rat_mode)
+{
+ switch (state) {
+ case EVENT_START_SCAN:
+ break;
+ case EVENT_FAIL_SCAN:
+ LOG_DBG("Modem failed to find a suitable network");
+ break;
+ case EVENT_ENTER_CAMPED:
+ LOG_DBG("Modem entered camped state on a suitable or acceptable cell");
+ break;
+ case EVENT_CONNECTION_ESTABLISHMENT:
+ LOG_DBG("Modem successfully established a connection to the network");
+ break;
+ case EVENT_START_RESCAN:
+ LOG_DBG("Modem is starting a rescan for available networks");
+ break;
+ case EVENT_RRC_CONNECTED:
+ LOG_DBG("Modem has established an RRC connection with the network");
+ break;
+ case EVENT_NO_SUITABLE_CELLS:
+ LOG_DBG("Modem did not find any suitable cells during the scan");
+ break;
+ case EVENT_ALL_REGISTRATION_FAILED:
+ LOG_DBG("Modem failed to register to any network");
+ break;
+ default:
+ LOG_DBG("Unhandled KSTATEV for state %d", state);
+ break;
+ }
+}
+#endif
+/**
+ * @brief This function doesn't handle incoming UDP data.
+ * It is just a placeholder for verbose debug logging of incoming UDP data.
+ * +KUDP_RCV: <remote_addr>,<remote_port>,
+ */
+void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ if (argc < 2) {
+ return;
+ }
+ HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]);
+}
+#endif
+/* Handler for +CGCONTRDP: <cid>,<bearer>,<apn>,<addr>,<dcomp>,<hcomp>,<dns1>[,<dns2>]
+ * This function is invoked by the chat layer when a CGCONTRDP URC is matched.
+ * It extracts the PDP context address, gateway and DNS servers and updates the
+ * per-instance socket_data DNS fields so dns_work_cb() can apply them.
+ */
+void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ const char *addr_field = NULL;
+ const char *gw_field = NULL;
+ const char *dns_field = NULL;
+ const char *apn_field = NULL;
+
+ /* Accept both comma-split argv[] or a single raw token that needs tokenizing */
+ if (argc >= 7) {
+ apn_field = argv[3];
+ addr_field = argv[4];
+ gw_field = argv[5];
+ dns_field = argv[6];
+ } else {
+ LOG_ERR("Incomplete CGCONTRDP response: argc=%d", argc);
+ return;
+ }
+
+ LOG_INF("Apn=%s", apn_field);
+ LOG_INF("Addr=%s", addr_field);
+ LOG_INF("Gw=%s", gw_field);
+ LOG_INF("DNS=%s", dns_field);
+#ifdef CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK
+ if (apn_field) {
+ hl78xx_extract_essential_part_apn(apn_field, data->identity.apn,
+ sizeof(data->identity.apn));
+ }
+#endif
+ /* Handle address parsing: IPv4 replies sometimes embed subnet as extra
+ * octets concatenated after the IP (e.g. "10.149.122.90.255.255.255.252").
+ * Split and parse into the instance IPv4 fields so the interface can be
+ * configured before the DNS resolver is invoked.
+ */
+#ifdef CONFIG_NET_IPV4
+ if (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')) {
+ char ip_addr[NET_IPV6_ADDR_LEN] = {0};
+ char subnet_mask[NET_IPV6_ADDR_LEN] = {0};
+
+ if (!split_ipv4_and_subnet(addr_field, ip_addr, sizeof(ip_addr), subnet_mask,
+ sizeof(subnet_mask))) {
+ LOG_ERR("CGCONTRDP: failed to split IPv4+subnet: %s", addr_field);
+ return;
+ }
+ if (!parse_ip(true, ip_addr, &socket_data->ipv4.new_addr)) {
+ return;
+ }
+ if (!parse_ip(true, subnet_mask, &socket_data->ipv4.subnet)) {
+ return;
+ }
+ if (gw_field && !parse_ip(true, gw_field, &socket_data->ipv4.gateway)) {
+ return;
+ }
+ }
+#else
+ ARG_UNUSED(gw_field);
+#endif
+
+#ifdef CONFIG_NET_IPV6
+ if (addr_field && strchr(addr_field, ':') &&
+ !parse_ip(false, addr_field, &socket_data->ipv6.new_addr)) {
+ return;
+ }
+#endif
+ /* Update DNS and configure interface */
+ if (!update_dns(socket_data,
+#ifdef CONFIG_NET_IPV4
+ (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')),
+#else
+ false,
+#endif
+ dns_field ? dns_field : "")) {
+ return;
+ }
+ /* Configure the interface addresses so net_if_is_up()/address selection
+ * will succeed before attempting to reconfigure the resolver.
+ */
+#ifdef CONFIG_NET_IPV4
+ set_iface(socket_data, (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')));
+#elif defined(CONFIG_NET_IPV6)
+ set_iface(socket_data, false);
+#endif
+
+ socket_data->dns.ready = false;
+ LOG_DBG("CGCONTRDP processed, dns strings: v4=%s v6=%s",
+#ifdef CONFIG_NET_IPV4
+ socket_data->dns.v4_string,
+#else
+ "<no-v4>",
+#endif
+#ifdef CONFIG_NET_IPV6
+ socket_data->dns.v6_string
+#else
+ "<no-v6>"
+#endif
+ );
+}
+/* ===== Network / Parsing Utilities ===================================
+ * Helpers that operate on IP address parsing and DNS/address helpers.
+ */
+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(struct hl78xx_socket_data *socket_data, bool is_ipv4, const char *dns_str)
+{
+ int ret;
+
+ /* ===== Interface helpers ==============================================
+ * Helpers that configure the network interface for IPv4/IPv6.
+ */
+ LOG_DBG("Updating DNS (%s): %s", is_ipv4 ? "IPv4" : "IPv6", dns_str);
+#ifdef CONFIG_NET_IPV4
+ 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);
+ }
+#else
+ if (is_ipv4) {
+ LOG_DBG("IPv4 DNS reported but IPv4 disabled in build; ignoring");
+ return false;
+ }
+#endif /* CONFIG_NET_IPV4 */
+#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 /* CONFIG_NET_IPV6 */
+ return true;
+}
+
+static void set_iface(struct hl78xx_socket_data *socket_data, bool is_ipv4)
+{
+ 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 (socket_data->ipv4.addr.s_addr != 0) {
+ net_if_ipv4_addr_rm(socket_data->net_iface, &socket_data->ipv4.addr);
+ }
+ /* Use MANUAL so the stack treats this as a configured address and it is
+ * available for source address selection immediately.
+ */
+ if (!net_if_ipv4_addr_add(socket_data->net_iface, &socket_data->ipv4.new_addr,
+ NET_ADDR_MANUAL, 0)) {
+ LOG_ERR("Failed to set IPv4 interface address.");
+ }
+
+ net_if_ipv4_set_netmask_by_addr(socket_data->net_iface, &socket_data->ipv4.new_addr,
+ &socket_data->ipv4.subnet);
+ net_if_ipv4_set_gw(socket_data->net_iface, &socket_data->ipv4.gateway);
+
+ net_ipaddr_copy(&socket_data->ipv4.addr, &socket_data->ipv4.new_addr);
+ LOG_DBG("IPv4 interface configuration complete.");
+
+ (void)net_if_up(socket_data->net_iface);
+#else
+ LOG_DBG("IPv4 disabled: skipping IPv4 interface configuration");
+#endif /* CONFIG_NET_IPV4 */
+ }
+#ifdef CONFIG_NET_IPV6
+ else {
+ net_if_ipv6_addr_rm(socket_data->net_iface, &socket_data->ipv6.addr);
+
+ if (!net_if_ipv6_addr_add(socket_data->net_iface, &socket_data->ipv6.new_addr,
+ NET_ADDR_MANUAL, 0)) {
+ LOG_ERR("Failed to set IPv6 interface address.");
+ } else {
+ LOG_DBG("IPv6 interface configuration complete.");
+ }
+ /* Ensure iface up after adding address */
+ (void)net_if_up(socket_data->net_iface);
+ }
+#endif /* CONFIG_NET_IPV6 */
+}
+
+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;
+ size_t ip_len = 0;
+
+ 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;
+ }
+
+ 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;
+}
+
+/* ===== Validation ====================================================
+ * Small validation helpers used by send/recv paths.
+ */
+static int validate_socket(const struct modem_socket *sock, struct hl78xx_socket_data *socket_data)
+{
+ if (!sock) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ bool not_connected = (!sock->is_connected && sock->type != SOCK_DGRAM);
+ bool tcp_disconnected =
+ (sock->type == SOCK_STREAM &&
+ !socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_connected);
+ bool udp_not_created =
+ (sock->type == SOCK_DGRAM &&
+ !socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(sock->id)].is_created);
+
+ if (not_connected || tcp_disconnected || udp_not_created) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* ===== Parser helpers ================================================
+ * Helpers that implement the streaming parser for incoming socket payloads
+ * and chat end-delimiter/EOF matching logic.
+ */
+static void parser_reset(struct hl78xx_socket_data *socket_data)
+{
+ memset(&socket_data->receive_buf, 0, sizeof(socket_data->receive_buf));
+ socket_data->parser_match_found = false;
+}
+
+static void found_reset(struct hl78xx_socket_data *socket_data)
+{
+ if (!socket_data) {
+ return;
+ }
+ /* Clear all parser progress state so a new transfer can start cleanly. */
+ socket_data->parser_state = HL78XX_PARSER_IDLE;
+ socket_data->parser_match_found = false;
+ socket_data->parser_socket_data_received = false;
+ socket_data->parser_eof_detected = false;
+ socket_data->parser_ok_detected = false;
+}
+
+static bool modem_chat_parse_end_del_start(struct hl78xx_socket_data *socket_data,
+ struct modem_chat *chat)
+{
+ if (socket_data->receive_buf.len == 0) {
+ return false;
+ }
+ /* If the last received byte matches any of the delimiter bytes, we are
+ * starting the end-delimiter sequence. Use memchr to avoid an explicit
+ * loop and to be clearer about intent.
+ */
+ return memchr(chat->delimiter,
+ socket_data->receive_buf.buf[socket_data->receive_buf.len - 1],
+ chat->delimiter_size) != NULL;
+}
+
+static bool modem_chat_parse_end_del_complete(struct hl78xx_socket_data *socket_data,
+ struct modem_chat *chat)
+{
+ if (socket_data->receive_buf.len < chat->delimiter_size) {
+ return false;
+ }
+
+ return memcmp(&socket_data->receive_buf
+ .buf[socket_data->receive_buf.len - chat->delimiter_size],
+ chat->delimiter, chat->delimiter_size) == 0;
+}
+
+static bool modem_chat_match_matches_received(struct hl78xx_socket_data *socket_data,
+ const char *match, uint16_t match_size)
+{
+ if (socket_data->receive_buf.len < match_size) {
+ return false;
+ }
+ return memcmp(socket_data->receive_buf.buf, match, match_size) == 0;
+}
+
+static bool is_receive_buffer_full(struct hl78xx_socket_data *socket_data)
+{
+ return socket_data->receive_buf.len >= ARRAY_SIZE(socket_data->receive_buf.buf);
+}
+
+static void handle_expected_length_decrement(struct hl78xx_socket_data *socket_data)
+{
+ /* Decrement expected length if CONNECT matched and expected length > 0 */
+ if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED &&
+ socket_data->expected_buf_len > 0) {
+ socket_data->expected_buf_len--;
+ }
+}
+
+static bool is_end_delimiter_only(struct hl78xx_socket_data *socket_data)
+{
+ return socket_data->receive_buf.len == socket_data->mdata_global->chat.delimiter_size;
+}
+
+static bool is_valid_eof_index(struct hl78xx_socket_data *socket_data, uint8_t size_match)
+{
+ socket_data->parser_start_index_eof = socket_data->receive_buf.len - size_match - 2;
+ return socket_data->parser_start_index_eof < ARRAY_SIZE(socket_data->receive_buf.buf);
+}
+
+/* Handle EOF pattern: if EOF_PATTERN is found at the expected location,
+ * push socket payload (excluding EOF marker) into the ring buffer.
+ * Returns number of bytes pushed on success, 0 otherwise.
+ */
+static int handle_eof_pattern(struct hl78xx_socket_data *socket_data)
+{
+ uint8_t size_match = strlen(EOF_PATTERN);
+
+ if (socket_data->receive_buf.len < size_match + 2) {
+ return 0;
+ }
+ if (!is_valid_eof_index(socket_data, size_match)) {
+ return 0;
+ }
+ if (strncmp(&socket_data->receive_buf.buf[socket_data->parser_start_index_eof], EOF_PATTERN,
+ size_match) == 0) {
+ int ret = ring_buf_put(socket_data->buf_pool, socket_data->receive_buf.buf,
+ socket_data->parser_start_index_eof);
+
+ if (ret <= 0) {
+ LOG_ERR("ring_buf_put failed: %d", ret);
+ return 0;
+ }
+
+ /* Mark that payload was successfully pushed and EOF was detected */
+ socket_data->parser_socket_data_received = true;
+ socket_data->parser_eof_detected = true;
+ LOG_DBG("pushed %d bytes to ring_buf; "
+ "collected_buf_len(before)=%u",
+ ret, socket_data->collected_buf_len);
+ socket_data->collected_buf_len += ret;
+ LOG_DBG("parser_socket_data_received=1 "
+ "collected_buf_len(after)=%u",
+ socket_data->collected_buf_len);
+ return ret;
+ }
+ return 0;
+}
+
+/* Helper: centralize handling when the chat end-delimiter has been fully
+ * received. Returns true if caller should return immediately after handling.
+ */
+static bool handle_delimiter_complete(struct hl78xx_socket_data *socket_data,
+ struct modem_chat *chat)
+{
+ if (!modem_chat_parse_end_del_complete(socket_data, chat)) {
+ return false;
+ }
+
+ if (is_end_delimiter_only(socket_data)) {
+ parser_reset(socket_data);
+ return true;
+ }
+
+ socket_data->parser_size_of_socketdata = socket_data->receive_buf.len;
+ if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED &&
+ socket_data->parser_state != HL78XX_PARSER_EOF_OK_MATCHED) {
+ size_t connect_len = strlen(CONNECT_STRING);
+ size_t connect_plus_delim = connect_len + chat->delimiter_size;
+
+ /* Case 1: Drop the initial "CONNECT" line including its CRLF */
+ if (socket_data->receive_buf.len == connect_plus_delim &&
+ modem_chat_match_matches_received(socket_data, CONNECT_STRING,
+ (uint16_t)connect_len)) {
+ parser_reset(socket_data);
+ return true;
+ }
+
+ /* Case 2: Try to handle EOF; only reset if EOF was actually found/pushed */
+ if (handle_eof_pattern(socket_data) > 0) {
+ parser_reset(socket_data);
+ return true;
+ }
+
+ /* Not the initial CONNECT+CRLF and no EOF yet -> keep accumulating */
+ return false;
+ }
+
+ /* For other states, treat CRLF as end-of-line and reset as before */
+ parser_reset(socket_data);
+ return true;
+}
+
+/* Convenience helper for matching an exact string against the receive buffer.
+ * This consolidates the repeated pattern of checking length and content.
+ */
+static inline bool modem_chat_match_exact(struct hl78xx_socket_data *socket_data, const char *match)
+{
+ size_t size_match = strlen(match);
+
+ if (socket_data->receive_buf.len != size_match) {
+ return false;
+ }
+ return modem_chat_match_matches_received(socket_data, match, (uint16_t)size_match);
+}
+
+static void socket_process_bytes(struct hl78xx_socket_data *socket_data, char byte)
+{
+ const size_t cme_size = strlen(CME_ERROR_STRING);
+
+ if (is_receive_buffer_full(socket_data)) {
+ LOG_WRN("Receive buffer overrun");
+ parser_reset(socket_data);
+ return;
+ }
+ socket_data->receive_buf.buf[socket_data->receive_buf.len++] = byte;
+ handle_expected_length_decrement(socket_data);
+ if (handle_delimiter_complete(socket_data, &socket_data->mdata_global->chat)) {
+ return;
+ }
+ if (modem_chat_parse_end_del_start(socket_data, &socket_data->mdata_global->chat)) {
+ return;
+ }
+ if (socket_data->parser_state != HL78XX_PARSER_ERROR_MATCHED &&
+ socket_data->parser_state != HL78XX_PARSER_CONNECT_MATCHED) {
+ /* Exact CONNECT match: length must equal CONNECT string length */
+ if (modem_chat_match_exact(socket_data, CONNECT_STRING)) {
+ socket_data->parser_state = HL78XX_PARSER_CONNECT_MATCHED;
+ LOG_DBG("CONNECT matched. Expecting %d more bytes.",
+ socket_data->expected_buf_len);
+ return;
+ }
+ /* Partial CME ERROR match: length must be at least CME string length */
+ if (socket_data->receive_buf.len >= cme_size &&
+ modem_chat_match_matches_received(socket_data, CME_ERROR_STRING,
+ (uint16_t)cme_size)) {
+ socket_data->parser_state =
+ HL78XX_PARSER_ERROR_MATCHED; /* 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->socket_data_error = true;
+ k_sem_give(&socket_data->mdata_global->script_stopped_sem_rx_int);
+ return;
+ }
+ }
+ if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED &&
+ socket_data->parser_state != HL78XX_PARSER_EOF_OK_MATCHED &&
+ modem_chat_match_exact(socket_data, OK_STRING)) {
+ socket_data->parser_state = HL78XX_PARSER_EOF_OK_MATCHED;
+ /* Mark that OK was observed. Payload may have already been pushed by EOF handler.
+ */
+ socket_data->parser_ok_detected = true;
+ LOG_DBG("OK matched. parser_ok_detected=%d parser_socket_data_received=%d "
+ "collected=%u",
+ socket_data->parser_ok_detected, socket_data->parser_socket_data_received,
+ socket_data->collected_buf_len);
+ }
+}
+
+/* ===== Modem pipe handlers ===========================================
+ * Handlers and callbacks for modem pipe events (receive/transmit).
+ */
+static int modem_process_handler(struct hl78xx_data *data)
+{
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ char work_buf_local[HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE] = {0};
+ int recv_len = 0;
+ int work_len = 0;
+ /* If no more data is expected, set leftover state and return */
+ if (socket_data->expected_buf_len == 0) {
+ LOG_DBG("No more data expected");
+ atomic_set_bit(&socket_data->mdata_global->state_leftover,
+ MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT);
+ return 0;
+ }
+
+ /* Use a small stack buffer for the pipe read to avoid TU-global BSS */
+ work_len = MIN(sizeof(work_buf_local), socket_data->expected_buf_len);
+ recv_len =
+ modem_pipe_receive(socket_data->mdata_global->uart_pipe, work_buf_local, work_len);
+ if (recv_len <= 0) {
+ return recv_len;
+ }
+
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+ LOG_HEXDUMP_DBG(work_buf_local, recv_len, "Received bytes:");
+#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
+ for (int i = 0; i < recv_len; i++) {
+ socket_process_bytes(socket_data, work_buf_local[i]);
+ }
+
+ LOG_DBG("post-process state=%d recv_len=%d recv_buf.len=%u "
+ "expected=%u collected=%u socket_data_received=%d",
+ socket_data->parser_state, recv_len, socket_data->receive_buf.len,
+ socket_data->expected_buf_len, socket_data->collected_buf_len,
+ socket_data->parser_socket_data_received);
+
+ /* Check if we've completed reception */
+ if (socket_data->parser_eof_detected && socket_data->parser_ok_detected &&
+ socket_data->parser_socket_data_received) {
+ LOG_DBG("All data received: %d bytes", socket_data->parser_size_of_socketdata);
+ socket_data->expected_buf_len = 0;
+ LOG_DBG("About to give RX semaphore (eof=%d ok=%d socket_data_received=%d "
+ "collected=%u)",
+ socket_data->parser_eof_detected, socket_data->parser_ok_detected,
+ socket_data->parser_socket_data_received, socket_data->collected_buf_len);
+ k_sem_give(&socket_data->mdata_global->script_stopped_sem_rx_int);
+ /* Clear parser progress after the receiver has been notified */
+ found_reset(socket_data);
+ }
+ return 0;
+}
+
+static void modem_pipe_callback(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_RECEIVE_READY:
+ (void)modem_process_handler(data);
+ break;
+
+ case MODEM_PIPE_EVENT_TRANSMIT_IDLE:
+ k_sem_give(&data->script_stopped_sem_tx_int);
+ break;
+
+ default:
+ LOG_DBG("Unhandled event: %d", event);
+ break;
+ }
+}
+
+void notif_carrier_off(const struct device *dev)
+{
+ struct hl78xx_data *data = dev->data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+
+ net_if_carrier_off(socket_data->net_iface);
+}
+
+void notif_carrier_on(const struct device *dev)
+{
+ struct hl78xx_data *data = dev->data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+
+ net_if_carrier_on(socket_data->net_iface);
+}
+
+void iface_status_work_cb(struct hl78xx_data *data, modem_chat_script_callback script_user_callback)
+{
+
+ const char *cmd = "AT+CGCONTRDP=1";
+ int ret = 0;
+
+ ret = modem_dynamic_cmd_send(data, script_user_callback, cmd, strlen(cmd),
+ hl78xx_get_cgdcontrdp_match(), 1, false);
+ if (ret < 0) {
+ LOG_ERR("Failed to send AT+CGCONTRDP command: %d", ret);
+ return;
+ }
+}
+
+void dns_work_cb(const struct device *dev, bool hard_reset)
+{
+#if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES)
+ int ret;
+ struct hl78xx_data *data = dev->data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ 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 (hard_reset) {
+ LOG_DBG("Resetting DNS resolver");
+ dnsCtx = dns_resolve_get_default();
+ if (!dnsCtx) {
+ LOG_WRN("No default DNS resolver context available; skipping "
+ "reconfigure");
+ socket_data->dns.ready = true;
+ return;
+ }
+ 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 */
+#ifdef CONFIG_NET_IPV4
+ 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);
+#endif
+ }
+#elif defined(CONFIG_NET_IPV4)
+ valid_address = net_ipaddr_parse(socket_data->dns.v4_string,
+ strlen(socket_data->dns.v4_string), &temp_addr);
+#else
+ /* No IP stack configured */
+ valid_address = false;
+#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) {
+ LOG_DBG("DNS already ready or net_iface problem %d %d %d", !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();
+ 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;
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ int ret = 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("%d Data retrieval mismatch: expected %u, got %d", __LINE__, len, ret);
+ return -EAGAIN;
+ }
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+ LOG_HEXDUMP_DBG(sock_data->recv_buf, ret, "Received Data:");
+#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
+ 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 */
+ 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)
+{
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+
+ return on_cmd_sockread_common(socket_data->current_sock_fd, socket_data->sizeof_socket_data,
+ target_len, data);
+}
+
+static int extract_ip_family_and_port(const struct sockaddr *addr, int *af, uint16_t *port)
+{
+#if defined(CONFIG_NET_IPV6)
+ if (addr->sa_family == AF_INET6) {
+ *port = ntohs(net_sin6(addr)->sin6_port);
+ *af = MDM_HL78XX_SOCKET_AF_IPV6;
+ } else {
+#endif /* CONFIG_NET_IPV6 */
+#if defined(CONFIG_NET_IPV4)
+ if (addr->sa_family == AF_INET) {
+ *port = ntohs(net_sin(addr)->sin_port);
+ *af = MDM_HL78XX_SOCKET_AF_IPV4;
+ } else {
+#endif /* CONFIG_NET_IPV4 */
+ errno = EAFNOSUPPORT;
+ return -1;
+#if defined(CONFIG_NET_IPV4)
+ }
+#endif /* CONFIG_NET_IPV4 */
+#if defined(CONFIG_NET_IPV6)
+ }
+#endif /* CONFIG_NET_IPV6 */
+ return 0;
+}
+
+static int format_ip_and_setup_tls(struct hl78xx_socket_data *socket_data,
+ const struct sockaddr *addr, char *ip_str, size_t ip_str_len,
+ struct modem_socket *sock)
+{
+ int ret = modem_context_sprint_ip_addr(addr, ip_str, ip_str_len);
+
+ if (ret != 0) {
+ LOG_ERR("Failed to format IP!");
+ errno = ENOMEM;
+ return -1;
+ }
+ if (sock->ip_proto == IPPROTO_TCP) {
+ /* Determine actual length of the formatted IP string (it may be
+ * shorter than the provided buffer size). Copy at most
+ * MDM_MAX_HOSTNAME_LEN - 1 bytes and ensure NUL-termination to
+ * avoid writing past the hostname buffer.
+ */
+ size_t actual_len = strnlen(ip_str, ip_str_len);
+ size_t copy_len = MIN(actual_len, (size_t)MDM_MAX_HOSTNAME_LEN - 1);
+
+ if (copy_len > 0) {
+ memcpy(socket_data->tls.hostname, ip_str, copy_len);
+ }
+ socket_data->tls.hostname[copy_len] = '\0';
+ socket_data->tls.hostname_set = false;
+ }
+ return 0;
+}
+
+static int send_tcp_or_tls_config(struct modem_socket *sock, uint16_t dst_port, int af, int mode,
+ struct hl78xx_socket_data *socket_data)
+{
+ int ret = 0;
+ char cmd_buf[sizeof("AT+KTCPCFG=#,#,\"" MODEM_HL78XX_ADDRESS_FAMILY_FORMAT
+ "\",#####,,,,#,,#") +
+ MDM_MAX_HOSTNAME_LEN + NET_IPV6_ADDR_LEN];
+
+ snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCFG=1,%d,\"%s\",%u,,,,%d,%s,0", mode,
+ socket_data->tls.hostname, dst_port, af, mode == 3 ? "0" : "");
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+ hl78xx_get_ktcpcfg_match(), 1, false);
+ if (ret < 0 ||
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_created == false) {
+ LOG_ERR("%s ret:%d", cmd_buf, ret);
+ modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+ /* Map negative internal return codes to positive errno; fall back to EIO
+ * when the code is non-negative but the operation failed.
+ */
+ hl78xx_set_errno_from_code(ret);
+ return -1;
+ }
+ return 0;
+}
+
+static int send_udp_config(const struct sockaddr *addr, struct hl78xx_socket_data *socket_data,
+ struct modem_socket *sock)
+{
+ int ret = 0;
+ char cmd_buf[64];
+ uint8_t display_data_urc = 0;
+
+#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", 0, display_data_urc,
+ (addr->sa_family - 1), 0);
+
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+ hl78xx_get_kudpind_match(), 1, false);
+ if (ret < 0) {
+ goto error;
+ }
+ return 0;
+error:
+ LOG_ERR("%s ret:%d", cmd_buf, ret);
+ modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+ hl78xx_set_errno_from_code(ret);
+ return -1;
+}
+
+static int create_socket(struct modem_socket *sock, const struct sockaddr *addr,
+ struct hl78xx_socket_data *data)
+{
+ LOG_DBG("entry fd=%d id=%d", sock->sock_fd, sock->id);
+ int af;
+ uint16_t dst_port;
+ char ip_str[NET_IPV6_ADDR_LEN];
+ bool is_udp;
+ int mode;
+ int ret;
+ /* save destination address */
+ memcpy(&sock->dst, addr, sizeof(*addr));
+ if (extract_ip_family_and_port(addr, &af, &dst_port) < 0) {
+ return -1;
+ }
+ if (format_ip_and_setup_tls(data, addr, ip_str, sizeof(ip_str), sock) < 0) {
+ return -1;
+ }
+ is_udp = (sock->ip_proto == IPPROTO_UDP);
+ if (is_udp) {
+ ret = send_udp_config(addr, data, sock);
+ LOG_DBG("send_udp_config returned %d", ret);
+ return ret;
+ }
+ mode = (sock->ip_proto == IPPROTO_TLS_1_2) ? 3 : 0;
+ /* only TCP and TLS are supported */
+ if (sock->ip_proto != IPPROTO_TCP && sock->ip_proto != IPPROTO_TLS_1_2) {
+ LOG_ERR("Unsupported protocol: %d", sock->ip_proto);
+ errno = EPROTONOSUPPORT;
+ return -1;
+ }
+ LOG_DBG("TCP/TLS socket, calling send_tcp_or_tls_config af=%d port=%u "
+ "mode=%d",
+ af, dst_port, mode);
+ ret = send_tcp_or_tls_config(sock, dst_port, af, mode, data);
+ LOG_DBG("send_tcp_or_tls_config returned %d", ret);
+ return ret;
+}
+
+static int socket_close(struct hl78xx_socket_data *socket_data, 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_dynamic_cmd_send(socket_data->mdata_global, NULL, buf, strlen(buf),
+ hl78xx_get_sockets_allow_matches(),
+ hl78xx_get_sockets_allow_matches_size(), false);
+ if (ret < 0) {
+ LOG_ERR("%s ret:%d", buf, ret);
+ }
+ return ret;
+}
+
+static int socket_delete(struct hl78xx_socket_data *socket_data, 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);
+ * No need to delete udp config here according to ref guide. The at UDPCLOSE
+ * automatically deletes the session
+ */
+ return 0;
+ }
+ snprintk(buf, sizeof(buf), "AT+KTCPDEL=%d", sock->id);
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, buf, strlen(buf),
+ hl78xx_get_sockets_allow_matches(),
+ hl78xx_get_sockets_allow_matches_size(), 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(); use accessor and check */
+ struct hl78xx_socket_data *g = hl78xx_get_socket_global();
+
+ HL78XX_LOG_DBG("%d %d %d %d", __LINE__, family, type, proto);
+
+ if (!g) {
+ LOG_ERR("Socket global not initialized");
+ errno = ENODEV;
+ return -1;
+ }
+ ret = modem_socket_get(&g->socket_config, family, type, proto);
+ if (ret < 0) {
+ hl78xx_set_errno_from_code(ret);
+ return -1;
+ }
+ errno = 0;
+ return ret;
+}
+
+static int offload_close(void *obj)
+{
+ struct modem_socket *sock = (struct modem_socket *)obj;
+ struct hl78xx_socket_data *socket_data = NULL;
+
+ if (!sock) {
+ return -EINVAL;
+ }
+ /* Recover the containing instance; guard in case sock isn't from this driver */
+ socket_data = hl78xx_get_socket_global();
+ if (!socket_data || !socket_data->offload_dev ||
+ socket_data->offload_dev->data != socket_data) {
+ LOG_WRN("parent mismatch: parent != offload_dev->data (%p != %p)", socket_data,
+ socket_data ? socket_data->offload_dev->data : NULL);
+ errno = EINVAL;
+ return -1;
+ }
+ /* 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, socket_data) == 0) {
+ socket_close(socket_data, sock);
+ socket_delete(socket_data, 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;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+ int ret = 0;
+
+ if (!sock || !socket_data || !socket_data->offload_dev) {
+ errno = EINVAL;
+ return -1;
+ }
+ LOG_DBG("entry for socket fd=%d id=%d", ((struct modem_socket *)obj)->sock_fd,
+ ((struct modem_socket *)obj)->id);
+ /* 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 */
+ ret = create_socket(sock, addr, socket_data);
+ LOG_DBG("create_socket returned %d", ret);
+ if (ret < 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;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+ int ret = 0;
+ char cmd_buf[sizeof("AT+KTCPCFG=#\r")];
+ char ip_str[NET_IPV6_ADDR_LEN];
+
+ if (!addr || !socket_data || !socket_data->offload_dev) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!hl78xx_is_registered(socket_data->mdata_global)) {
+ errno = ENETUNREACH;
+ 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) {
+ LOG_DBG("%d no socket assigned", __LINE__);
+ if (create_socket(sock, addr, socket_data) < 0) {
+ return -1;
+ }
+ }
+ memcpy(&sock->dst, addr, sizeof(*addr));
+ /* 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) {
+ hl78xx_set_errno_from_code(ret);
+ LOG_ERR("Error formatting IP string %d", ret);
+ return -1;
+ }
+ /* send connect command */
+ snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCNX=%d", sock->id);
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+ hl78xx_get_ktcpind_match(), 1, false);
+ if (ret < 0 ||
+ socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_connected == false) {
+ sock->is_connected = false;
+ LOG_ERR("%s ret:%d", cmd_buf, ret);
+ /* Map tcp_conn_status.err_code:
+ * - positive values are assumed to be direct POSIX errno values -> pass
+ * through
+ * - zero or unknown -> use conservative EIO
+ */
+ errno = (socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].err_code > 0)
+ ? socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)]
+ .err_code
+ : EIO;
+ 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 wait_for_data_if_needed(struct hl78xx_socket_data *socket_data,
+ 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, socket_data) == -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(struct hl78xx_socket_data *socket_data, 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);
+}
+
+/* Perform the receive transaction: release chat, attach pipe, wait for tx sem,
+ * transmit read command, wait for rx sem and capture data. Returns 0 on
+ * success or a negative code which will be mapped by caller.
+ */
+static int hl78xx_perform_receive_transaction(struct hl78xx_socket_data *socket_data,
+ const char *sendbuf)
+{
+ int rv;
+ int ret;
+
+ modem_chat_release(&socket_data->mdata_global->chat);
+ modem_pipe_attach(socket_data->mdata_global->uart_pipe, modem_pipe_callback,
+ socket_data->mdata_global);
+
+ rv = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER);
+ if (rv < 0) {
+ LOG_ERR("%s: k_sem_take(tx) returned %d", __func__, rv);
+ return rv;
+ }
+
+ ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe, (const uint8_t *)sendbuf,
+ strlen(sendbuf));
+ if (ret < 0) {
+ LOG_ERR("Error sending read command: %d", ret);
+ return ret;
+ }
+ rv = k_sem_take(&socket_data->mdata_global->script_stopped_sem_rx_int, K_FOREVER);
+ if (rv < 0) {
+ return rv;
+ }
+
+ rv = modem_handle_data_capture(socket_data->sizeof_socket_data, socket_data->mdata_global);
+ if (rv < 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+static void setup_socket_data(struct hl78xx_socket_data *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 check_tcp_state_if_needed(struct hl78xx_socket_data *socket_data,
+ struct modem_socket *sock)
+{
+ const char *check_ktcp_stat = "AT+KTCPSTAT";
+ /* Only check for TCP sockets */
+ if (sock->type != SOCK_STREAM) {
+ return;
+ }
+ if (atomic_test_and_clear_bit(&socket_data->mdata_global->state_leftover,
+ MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT) &&
+ sock && sock->ip_proto == IPPROTO_TCP) {
+ modem_dynamic_cmd_send(socket_data->mdata_global, NULL, check_ktcp_stat,
+ strlen(check_ktcp_stat), hl78xx_get_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;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+ char sendbuf[sizeof("AT+KUDPRCV=#,##########\r\n")];
+ struct socket_read_data sock_data;
+ int next_packet_size = 0;
+ uint32_t max_data_length = 0;
+ uint16_t read_size = 0;
+ int trv = 0;
+ int ret;
+
+ if (!sock || !socket_data || !socket_data->offload_dev) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* If modem is not registered yet, propagate EAGAIN to indicate try again
+ * later. However, if the socket simply isn't connected (validate_socket
+ * returns -1) we return 0 with errno cleared so upper layers (eg. DNS
+ * dispatcher) treat this as no data available rather than an error and
+ * avoid noisy repeated error logs.
+ */
+ if (!hl78xx_is_registered(socket_data->mdata_global)) {
+ errno = EAGAIN;
+ return -1;
+ }
+ if (validate_socket(sock, socket_data) == -1) {
+ errno = 0;
+ return 0;
+ }
+
+ if (!validate_recv_args(buf, len, flags)) {
+ return -1;
+ }
+ 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);
+ hl78xx_set_errno_from_code(ret);
+ return -1;
+ }
+ next_packet_size = wait_for_data_if_needed(socket_data, sock, flags);
+ if (next_packet_size <= 0) {
+ ret = next_packet_size;
+ goto exit;
+ }
+ max_data_length =
+ MDM_MAX_DATA_LENGTH - (socket_data->mdata_global->buffers.eof_pattern_size +
+ sizeof(MODEM_STREAM_STARTER_WORD) - 1);
+ /* limit read size to modem max data length */
+ next_packet_size = MIN(next_packet_size, max_data_length);
+ /* limit read size to user buffer length */
+ read_size = MIN(next_packet_size, len);
+ /* prepare socket data for the read operation */
+ setup_socket_data(socket_data, sock, &sock_data, buf, len, from, read_size);
+ prepare_read_command(socket_data, sendbuf, sizeof(sendbuf), sock, read_size);
+ HL78XX_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");
+ trv = hl78xx_perform_receive_transaction(socket_data, sendbuf);
+ if (trv < 0) {
+ hl78xx_set_errno_from_code(trv);
+ 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:
+ 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;
+ check_tcp_state_if_needed(socket_data, sock);
+ return ret;
+}
+
+int check_if_any_socket_connected(const struct device *dev)
+{
+ struct hl78xx_data *data = dev->data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+ 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 */
+ k_sem_give(&cfg->sem_lock);
+ return true;
+ }
+ }
+ k_sem_give(&cfg->sem_lock);
+ return false;
+}
+
+/* ===== Send / Receive helpers ========================================
+ * Helpers used by sendto/recv paths, preparing commands and transmitting
+ * data over the modem pipe.
+ */
+static int prepare_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr,
+ size_t buf_len, char *cmd_buf, size_t cmd_buf_size)
+{
+ int ret = 0;
+
+ if (sock->ip_proto == IPPROTO_UDP) {
+ char ip_str[NET_IPV6_ADDR_LEN];
+ uint16_t dst_port = 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;
+ }
+
+ /* Default to TCP-style send command */
+ snprintk(cmd_buf, cmd_buf_size, "AT+KTCPSND=%d,%zu", sock->id, buf_len);
+ return 0;
+}
+
+static int send_data_buffer(struct hl78xx_socket_data *socket_data, 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) {
+ LOG_DBG("waiting for TX semaphore (offset=%u len=%d)", offset, len);
+ if (k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER) <
+ 0) {
+ LOG_ERR("%s: k_sem_take(tx) failed", __func__);
+ 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;
+}
+
+static int validate_and_prepare(struct modem_socket *sock, const struct sockaddr **dst_addr,
+ size_t *buf_len, char *cmd_buf, size_t cmd_buf_len)
+{
+ /* Validate args and prepare send command */
+ if (!sock) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (sock->type != SOCK_DGRAM && !sock->is_connected) {
+ errno = ENOTCONN;
+ return -1;
+ }
+ 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;
+ }
+ /* Consolidated send command helper handles UDP vs TCP formatting */
+ return prepare_send_cmd(sock, *dst_addr, *buf_len, cmd_buf, cmd_buf_len);
+}
+
+static int transmit_regular_data(struct hl78xx_socket_data *socket_data, const char *buf,
+ size_t buf_len, int *sock_written)
+{
+ int ret;
+
+ ret = send_data_buffer(socket_data, buf, buf_len, sock_written);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER);
+ if (ret < 0) {
+ LOG_ERR("%s: k_sem_take(tx) returned %d", __func__, ret);
+ return ret;
+ }
+ return 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);
+}
+
+/* send binary data via the +KUDPSND/+KTCPSND commands */
+static ssize_t send_socket_data(void *obj, struct hl78xx_socket_data *socket_data,
+ 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_and_prepare(sock, &dst_addr, &buf_len, cmd_buf, sizeof(cmd_buf));
+ if (ret < 0) {
+ return ret;
+ }
+ socket_data->socket_data_error = false;
+ if (k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)) < 0) {
+ return -1;
+ }
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+ (const struct modem_chat_match *)hl78xx_get_connect_matches(),
+ hl78xx_get_connect_matches_size(), false);
+ if (ret < 0 || socket_data->socket_data_error) {
+ hl78xx_set_errno_from_code(ret);
+ ret = -1;
+ goto cleanup;
+ }
+ modem_pipe_attach(socket_data->mdata_global->chat.pipe, modem_pipe_callback,
+ socket_data->mdata_global);
+ ret = transmit_regular_data(socket_data, buf, buf_len, &sock_written);
+ if (ret < 0) {
+ goto cleanup;
+ }
+ modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe);
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, "", 0,
+ hl78xx_get_sockets_ok_match(), 1, false);
+ if (ret < 0) {
+ LOG_ERR("Final confirmation failed: %d", ret);
+ goto cleanup;
+ }
+cleanup:
+ k_mutex_unlock(&socket_data->mdata_global->tx_lock);
+ return (ret < 0) ? -1 : sock_written;
+}
+
+#ifdef CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS
+/* ===== TLS implementation (conditional) ================================
+ * TLS credential upload and chipper settings helper implementations.
+ */
+static int handle_tls_sockopts(void *obj, int optname, const void *optval, socklen_t optlen)
+{
+ int ret;
+ struct modem_socket *sock = (struct modem_socket *)obj;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+
+ if (!sock || !socket_data || !socket_data->offload_dev) {
+ return -EINVAL;
+ }
+
+ switch (optname) {
+ case TLS_SEC_TAG_LIST:
+ ret = map_credentials(socket_data, optval, optlen);
+ return ret;
+
+ case TLS_HOSTNAME:
+ if (optlen >= MDM_MAX_HOSTNAME_LEN) {
+ return -EINVAL;
+ }
+ memset(socket_data->tls.hostname, 0, MDM_MAX_HOSTNAME_LEN);
+ memcpy(socket_data->tls.hostname, optval, optlen);
+ socket_data->tls.hostname[optlen] = '\0';
+ socket_data->tls.hostname_set = true;
+ ret = hl78xx_configure_chipper_suit(socket_data);
+ if (ret < 0) {
+ LOG_ERR("Failed to configure chipper suit: %d", ret);
+ return ret;
+ }
+ LOG_DBG("TLS hostname set to: %s", socket_data->tls.hostname);
+ return 0;
+
+ case TLS_PEER_VERIFY:
+ if (*(const uint32_t *)optval != TLS_PEER_VERIFY_REQUIRED) {
+ LOG_WRN("Disabling peer verification is not supported");
+ }
+ return 0;
+
+ case TLS_CERT_NOCOPY:
+ return 0; /* No-op, success */
+
+ default:
+ LOG_DBG("Unsupported TLS option: %d", optname);
+ return -EINVAL;
+ }
+}
+
+static int offload_setsockopt(void *obj, int level, int optname, const void *optval,
+ socklen_t optlen)
+{
+ int ret = 0;
+
+ if (!IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
+ return -EINVAL;
+ }
+ if (level == SOL_TLS) {
+ ret = handle_tls_sockopts(obj, optname, optval, optlen);
+ if (ret < 0) {
+ hl78xx_set_errno_from_code(ret);
+ return -1;
+ }
+ return 0;
+ }
+ LOG_DBG("Unsupported socket option: %d", optname);
+ return -EINVAL;
+}
+#endif /* CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS */
+
+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;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+
+ if (!sock || !socket_data || !socket_data->offload_dev) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!hl78xx_is_registered(socket_data->mdata_global)) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return -EAGAIN;
+ }
+ /* Do some sanity checks. */
+ if (!buf || len == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* For stream sockets (TCP) the socket must be connected. For datagram
+ * sockets (UDP) sendto can be used without a prior connect as long as a
+ * destination address is provided or the socket has a stored dst. The
+ * helper validate_and_prepare will supply sock->dst for UDP when needed.
+ */
+ if (sock->type != SOCK_DGRAM && !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, socket_data, to, buf, len, K_SECONDS(MDM_CMD_TIMEOUT));
+ if (ret < 0) {
+ /* Map internal negative return codes to positive errno values. Use EIO as
+ * a conservative fallback when ret is non-negative (unexpected)
+ */
+ hl78xx_set_errno_from_code(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;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+ struct zsock_pollfd *pfd;
+ struct k_poll_event **pev;
+ struct k_poll_event *pev_end;
+ /* sanity check: does parent == parent->offload_dev->data ? */
+ if (socket_data && socket_data->offload_dev &&
+ socket_data->offload_dev->data != socket_data) {
+ LOG_WRN("parent mismatch: parent != offload_dev->data (%p != %p)", socket_data,
+ socket_data->offload_dev->data);
+ }
+ switch (request) {
+ case ZFD_IOCTL_POLL_PREPARE:
+ pfd = va_arg(args, struct zsock_pollfd *);
+ pev = va_arg(args, struct k_poll_event **);
+ pev_end = va_arg(args, struct k_poll_event *);
+ 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:
+ 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: {
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+ int flags = va_arg(args, int);
+
+ LOG_DBG("F_SETFL called with flags=0x%x", flags);
+ ARG_UNUSED(flags);
+#endif
+ /* 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};
+ struct modem_socket *sock = (struct modem_socket *)obj;
+ struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+ size_t full_len = 0;
+ int ret;
+
+ if (!sock || !socket_data || !socket_data->offload_dev) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* 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;
+ }
+ 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++;
+ }
+ /* send_socket_data expects a buffer pointer and its byte length.
+ * crafted_msg.msg_iovlen is the number of iovec entries and is
+ * incorrect here (was causing sends of '2' bytes when two iovecs
+ * were present). Use the first iovec's iov_len for the byte length.
+ */
+ ret = send_socket_data(obj, socket_data, crafted_msg.msg_name,
+ crafted_msg.msg_iov->iov_base, crafted_msg.msg_iov->iov_len,
+ K_SECONDS(MDM_CMD_TIMEOUT));
+ if (bkp_iovec_idx != -1) {
+ msg->msg_iov[bkp_iovec_idx] = bkp_iovec;
+ }
+ if (ret < 0) {
+ /* Map negative internal return code to positive errno; fall back to
+ * EIO
+ */
+ hl78xx_set_errno_from_code(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,
+#if defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+ .setsockopt = offload_setsockopt,
+#else
+ .setsockopt = NULL,
+#endif
+};
+/* clang-format on */
+static int hl78xx_init_sockets(const struct device *dev)
+{
+ int ret;
+ struct hl78xx_socket_data *socket_data = (struct hl78xx_socket_data *)dev->data;
+
+ socket_data->buf_pool = &mdm_recv_pool;
+ /* 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;
+}
+static void socket_notify_data(int socket_id, int new_total, void *user_data)
+{
+ int ret = 0;
+ struct modem_socket *sock;
+ struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+ struct hl78xx_socket_data *socket_data =
+ (struct hl78xx_socket_data *)data->offload_dev->data;
+
+ if (!data || !socket_data) {
+ LOG_ERR("%s: invalid user_data", __func__);
+ return;
+ }
+ sock = modem_socket_from_id(&socket_data->socket_config, socket_id);
+ if (!sock) {
+ return;
+ }
+ /* Update the packet size */
+ 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);
+ }
+ /* Duplicate/chat callback block removed; grouped versions live earlier */
+}
+
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+static int hl78xx_configure_chipper_suit(struct hl78xx_socket_data *socket_data)
+{
+ const char *cmd_chipper_suit = "AT+KSSLCRYPTO=0,8,1,8192,4,4,3,0";
+
+ return modem_dynamic_cmd_send(
+ socket_data->mdata_global, NULL, cmd_chipper_suit, strlen(cmd_chipper_suit),
+ (const struct modem_chat_match *)hl78xx_get_ok_match(), 1, false);
+}
+/* send binary data via the K....STORE commands */
+static ssize_t hl78xx_send_cert(struct hl78xx_socket_data *socket_data, const char *cert_data,
+ size_t cert_len, enum tls_credential_type cert_type)
+{
+ int ret;
+ char send_buf[sizeof("AT+KPRIVKSTORE=#,####\r\n")];
+ int sock_written = 0;
+
+ if (!socket_data || !socket_data->mdata_global) {
+ return -EINVAL;
+ }
+
+ if (cert_len == 0 || !cert_data) {
+ LOG_ERR("Invalid certificate data or length");
+ return -EINVAL;
+ }
+ /** Certificate length exceeds maximum allowed size */
+ if (cert_len > MDM_MAX_CERT_LENGTH) {
+ return -EINVAL;
+ }
+
+ if (cert_type == TLS_CREDENTIAL_CA_CERTIFICATE ||
+ cert_type == TLS_CREDENTIAL_SERVER_CERTIFICATE) {
+ snprintk(send_buf, sizeof(send_buf), "AT+KCERTSTORE=%d,%d", (cert_type - 1),
+ cert_len);
+
+ } else if (cert_type == TLS_CREDENTIAL_PRIVATE_KEY) {
+ snprintk(send_buf, sizeof(send_buf), "AT+KPRIVKSTORE=0,%d", cert_len);
+
+ } else {
+ LOG_ERR("Unsupported certificate type: %d", cert_type);
+ return -EINVAL;
+ }
+ socket_data->socket_data_error = false;
+ if (k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)) < 0) {
+ errno = EBUSY;
+ return -1;
+ }
+ ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, send_buf, strlen(send_buf),
+ (const struct modem_chat_match *)hl78xx_get_connect_matches(),
+ hl78xx_get_connect_matches_size(), 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);
+ ret = send_data_buffer(socket_data, cert_data, cert_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_dynamic_cmd_send(socket_data->mdata_global, NULL, "", 0,
+ (const struct modem_chat_match *)hl78xx_get_ok_match(), 1,
+ false);
+ if (ret < 0) {
+ LOG_ERR("Final confirmation failed: %d", ret);
+ goto cleanup;
+ }
+cleanup:
+ k_mutex_unlock(&socket_data->mdata_global->tx_lock);
+ return (ret < 0) ? -1 : sock_written;
+}
+
+static int map_credentials(struct hl78xx_socket_data *socket_data, const void *optval,
+ socklen_t optlen)
+{
+ const sec_tag_t *sec_tags = (const sec_tag_t *)optval;
+ int ret = 0;
+ int tags_len;
+ sec_tag_t tag;
+ int i;
+ struct tls_credential *cert;
+
+ if ((optlen % sizeof(sec_tag_t)) != 0 || (optlen == 0)) {
+ return -EINVAL;
+ }
+ tags_len = optlen / sizeof(sec_tag_t);
+ /* For each tag, retrieve the credentials value and type: */
+ for (i = 0; i < tags_len; i++) {
+ tag = sec_tags[i];
+ cert = credential_next_get(tag, NULL);
+ while (cert != NULL) {
+ switch (cert->type) {
+ case TLS_CREDENTIAL_CA_CERTIFICATE:
+ LOG_DBG("TLS_CREDENTIAL_CA_CERTIFICATE tag: %d", tag);
+ break;
+
+ case TLS_CREDENTIAL_SERVER_CERTIFICATE:
+ LOG_DBG("TLS_CREDENTIAL_SERVER_CERTIFICATE tag: %d", tag);
+ break;
+
+ case TLS_CREDENTIAL_PRIVATE_KEY:
+ LOG_DBG("TLS_CREDENTIAL_PRIVATE_KEY tag: %d", tag);
+ break;
+
+ case TLS_CREDENTIAL_NONE:
+ case TLS_CREDENTIAL_PSK:
+ case TLS_CREDENTIAL_PSK_ID:
+ default:
+ /* Not handled */
+ return -EINVAL;
+ }
+ ret = hl78xx_send_cert(socket_data, cert->buf, cert->len, cert->type);
+ if (ret < 0) {
+ return ret;
+ }
+ cert = credential_next_get(tag, cert);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static int hl78xx_socket_init(const struct device *dev)
+{
+
+ struct hl78xx_socket_data *data = (struct hl78xx_socket_data *)dev->data;
+
+ data->offload_dev = dev;
+ /* Ensure the parent modem device pointer was set at static init time */
+ if (data->modem_dev == NULL) {
+ LOG_ERR("modem_dev not initialized for %s", dev->name);
+ return -EINVAL;
+ }
+ /* Ensure the modem device is ready before accessing its driver data */
+ if (!device_is_ready(data->modem_dev)) {
+ LOG_ERR("modem device %s not ready", data->modem_dev->name);
+ return -ENODEV;
+ }
+ if (data->modem_dev->data == NULL) {
+ LOG_ERR("modem device %s has no driver data yet", data->modem_dev->name);
+ return -EAGAIN;
+ }
+ data->mdata_global = (struct hl78xx_data *)data->modem_dev->data;
+ data->mdata_global->offload_dev = dev;
+ /* Keep original single global pointer usage but set via accessor. */
+ hl78xx_set_socket_global(data);
+ atomic_set(&data->mdata_global->state_leftover, 0);
+
+ return 0;
+}
+
+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;
+
+ /* startup trace */
+ if (!data->mdata_global) {
+ LOG_WRN("mdata_global not set for net iface init on %s", dev->name);
+ }
+ net_if_set_link_addr(
+ iface,
+ modem_get_mac(data->mac_addr,
+ data->mdata_global ? data->mdata_global->identity.imei : NULL),
+ 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)
+{
+ bool fam_ok = false;
+
+#ifdef CONFIG_NET_IPV4
+ if (family == AF_INET) {
+ fam_ok = true;
+ }
+#endif
+#ifdef CONFIG_NET_IPV6
+ if (family == AF_INET6) {
+ fam_ok = true;
+ }
+#endif
+ if (!fam_ok) {
+ return false;
+ }
+ if (!(type == SOCK_DGRAM || type == SOCK_STREAM)) {
+ return false;
+ }
+ if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) {
+ return true;
+ }
+#if defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+ if (proto == IPPROTO_TLS_1_2) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+#define MODEM_HL78XX_DEFINE_OFFLOAD_INSTANCE(inst) \
+ static struct hl78xx_socket_data hl78xx_socket_data_##inst = { \
+ .modem_dev = DEVICE_DT_GET(DT_PARENT(DT_DRV_INST(inst))), \
+ }; \
+ NET_DEVICE_OFFLOAD_INIT( \
+ inst, "hl78xx_dev", hl78xx_socket_init, NULL, &hl78xx_socket_data_##inst, 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_OFFLOAD_DEVICE_SWIR_HL78XX(inst) MODEM_HL78XX_DEFINE_OFFLOAD_INSTANCE(inst)
+
+#define DT_DRV_COMPAT swir_hl7812_offload
+DT_INST_FOREACH_STATUS_OKAY(MODEM_OFFLOAD_DEVICE_SWIR_HL78XX)
+#undef DT_DRV_COMPAT
+
+#define DT_DRV_COMPAT swir_hl7800_offload
+DT_INST_FOREACH_STATUS_OKAY(MODEM_OFFLOAD_DEVICE_SWIR_HL78XX)
+#undef DT_DRV_COMPAT
diff --git a/include/zephyr/drivers/modem/hl78xx_apis.h b/include/zephyr/drivers/modem/hl78xx_apis.h
new file mode 100644
index 0000000..bffd35d
--- /dev/null
+++ b/include/zephyr/drivers/modem/hl78xx_apis.h
@@ -0,0 +1,458 @@
+/*
+ * 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 <zephyr/types.h>
+#include <zephyr/device.h>
+#include <errno.h>
+#include <stddef.h>
+#include <zephyr/modem/chat.h>
+#include <zephyr/drivers/cellular.h>
+
+#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 * (v)))
+#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
+#define MDM_MANUFACTURER_LENGTH 20
+#define MDM_MODEL_LENGTH 32
+#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 MDM_MAX_CERT_LENGTH 4096
+#define MDM_MAX_HOSTNAME_LEN 128
+/**
+ * @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 STRUCT_SECTION_ITERABLE(hl78xx_evt_monitor_entry, name) = { \
+ .handler = _handler, \
+ .next = NULL, \
+ .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_HL78XX_12
+ HL78XX_RAT_GSM,
+#ifdef CONFIG_MODEM_HL78XX_12_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
+};
+
+/** Phone functionality modes */
+enum hl78xx_phone_functionality {
+ HL78XX_SIM_POWER_OFF,
+ HL78XX_FULLY_FUNCTIONAL,
+ HL78XX_AIRPLANE = 4,
+};
+/** Module status codes */
+enum hl78xx_module_status {
+ HL78XX_MODULE_READY = 0,
+ HL78XX_MODULE_WAITING_FOR_ACCESS_CODE,
+ HL78XX_MODULE_SIM_NOT_PRESENT,
+ HL78XX_MODULE_SIMLOCK,
+ HL78XX_MODULE_UNRECOVERABLE_ERROR,
+ HL78XX_MODULE_UNKNOWN_STATE,
+ HL78XX_MODULE_INACTIVE_SIM
+};
+
+/** Cellular modem info type */
+enum hl78xx_modem_info_type {
+ /* <APN> Access Point Name */
+ HL78XX_MODEM_INFO_APN,
+ /* <Current RAT> */
+ HL78XX_MODEM_INFO_CURRENT_RAT,
+ /* <Network Operator> */
+ HL78XX_MODEM_INFO_NETWORK_OPERATOR,
+};
+
+/** 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;
+};
+
+enum hl78xx_evt_type {
+ HL78XX_LTE_RAT_UPDATE,
+ HL78XX_LTE_REGISTRATION_STAT_UPDATE,
+ HL78XX_LTE_SIM_REGISTRATION,
+ HL78XX_LTE_MODEM_STARTUP,
+};
+
+struct hl78xx_evt {
+ enum hl78xx_evt_type type;
+
+ union {
+ enum cellular_registration_status reg_status;
+ enum hl78xx_cell_rat_mode rat_mode;
+ 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 cellular_signal_type type,
+ int16_t *value);
+
+/** API for getting modem information */
+typedef int (*hl78xx_api_get_modem_info)(const struct device *dev,
+ const enum cellular_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 cellular_access_technology tech,
+ enum cellular_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);
+
+/**< Event monitor entry */
+struct hl78xx_evt_monitor_entry; /* forward declaration */
+/* Event monitor dispatcher */
+typedef void (*hl78xx_evt_monitor_dispatcher_t)(struct hl78xx_evt *notif);
+/* Event monitor handler */
+typedef void (*hl78xx_evt_monitor_handler_t)(struct hl78xx_evt *notif,
+ struct hl78xx_evt_monitor_entry *mon);
+
+struct hl78xx_evt_monitor_entry {
+ /** Monitor callback. */
+ const hl78xx_evt_monitor_handler_t handler;
+ /* link for runtime list */
+ struct hl78xx_evt_monitor_entry *next;
+ struct {
+ uint8_t paused: 1; /* Monitor is paused. */
+ uint8_t direct: 1; /* Dispatch in ISR. */
+ } flags;
+};
+/**
+ * @brief hl78xx_api_func_set_phone_functionality
+ * @param dev Cellular network device instance
+ * @param functionality phone functionality mode to set
+ * @param reset If true, the modem will be reset as part of applying the functionality change.
+ * @return 0 if successful.
+ */
+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
+ * @param dev Cellular network device instance
+ * @param functionality Pointer to store the current phone functionality mode
+ * @return 0 if successful.
+ */
+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 Cellular network device instance
+ * @param type Type of the signal to retrieve
+ * @param value Pointer to store the signal value
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_get_signal(const struct device *dev, const enum cellular_signal_type type,
+ int16_t *value);
+/**
+ * @brief hl78xx_api_func_get_modem_info_vendor - Brief description of the function.
+ * @param dev Cellular network device instance
+ * @param type Type of the modem info to retrieve
+ * @param info Pointer to store the modem info
+ * @param size Size of the info buffer
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_get_modem_info_vendor(const struct device *dev,
+ enum hl78xx_modem_info_type type, void *info,
+ size_t size);
+/**
+ * @brief hl78xx_api_func_modem_dynamic_cmd_send - Brief description of the function.
+ * @param dev Cellular network device instance
+ * @param cmd AT command to send
+ * @param cmd_size Size of the AT command
+ * @param response_matches Expected response patterns
+ * @param matches_size Size of the response patterns
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_modem_dynamic_cmd_send(const struct device *dev, const char *cmd,
+ uint16_t cmd_size,
+ const struct modem_chat_match *response_matches,
+ uint16_t matches_size);
+/**
+ * @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, void *info,
+ size_t size)
+{
+ return hl78xx_api_func_get_modem_info_vendor(dev, type, info, 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)
+{
+ return hl78xx_api_func_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)
+{
+ return hl78xx_api_func_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)
+{
+
+ return hl78xx_api_func_modem_dynamic_cmd_send(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: <rssi>,<ber> 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 0–97, 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: <rxlev>,<ber>,<rscp>,<ecn0>,<rsrq>,<rsrp> 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_dispatcher_t handler);
+/**
+ * @brief Register an event monitor to receive HL78xx modem event notifications.
+ */
+int hl78xx_evt_monitor_register(struct hl78xx_evt_monitor_entry *mon);
+/**
+ * @brief Unregister an event monitor from receiving HL78xx modem event notifications.
+ */
+int hl78xx_evt_monitor_unregister(struct hl78xx_evt_monitor_entry *mon);
+/**
+ * @brief Convert HL78xx RAT mode to standard cellular API.
+ */
+enum cellular_access_technology hl78xx_rat_to_access_tech(enum hl78xx_cell_rat_mode rat_mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ */