1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | /* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved. |
4 | * Copyright (C) 2018-2022 Linaro Ltd. |
5 | */ |
6 | |
7 | #include <linux/types.h> |
8 | #include <linux/string.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/qrtr.h> |
11 | #include <linux/soc/qcom/qmi.h> |
12 | |
13 | #include "ipa.h" |
14 | #include "ipa_endpoint.h" |
15 | #include "ipa_mem.h" |
16 | #include "ipa_table.h" |
17 | #include "ipa_modem.h" |
18 | #include "ipa_qmi_msg.h" |
19 | |
20 | /** |
21 | * DOC: AP/Modem QMI Handshake |
22 | * |
23 | * The AP and modem perform a "handshake" at initialization time to ensure |
24 | * both sides know when everything is ready to begin operating. The AP |
25 | * driver (this code) uses two QMI handles (endpoints) for this; a client |
26 | * using a service on the modem, and server to service modem requests (and |
27 | * to supply an indication message from the AP). Once the handshake is |
28 | * complete, the AP and modem may begin IPA operation. This occurs |
29 | * only when the AP IPA driver, modem IPA driver, and IPA microcontroller |
30 | * are ready. |
31 | * |
32 | * The QMI service on the modem expects to receive an INIT_DRIVER request from |
33 | * the AP, which contains parameters used by the modem during initialization. |
34 | * The AP sends this request as soon as it is knows the modem side service |
35 | * is available. The modem responds to this request, and if this response |
36 | * contains a success result, the AP knows the modem IPA driver is ready. |
37 | * |
38 | * The modem is responsible for loading firmware on the IPA microcontroller. |
39 | * This occurs only during the initial modem boot. The modem sends a |
40 | * separate DRIVER_INIT_COMPLETE request to the AP to report that the |
41 | * microcontroller is ready. The AP may assume the microcontroller is |
42 | * ready and remain so (even if the modem reboots) once it has received |
43 | * and responded to this request. |
44 | * |
45 | * There is one final exchange involved in the handshake. It is required |
46 | * on the initial modem boot, but optional (but in practice does occur) on |
47 | * subsequent boots. The modem expects to receive a final INIT_COMPLETE |
48 | * indication message from the AP when it is about to begin its normal |
49 | * operation. The AP will only send this message after it has received |
50 | * and responded to an INDICATION_REGISTER request from the modem. |
51 | * |
52 | * So in summary: |
53 | * - Whenever the AP learns the modem has booted and its IPA QMI service |
54 | * is available, it sends an INIT_DRIVER request to the modem. The |
55 | * modem supplies a success response when it is ready to operate. |
56 | * - On the initial boot, the modem sets up the IPA microcontroller, and |
57 | * sends a DRIVER_INIT_COMPLETE request to the AP when this is done. |
58 | * - When the modem is ready to receive an INIT_COMPLETE indication from |
59 | * the AP, it sends an INDICATION_REGISTER request to the AP. |
60 | * - On the initial modem boot, everything is ready when: |
61 | * - AP has received a success response from its INIT_DRIVER request |
62 | * - AP has responded to a DRIVER_INIT_COMPLETE request |
63 | * - AP has responded to an INDICATION_REGISTER request from the modem |
64 | * - AP has sent an INIT_COMPLETE indication to the modem |
65 | * - On subsequent modem boots, everything is ready when: |
66 | * - AP has received a success response from its INIT_DRIVER request |
67 | * - AP has responded to a DRIVER_INIT_COMPLETE request |
68 | * - The INDICATION_REGISTER request and INIT_COMPLETE indication are |
69 | * optional for non-initial modem boots, and have no bearing on the |
70 | * determination of when things are "ready" |
71 | */ |
72 | |
73 | #define IPA_HOST_SERVICE_SVC_ID 0x31 |
74 | #define IPA_HOST_SVC_VERS 1 |
75 | #define IPA_HOST_SERVICE_INS_ID 1 |
76 | |
77 | #define IPA_MODEM_SERVICE_SVC_ID 0x31 |
78 | #define IPA_MODEM_SERVICE_INS_ID 2 |
79 | #define IPA_MODEM_SVC_VERS 1 |
80 | |
81 | #define QMI_INIT_DRIVER_TIMEOUT 60000 /* A minute in milliseconds */ |
82 | |
83 | /* Send an INIT_COMPLETE indication message to the modem */ |
84 | static void ipa_server_init_complete(struct ipa_qmi *ipa_qmi) |
85 | { |
86 | struct ipa *ipa = container_of(ipa_qmi, struct ipa, qmi); |
87 | struct qmi_handle *qmi = &ipa_qmi->server_handle; |
88 | struct sockaddr_qrtr *sq = &ipa_qmi->modem_sq; |
89 | struct ipa_init_complete_ind ind = { }; |
90 | int ret; |
91 | |
92 | ind.status.result = QMI_RESULT_SUCCESS_V01; |
93 | ind.status.error = QMI_ERR_NONE_V01; |
94 | |
95 | ret = qmi_send_indication(qmi, sq, IPA_QMI_INIT_COMPLETE, |
96 | IPA_QMI_INIT_COMPLETE_IND_SZ, |
97 | ei: ipa_init_complete_ind_ei, c_struct: &ind); |
98 | if (ret) |
99 | dev_err(ipa->dev, |
100 | "error %d sending init complete indication\n" , ret); |
101 | else |
102 | ipa_qmi->indication_sent = true; |
103 | } |
104 | |
105 | /* If requested (and not already sent) send the INIT_COMPLETE indication */ |
106 | static void ipa_qmi_indication(struct ipa_qmi *ipa_qmi) |
107 | { |
108 | if (!ipa_qmi->indication_requested) |
109 | return; |
110 | |
111 | if (ipa_qmi->indication_sent) |
112 | return; |
113 | |
114 | ipa_server_init_complete(ipa_qmi); |
115 | } |
116 | |
117 | /* Determine whether everything is ready to start normal operation. |
118 | * We know everything (else) is ready when we know the IPA driver on |
119 | * the modem is ready, and the microcontroller is ready. |
120 | * |
121 | * When the modem boots (or reboots), the handshake sequence starts |
122 | * with the AP sending the modem an INIT_DRIVER request. Within |
123 | * that request, the uc_loaded flag will be zero (false) for an |
124 | * initial boot, non-zero (true) for a subsequent (SSR) boot. |
125 | */ |
126 | static void ipa_qmi_ready(struct ipa_qmi *ipa_qmi) |
127 | { |
128 | struct ipa *ipa; |
129 | int ret; |
130 | |
131 | /* We aren't ready until the modem and microcontroller are */ |
132 | if (!ipa_qmi->modem_ready || !ipa_qmi->uc_ready) |
133 | return; |
134 | |
135 | /* Send the indication message if it was requested */ |
136 | ipa_qmi_indication(ipa_qmi); |
137 | |
138 | /* The initial boot requires us to send the indication. */ |
139 | if (ipa_qmi->initial_boot) { |
140 | if (!ipa_qmi->indication_sent) |
141 | return; |
142 | |
143 | /* The initial modem boot completed successfully */ |
144 | ipa_qmi->initial_boot = false; |
145 | } |
146 | |
147 | /* We're ready. Start up normal operation */ |
148 | ipa = container_of(ipa_qmi, struct ipa, qmi); |
149 | ret = ipa_modem_start(ipa); |
150 | if (ret) |
151 | dev_err(ipa->dev, "error %d starting modem\n" , ret); |
152 | } |
153 | |
154 | /* All QMI clients from the modem node are gone (modem shut down or crashed). */ |
155 | static void ipa_server_bye(struct qmi_handle *qmi, unsigned int node) |
156 | { |
157 | struct ipa_qmi *ipa_qmi; |
158 | |
159 | ipa_qmi = container_of(qmi, struct ipa_qmi, server_handle); |
160 | |
161 | /* The modem client and server go away at the same time */ |
162 | memset(&ipa_qmi->modem_sq, 0, sizeof(ipa_qmi->modem_sq)); |
163 | |
164 | /* initial_boot doesn't change when modem reboots */ |
165 | /* uc_ready doesn't change when modem reboots */ |
166 | ipa_qmi->modem_ready = false; |
167 | ipa_qmi->indication_requested = false; |
168 | ipa_qmi->indication_sent = false; |
169 | } |
170 | |
171 | static const struct qmi_ops ipa_server_ops = { |
172 | .bye = ipa_server_bye, |
173 | }; |
174 | |
175 | /* Callback function to handle an INDICATION_REGISTER request message from the |
176 | * modem. This informs the AP that the modem is now ready to receive the |
177 | * INIT_COMPLETE indication message. |
178 | */ |
179 | static void ipa_server_indication_register(struct qmi_handle *qmi, |
180 | struct sockaddr_qrtr *sq, |
181 | struct qmi_txn *txn, |
182 | const void *decoded) |
183 | { |
184 | struct ipa_indication_register_rsp rsp = { }; |
185 | struct ipa_qmi *ipa_qmi; |
186 | struct ipa *ipa; |
187 | int ret; |
188 | |
189 | ipa_qmi = container_of(qmi, struct ipa_qmi, server_handle); |
190 | ipa = container_of(ipa_qmi, struct ipa, qmi); |
191 | |
192 | rsp.rsp.result = QMI_RESULT_SUCCESS_V01; |
193 | rsp.rsp.error = QMI_ERR_NONE_V01; |
194 | |
195 | ret = qmi_send_response(qmi, sq, txn, IPA_QMI_INDICATION_REGISTER, |
196 | IPA_QMI_INDICATION_REGISTER_RSP_SZ, |
197 | ei: ipa_indication_register_rsp_ei, c_struct: &rsp); |
198 | if (!ret) { |
199 | ipa_qmi->indication_requested = true; |
200 | ipa_qmi_ready(ipa_qmi); /* We might be ready now */ |
201 | } else { |
202 | dev_err(ipa->dev, |
203 | "error %d sending register indication response\n" , ret); |
204 | } |
205 | } |
206 | |
207 | /* Respond to a DRIVER_INIT_COMPLETE request message from the modem. */ |
208 | static void ipa_server_driver_init_complete(struct qmi_handle *qmi, |
209 | struct sockaddr_qrtr *sq, |
210 | struct qmi_txn *txn, |
211 | const void *decoded) |
212 | { |
213 | struct ipa_driver_init_complete_rsp rsp = { }; |
214 | struct ipa_qmi *ipa_qmi; |
215 | struct ipa *ipa; |
216 | int ret; |
217 | |
218 | ipa_qmi = container_of(qmi, struct ipa_qmi, server_handle); |
219 | ipa = container_of(ipa_qmi, struct ipa, qmi); |
220 | |
221 | rsp.rsp.result = QMI_RESULT_SUCCESS_V01; |
222 | rsp.rsp.error = QMI_ERR_NONE_V01; |
223 | |
224 | ret = qmi_send_response(qmi, sq, txn, IPA_QMI_DRIVER_INIT_COMPLETE, |
225 | IPA_QMI_DRIVER_INIT_COMPLETE_RSP_SZ, |
226 | ei: ipa_driver_init_complete_rsp_ei, c_struct: &rsp); |
227 | if (!ret) { |
228 | ipa_qmi->uc_ready = true; |
229 | ipa_qmi_ready(ipa_qmi); /* We might be ready now */ |
230 | } else { |
231 | dev_err(ipa->dev, |
232 | "error %d sending init complete response\n" , ret); |
233 | } |
234 | } |
235 | |
236 | /* The server handles two request message types sent by the modem. */ |
237 | static const struct qmi_msg_handler ipa_server_msg_handlers[] = { |
238 | { |
239 | .type = QMI_REQUEST, |
240 | .msg_id = IPA_QMI_INDICATION_REGISTER, |
241 | .ei = ipa_indication_register_req_ei, |
242 | .decoded_size = IPA_QMI_INDICATION_REGISTER_REQ_SZ, |
243 | .fn = ipa_server_indication_register, |
244 | }, |
245 | { |
246 | .type = QMI_REQUEST, |
247 | .msg_id = IPA_QMI_DRIVER_INIT_COMPLETE, |
248 | .ei = ipa_driver_init_complete_req_ei, |
249 | .decoded_size = IPA_QMI_DRIVER_INIT_COMPLETE_REQ_SZ, |
250 | .fn = ipa_server_driver_init_complete, |
251 | }, |
252 | { }, |
253 | }; |
254 | |
255 | /* Handle an INIT_DRIVER response message from the modem. */ |
256 | static void ipa_client_init_driver(struct qmi_handle *qmi, |
257 | struct sockaddr_qrtr *sq, |
258 | struct qmi_txn *txn, const void *decoded) |
259 | { |
260 | txn->result = 0; /* IPA_QMI_INIT_DRIVER request was successful */ |
261 | complete(&txn->completion); |
262 | } |
263 | |
264 | /* The client handles one response message type sent by the modem. */ |
265 | static const struct qmi_msg_handler ipa_client_msg_handlers[] = { |
266 | { |
267 | .type = QMI_RESPONSE, |
268 | .msg_id = IPA_QMI_INIT_DRIVER, |
269 | .ei = ipa_init_modem_driver_rsp_ei, |
270 | .decoded_size = IPA_QMI_INIT_DRIVER_RSP_SZ, |
271 | .fn = ipa_client_init_driver, |
272 | }, |
273 | { }, |
274 | }; |
275 | |
276 | /* Return a pointer to an init modem driver request structure, which contains |
277 | * configuration parameters for the modem. The modem may be started multiple |
278 | * times, but generally these parameters don't change so we can reuse the |
279 | * request structure once it's initialized. The only exception is the |
280 | * skip_uc_load field, which will be set only after the microcontroller has |
281 | * reported it has completed its initialization. |
282 | */ |
283 | static const struct ipa_init_modem_driver_req * |
284 | init_modem_driver_req(struct ipa_qmi *ipa_qmi) |
285 | { |
286 | struct ipa *ipa = container_of(ipa_qmi, struct ipa, qmi); |
287 | u32 modem_route_count = ipa->modem_route_count; |
288 | static struct ipa_init_modem_driver_req req; |
289 | const struct ipa_mem *mem; |
290 | |
291 | /* The microcontroller is initialized on the first boot */ |
292 | req.skip_uc_load_valid = 1; |
293 | req.skip_uc_load = ipa->uc_loaded ? 1 : 0; |
294 | |
295 | /* We only have to initialize most of it once */ |
296 | if (req.platform_type_valid) |
297 | return &req; |
298 | |
299 | req.platform_type_valid = 1; |
300 | req.platform_type = IPA_QMI_PLATFORM_TYPE_MSM_ANDROID; |
301 | |
302 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_MODEM_HEADER); |
303 | if (mem->size) { |
304 | req.hdr_tbl_info_valid = 1; |
305 | req.hdr_tbl_info.start = ipa->mem_offset + mem->offset; |
306 | req.hdr_tbl_info.end = req.hdr_tbl_info.start + mem->size - 1; |
307 | } |
308 | |
309 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V4_ROUTE); |
310 | req.v4_route_tbl_info_valid = 1; |
311 | req.v4_route_tbl_info.start = ipa->mem_offset + mem->offset; |
312 | req.v4_route_tbl_info.end = modem_route_count - 1; |
313 | |
314 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V6_ROUTE); |
315 | req.v6_route_tbl_info_valid = 1; |
316 | req.v6_route_tbl_info.start = ipa->mem_offset + mem->offset; |
317 | req.v6_route_tbl_info.end = modem_route_count - 1; |
318 | |
319 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V4_FILTER); |
320 | req.v4_filter_tbl_start_valid = 1; |
321 | req.v4_filter_tbl_start = ipa->mem_offset + mem->offset; |
322 | |
323 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V6_FILTER); |
324 | req.v6_filter_tbl_start_valid = 1; |
325 | req.v6_filter_tbl_start = ipa->mem_offset + mem->offset; |
326 | |
327 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_MODEM); |
328 | if (mem->size) { |
329 | req.modem_mem_info_valid = 1; |
330 | req.modem_mem_info.start = ipa->mem_offset + mem->offset; |
331 | req.modem_mem_info.size = mem->size; |
332 | } |
333 | |
334 | req.ctrl_comm_dest_end_pt_valid = 1; |
335 | req.ctrl_comm_dest_end_pt = |
336 | ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]->endpoint_id; |
337 | |
338 | /* skip_uc_load_valid and skip_uc_load are set above */ |
339 | |
340 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_MODEM_PROC_CTX); |
341 | if (mem->size) { |
342 | req.hdr_proc_ctx_tbl_info_valid = 1; |
343 | req.hdr_proc_ctx_tbl_info.start = |
344 | ipa->mem_offset + mem->offset; |
345 | req.hdr_proc_ctx_tbl_info.end = |
346 | req.hdr_proc_ctx_tbl_info.start + mem->size - 1; |
347 | } |
348 | |
349 | /* Nothing to report for the compression table (zip_tbl_info) */ |
350 | |
351 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V4_ROUTE_HASHED); |
352 | if (mem->size) { |
353 | req.v4_hash_route_tbl_info_valid = 1; |
354 | req.v4_hash_route_tbl_info.start = |
355 | ipa->mem_offset + mem->offset; |
356 | req.v4_hash_route_tbl_info.end = modem_route_count - 1; |
357 | } |
358 | |
359 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V6_ROUTE_HASHED); |
360 | if (mem->size) { |
361 | req.v6_hash_route_tbl_info_valid = 1; |
362 | req.v6_hash_route_tbl_info.start = |
363 | ipa->mem_offset + mem->offset; |
364 | req.v6_hash_route_tbl_info.end = modem_route_count - 1; |
365 | } |
366 | |
367 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V4_FILTER_HASHED); |
368 | if (mem->size) { |
369 | req.v4_hash_filter_tbl_start_valid = 1; |
370 | req.v4_hash_filter_tbl_start = ipa->mem_offset + mem->offset; |
371 | } |
372 | |
373 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_V6_FILTER_HASHED); |
374 | if (mem->size) { |
375 | req.v6_hash_filter_tbl_start_valid = 1; |
376 | req.v6_hash_filter_tbl_start = ipa->mem_offset + mem->offset; |
377 | } |
378 | |
379 | /* The stats fields are only valid for IPA v4.0+ */ |
380 | if (ipa->version >= IPA_VERSION_4_0) { |
381 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_STATS_QUOTA_MODEM); |
382 | if (mem->size) { |
383 | req.hw_stats_quota_base_addr_valid = 1; |
384 | req.hw_stats_quota_base_addr = |
385 | ipa->mem_offset + mem->offset; |
386 | req.hw_stats_quota_size_valid = 1; |
387 | req.hw_stats_quota_size = ipa->mem_offset + mem->size; |
388 | } |
389 | |
390 | /* If the DROP stats region is defined, include it */ |
391 | mem = ipa_mem_find(ipa, mem_id: IPA_MEM_STATS_DROP); |
392 | if (mem && mem->size) { |
393 | req.hw_stats_drop_base_addr_valid = 1; |
394 | req.hw_stats_drop_base_addr = |
395 | ipa->mem_offset + mem->offset; |
396 | req.hw_stats_drop_size_valid = 1; |
397 | req.hw_stats_drop_size = ipa->mem_offset + mem->size; |
398 | } |
399 | } |
400 | |
401 | return &req; |
402 | } |
403 | |
404 | /* Send an INIT_DRIVER request to the modem, and wait for it to complete. */ |
405 | static void ipa_client_init_driver_work(struct work_struct *work) |
406 | { |
407 | unsigned long timeout = msecs_to_jiffies(QMI_INIT_DRIVER_TIMEOUT); |
408 | const struct ipa_init_modem_driver_req *req; |
409 | struct ipa_qmi *ipa_qmi; |
410 | struct qmi_handle *qmi; |
411 | struct qmi_txn txn; |
412 | struct device *dev; |
413 | struct ipa *ipa; |
414 | int ret; |
415 | |
416 | ipa_qmi = container_of(work, struct ipa_qmi, init_driver_work); |
417 | qmi = &ipa_qmi->client_handle; |
418 | |
419 | ipa = container_of(ipa_qmi, struct ipa, qmi); |
420 | dev = ipa->dev; |
421 | |
422 | ret = qmi_txn_init(qmi, txn: &txn, NULL, NULL); |
423 | if (ret < 0) { |
424 | dev_err(dev, "error %d preparing init driver request\n" , ret); |
425 | return; |
426 | } |
427 | |
428 | /* Send the request, and if successful wait for its response */ |
429 | req = init_modem_driver_req(ipa_qmi); |
430 | ret = qmi_send_request(qmi, sq: &ipa_qmi->modem_sq, txn: &txn, |
431 | IPA_QMI_INIT_DRIVER, IPA_QMI_INIT_DRIVER_REQ_SZ, |
432 | ei: ipa_init_modem_driver_req_ei, c_struct: req); |
433 | if (ret) |
434 | dev_err(dev, "error %d sending init driver request\n" , ret); |
435 | else if ((ret = qmi_txn_wait(txn: &txn, timeout))) |
436 | dev_err(dev, "error %d awaiting init driver response\n" , ret); |
437 | |
438 | if (!ret) { |
439 | ipa_qmi->modem_ready = true; |
440 | ipa_qmi_ready(ipa_qmi); /* We might be ready now */ |
441 | } else { |
442 | /* If any error occurs we need to cancel the transaction */ |
443 | qmi_txn_cancel(txn: &txn); |
444 | } |
445 | } |
446 | |
447 | /* The modem server is now available. We will send an INIT_DRIVER request |
448 | * to the modem, but can't wait for it to complete in this callback thread. |
449 | * Schedule a worker on the global workqueue to do that for us. |
450 | */ |
451 | static int |
452 | ipa_client_new_server(struct qmi_handle *qmi, struct qmi_service *svc) |
453 | { |
454 | struct ipa_qmi *ipa_qmi; |
455 | |
456 | ipa_qmi = container_of(qmi, struct ipa_qmi, client_handle); |
457 | |
458 | ipa_qmi->modem_sq.sq_family = AF_QIPCRTR; |
459 | ipa_qmi->modem_sq.sq_node = svc->node; |
460 | ipa_qmi->modem_sq.sq_port = svc->port; |
461 | |
462 | schedule_work(work: &ipa_qmi->init_driver_work); |
463 | |
464 | return 0; |
465 | } |
466 | |
467 | static const struct qmi_ops ipa_client_ops = { |
468 | .new_server = ipa_client_new_server, |
469 | }; |
470 | |
471 | /* Set up for QMI message exchange */ |
472 | int ipa_qmi_setup(struct ipa *ipa) |
473 | { |
474 | struct ipa_qmi *ipa_qmi = &ipa->qmi; |
475 | int ret; |
476 | |
477 | ipa_qmi->initial_boot = true; |
478 | |
479 | /* The server handle is used to handle the DRIVER_INIT_COMPLETE |
480 | * request on the first modem boot. It also receives the |
481 | * INDICATION_REGISTER request on the first boot and (optionally) |
482 | * subsequent boots. The INIT_COMPLETE indication message is |
483 | * sent over the server handle if requested. |
484 | */ |
485 | ret = qmi_handle_init(qmi: &ipa_qmi->server_handle, |
486 | IPA_QMI_SERVER_MAX_RCV_SZ, ops: &ipa_server_ops, |
487 | handlers: ipa_server_msg_handlers); |
488 | if (ret) |
489 | return ret; |
490 | |
491 | ret = qmi_add_server(qmi: &ipa_qmi->server_handle, IPA_HOST_SERVICE_SVC_ID, |
492 | IPA_HOST_SVC_VERS, IPA_HOST_SERVICE_INS_ID); |
493 | if (ret) |
494 | goto err_server_handle_release; |
495 | |
496 | /* The client handle is only used for sending an INIT_DRIVER request |
497 | * to the modem, and receiving its response message. |
498 | */ |
499 | ret = qmi_handle_init(qmi: &ipa_qmi->client_handle, |
500 | IPA_QMI_CLIENT_MAX_RCV_SZ, ops: &ipa_client_ops, |
501 | handlers: ipa_client_msg_handlers); |
502 | if (ret) |
503 | goto err_server_handle_release; |
504 | |
505 | /* We need this ready before the service lookup is added */ |
506 | INIT_WORK(&ipa_qmi->init_driver_work, ipa_client_init_driver_work); |
507 | |
508 | ret = qmi_add_lookup(qmi: &ipa_qmi->client_handle, IPA_MODEM_SERVICE_SVC_ID, |
509 | IPA_MODEM_SVC_VERS, IPA_MODEM_SERVICE_INS_ID); |
510 | if (ret) |
511 | goto err_client_handle_release; |
512 | |
513 | return 0; |
514 | |
515 | err_client_handle_release: |
516 | /* Releasing the handle also removes registered lookups */ |
517 | qmi_handle_release(qmi: &ipa_qmi->client_handle); |
518 | memset(&ipa_qmi->client_handle, 0, sizeof(ipa_qmi->client_handle)); |
519 | err_server_handle_release: |
520 | /* Releasing the handle also removes registered services */ |
521 | qmi_handle_release(qmi: &ipa_qmi->server_handle); |
522 | memset(&ipa_qmi->server_handle, 0, sizeof(ipa_qmi->server_handle)); |
523 | |
524 | return ret; |
525 | } |
526 | |
527 | /* Tear down IPA QMI handles */ |
528 | void ipa_qmi_teardown(struct ipa *ipa) |
529 | { |
530 | cancel_work_sync(work: &ipa->qmi.init_driver_work); |
531 | |
532 | qmi_handle_release(qmi: &ipa->qmi.client_handle); |
533 | memset(&ipa->qmi.client_handle, 0, sizeof(ipa->qmi.client_handle)); |
534 | |
535 | qmi_handle_release(qmi: &ipa->qmi.server_handle); |
536 | memset(&ipa->qmi.server_handle, 0, sizeof(ipa->qmi.server_handle)); |
537 | } |
538 | |