1 /*
2 * Copyright (c) 2024 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "nrfs_backend_ipc_service.h"
8
9 #include <internal/nrfs_backend.h>
10 #include <internal/nrfs_dispatcher.h>
11
12 #include <zephyr/kernel.h>
13 #include <zephyr/sys/reboot.h>
14 #include <zephyr/sys_clock.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/init.h>
17
18 LOG_MODULE_REGISTER(NRFS_BACKEND, CONFIG_NRFS_BACKEND_LOG_LEVEL);
19
20 #define MAX_PACKET_DATA_SIZE (CONFIG_NRFS_MAX_BACKEND_PACKET_SIZE)
21
22 K_MSGQ_DEFINE(ipc_transmit_msgq, sizeof(struct ipc_data_packet),
23 CONFIG_NRFS_BACKEND_TX_MSG_QUEUE_SIZE, 4);
24
25 static struct k_work backend_send_work;
26
27 static void ipc_sysctrl_ept_bound(void *priv);
28 static void ipc_sysctrl_ept_recv(const void *data, size_t size, void *priv);
29
30 static K_EVENT_DEFINE(ipc_connected_event);
31
32 #define IPC_INIT_DONE_EVENT (0x01)
33
34 struct ipc_channel_config {
35 const struct device *ipc_instance;
36 struct ipc_ept_cfg *endpoint_config;
37 struct ipc_ept ipc_ept;
38 atomic_t status;
39 bool enabled;
40 };
41
42 static struct ipc_ept_cfg ipc_sysctrl_ept_cfg = {
43 .name = "ipc_to_sysctrl",
44 .cb = {
45 .bound = ipc_sysctrl_ept_bound,
46 .received = ipc_sysctrl_ept_recv,
47 },
48 };
49
50 static struct ipc_channel_config ipc_cpusys_channel_config = {
51 .ipc_instance = DEVICE_DT_GET(DT_ALIAS(ipc_to_cpusys)),
52 .endpoint_config = &ipc_sysctrl_ept_cfg,
53 .status = ATOMIC_INIT(NOT_CONNECTED),
54 .enabled = true
55 };
56
57 static sys_slist_t nrfs_backend_info_cb_slist = SYS_SLIST_STATIC_INIT(&nrfs_backend_info_cb_slist);
58
59 /**
60 * @brief nrfs backend error handler
61 *
62 * @param error_id The id of an error to handle.
63 * @param error additional error code if needed, if not needed use 0.
64 * @param fatal true if fatal error and needs special handling
65 */
nrfs_backend_error_handler(enum nrfs_backend_error error_id,int error,bool fatal)66 __weak void nrfs_backend_error_handler(enum nrfs_backend_error error_id, int error, bool fatal)
67 {
68 switch (error_id) {
69 case NRFS_ERROR_EPT_RECEIVE_DATA_TOO_LONG:
70 LOG_ERR("Received data is too long. Config error.");
71 break;
72
73 case NRFS_ERROR_NO_DATA_RECEIVED:
74 LOG_ERR("No data in received message!");
75 break;
76
77 case NRFS_ERROR_IPC_OPEN_INSTANCE:
78 LOG_ERR("IPC open instance failure with error: %d", error);
79 break;
80
81 case NRFS_ERROR_IPC_REGISTER_ENDPOINT:
82 LOG_ERR("IPC register endpoint failure with error: %d", error);
83 break;
84
85 case NRFS_ERROR_SEND_DATA_FROM_QUEUE:
86 LOG_ERR("IPC backend sent data failed.");
87 break;
88
89 default:
90 LOG_ERR("Undefined error id: %d, error cause: %d", error_id, error);
91 break;
92 }
93
94 if (fatal) {
95 nrfs_backend_fatal_error_handler(error_id);
96 }
97 }
98
ipc_sysctrl_ept_bound(void * priv)99 static void ipc_sysctrl_ept_bound(void *priv)
100 {
101 LOG_DBG("Bound to sysctrl.");
102 k_event_post(&ipc_connected_event, IPC_INIT_DONE_EVENT);
103 atomic_set(&ipc_cpusys_channel_config.status, CONNECTED);
104
105 if (k_msgq_num_used_get(&ipc_transmit_msgq) > 0) {
106 k_work_submit(&backend_send_work);
107 }
108
109 struct nrfs_backend_bound_info_subs *subs;
110
111 SYS_SLIST_FOR_EACH_CONTAINER(&nrfs_backend_info_cb_slist, subs, node) {
112 subs->cb();
113 }
114 }
115
ipc_sysctrl_ept_recv(const void * data,size_t size,void * priv)116 static void ipc_sysctrl_ept_recv(const void *data, size_t size, void *priv)
117 {
118 struct ipc_data_packet rx_data;
119
120 __ASSERT(size <= MAX_PACKET_DATA_SIZE, "Received data is too long. Config error.");
121 if (size <= MAX_PACKET_DATA_SIZE) {
122 rx_data.channel_id = IPC_CPUSYS_CHANNEL_ID;
123 rx_data.size = size;
124 if (data) {
125 memcpy(rx_data.data, (uint8_t *)data, size);
126 nrfs_dispatcher_notify(&rx_data.data, rx_data.size);
127 } else {
128 nrfs_backend_error_handler(NRFS_ERROR_NO_DATA_RECEIVED, 0, false);
129 }
130 } else {
131 nrfs_backend_error_handler(NRFS_ERROR_EPT_RECEIVE_DATA_TOO_LONG, 0, true);
132 }
133 }
134
135 /**
136 * @brief This function will try to send data directly using ipc service
137 * In case of errors it will retry if configured.
138 *
139 * @param message Pointer to the buffer to send.
140 * @param size Number of bytes to send.
141 * @retval NRFS_SUCCESS Message sent successfully.
142 * @retval NRFS_ERR_IPC Backend returned error during message sending.
143 */
nrfs_backend_try_send_directly_over_ipc_service(void * message,size_t size)144 static nrfs_err_t nrfs_backend_try_send_directly_over_ipc_service(void *message, size_t size)
145 {
146 size_t retry_count = CONFIG_NRFS_SEND_RETRY_MAX_COUNT + 1;
147 int ret = 0;
148
149 do {
150 ret = ipc_service_send(&ipc_cpusys_channel_config.ipc_ept, message, size);
151 if (ret < (int)size) {
152 k_usleep(CONFIG_NRFS_SEND_RETRY_DELAY);
153 } else {
154 return NRFS_SUCCESS;
155 }
156 } while (--retry_count);
157
158 return NRFS_ERR_IPC;
159 }
160
nrfs_backend_send_work(struct k_work * item)161 static void nrfs_backend_send_work(struct k_work *item)
162 {
163 struct ipc_data_packet data_to_send;
164
165 LOG_DBG("Sending data from workqueue");
166 while (k_msgq_get(&ipc_transmit_msgq, &data_to_send, K_NO_WAIT) == 0) {
167
168 nrfs_err_t ret = nrfs_backend_try_send_directly_over_ipc_service(&data_to_send.data,
169 data_to_send.size);
170
171 if (ret != NRFS_SUCCESS) {
172 nrfs_backend_error_handler(NRFS_ERROR_SEND_DATA_FROM_QUEUE, 0, true);
173 }
174 }
175 }
176
177 /**
178 * @brief Initialize ipc channel
179 *
180 * @return -EINVAL when instance configuration is invalid.
181 * @return -EIO when no backend is registered.
182 * @return -EALREADY when the instance is already opened (or being opened).
183 * @return -EBUSY when the instance is busy.
184 * @return 0 on success
185 */
ipc_channel_init(void)186 static int ipc_channel_init(void)
187 {
188 struct ipc_channel_config *ch_cfg;
189 int ret = 0;
190
191 k_work_init(&backend_send_work, nrfs_backend_send_work);
192 ch_cfg = &ipc_cpusys_channel_config;
193
194 ret = ipc_service_open_instance(ch_cfg->ipc_instance);
195 if ((ret < 0) && (ret != -EALREADY)) {
196 nrfs_backend_error_handler(NRFS_ERROR_IPC_OPEN_INSTANCE, ret, false);
197 return ret;
198 }
199
200 LOG_DBG("ipc_service_open_instance() done.");
201
202 ret = ipc_service_register_endpoint(ch_cfg->ipc_instance,
203 &ch_cfg->ipc_ept,
204 ch_cfg->endpoint_config);
205 if (ret < 0) {
206 nrfs_backend_error_handler(NRFS_ERROR_IPC_REGISTER_ENDPOINT, ret, false);
207 return ret;
208 }
209
210 LOG_DBG("ipc_service_register_endpoint() done.");
211
212 return ret;
213 }
214
nrfs_backend_send(void * message,size_t size)215 nrfs_err_t nrfs_backend_send(void *message, size_t size)
216 {
217 return nrfs_backend_send_ex(message, size, K_NO_WAIT, false);
218 }
219
nrfs_backend_send_ex(void * message,size_t size,k_timeout_t timeout,bool high_prio)220 nrfs_err_t nrfs_backend_send_ex(void *message, size_t size, k_timeout_t timeout, bool high_prio)
221 {
222 if (!k_is_in_isr() && nrfs_backend_connected()) {
223 return nrfs_backend_try_send_directly_over_ipc_service(message, size);
224
225 } else if (size <= MAX_PACKET_DATA_SIZE) {
226 int err;
227 struct ipc_data_packet tx_data;
228
229 tx_data.channel_id = IPC_CPUSYS_CHANNEL_ID;
230 tx_data.size = size;
231 memcpy(tx_data.data, (uint8_t *)message, size);
232
233 err = k_msgq_put(&ipc_transmit_msgq, &tx_data, timeout);
234 if (err) {
235 return NRFS_ERR_IPC;
236 }
237
238 if (nrfs_backend_connected()) {
239 err = k_work_submit(&backend_send_work);
240 }
241
242 return err >= 0 ? 0 : NRFS_ERR_IPC;
243 }
244
245 LOG_ERR("Trying to send %d bytes where max is %d.", size, MAX_PACKET_DATA_SIZE);
246
247 return NRFS_ERR_IPC;
248 }
249
nrfs_backend_connected(void)250 bool nrfs_backend_connected(void)
251 {
252 return atomic_get(&ipc_cpusys_channel_config.status) == CONNECTED;
253 }
254
nrfs_backend_wait_for_connection(k_timeout_t timeout)255 int nrfs_backend_wait_for_connection(k_timeout_t timeout)
256 {
257 uint32_t events;
258
259 if (nrfs_backend_connected()) {
260 return 0;
261 }
262
263 events = k_event_wait(&ipc_connected_event, IPC_INIT_DONE_EVENT, false, timeout);
264
265 return (events == IPC_INIT_DONE_EVENT ? 0 : (-EAGAIN));
266 }
267
nrfs_backend_register_bound_subscribe(struct nrfs_backend_bound_info_subs * subs,nrfs_backend_bound_info_cb_t cb)268 void nrfs_backend_register_bound_subscribe(struct nrfs_backend_bound_info_subs *subs,
269 nrfs_backend_bound_info_cb_t cb)
270 {
271 if (cb) {
272 subs->cb = cb;
273 sys_slist_append(&nrfs_backend_info_cb_slist, &subs->node);
274 }
275 }
276
nrfs_backend_fatal_error_handler(enum nrfs_backend_error error_id)277 __weak void nrfs_backend_fatal_error_handler(enum nrfs_backend_error error_id)
278 {
279 LOG_ERR("Fatal error: %d rebooting...", error_id);
280 sys_reboot(SYS_REBOOT_WARM);
281 }
282
283 SYS_INIT(ipc_channel_init, POST_KERNEL, CONFIG_NRFS_BACKEND_IPC_SERVICE_INIT_PRIO);
284