summaryrefslogtreecommitdiffstats
path: root/samples
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-04-10 15:09:27 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-04-10 15:09:27 -0400
commit92589cbdda677a84ca5e485e1083c7d3bdcfc7b9 (patch)
tree3ba481707efe11a28d2cb27f8bacfde4224ad6cd /samples
parent9ab89c407d904c284558bbcd285eb3baef9d8c07 (diff)
parent730b2ad8f72898029160a6832141ba954122a0c8 (diff)
Merge tag 'rproc-v4.17' of git://github.com/andersson/remoteproc
Pull remoteproc updates from Bjorn Andersson: - add support for generating coredumps for remoteprocs using devcoredump - add the Qualcomm sysmon driver for intra-remoteproc crash handling - a number of fixes in Qualcomm and IMX drivers * tag 'rproc-v4.17' of git://github.com/andersson/remoteproc: remoteproc: fix null pointer dereference on glink only platforms soc: qcom: qmi: add CONFIG_NET dependency remoteproc: imx_rproc: Slightly simplify code in 'imx_rproc_probe()' remoteproc: imx_rproc: Re-use existing error handling path in 'imx_rproc_probe()' remoteproc: imx_rproc: Fix an error handling path in 'imx_rproc_probe()' samples: Introduce Qualcomm QMI sample client remoteproc: qcom: Introduce sysmon remoteproc: Pass type of shutdown to subdev remove remoteproc: qcom: Register segments for core dump soc: qcom: mdt-loader: Return relocation base remoteproc: Rename "load_rsc_table" to "parse_fw" remoteproc: Add remote processor coredump support remoteproc: Remove null character write of shared mem
Diffstat (limited to 'samples')
-rw-r--r--samples/Kconfig10
-rw-r--r--samples/Makefile2
-rw-r--r--samples/qmi/Makefile1
-rw-r--r--samples/qmi/qmi_sample_client.c622
4 files changed, 634 insertions, 1 deletions
diff --git a/samples/Kconfig b/samples/Kconfig
index f524f551718e..3db002b9e1d3 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -62,6 +62,16 @@ config SAMPLE_KDB
62 Build an example of how to dynamically add the hello 62 Build an example of how to dynamically add the hello
63 command to the kdb shell. 63 command to the kdb shell.
64 64
65config SAMPLE_QMI_CLIENT
66 tristate "Build qmi client sample -- loadable modules only"
67 depends on m
68 depends on ARCH_QCOM
69 depends on NET
70 select QCOM_QMI_HELPERS
71 help
72 Build an QMI client sample driver, which demonstrates how to
73 communicate with a remote QRTR service, using QMI encoded messages.
74
65config SAMPLE_RPMSG_CLIENT 75config SAMPLE_RPMSG_CLIENT
66 tristate "Build rpmsg client sample -- loadable modules only" 76 tristate "Build rpmsg client sample -- loadable modules only"
67 depends on RPMSG && m 77 depends on RPMSG && m
diff --git a/samples/Makefile b/samples/Makefile
index 70cf3758dcf2..bd601c038b86 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -3,4 +3,4 @@
3obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ 3obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \
4 hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \ 4 hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \
5 configfs/ connector/ v4l/ trace_printk/ \ 5 configfs/ connector/ v4l/ trace_printk/ \
6 vfio-mdev/ statx/ 6 vfio-mdev/ statx/ qmi/
diff --git a/samples/qmi/Makefile b/samples/qmi/Makefile
new file mode 100644
index 000000000000..2b111d2769df
--- /dev/null
+++ b/samples/qmi/Makefile
@@ -0,0 +1 @@
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi_sample_client.o
diff --git a/samples/qmi/qmi_sample_client.c b/samples/qmi/qmi_sample_client.c
new file mode 100644
index 000000000000..c9e7276c3d83
--- /dev/null
+++ b/samples/qmi/qmi_sample_client.c
@@ -0,0 +1,622 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Sample in-kernel QMI client driver
4 *
5 * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
6 * Copyright (C) 2017 Linaro Ltd.
7 */
8#include <linux/kernel.h>
9#include <linux/module.h>
10#include <linux/debugfs.h>
11#include <linux/device.h>
12#include <linux/platform_device.h>
13#include <linux/qrtr.h>
14#include <linux/net.h>
15#include <linux/completion.h>
16#include <linux/idr.h>
17#include <linux/string.h>
18#include <net/sock.h>
19#include <linux/soc/qcom/qmi.h>
20
21#define PING_REQ1_TLV_TYPE 0x1
22#define PING_RESP1_TLV_TYPE 0x2
23#define PING_OPT1_TLV_TYPE 0x10
24#define PING_OPT2_TLV_TYPE 0x11
25
26#define DATA_REQ1_TLV_TYPE 0x1
27#define DATA_RESP1_TLV_TYPE 0x2
28#define DATA_OPT1_TLV_TYPE 0x10
29#define DATA_OPT2_TLV_TYPE 0x11
30
31#define TEST_MED_DATA_SIZE_V01 8192
32#define TEST_MAX_NAME_SIZE_V01 255
33
34#define TEST_PING_REQ_MSG_ID_V01 0x20
35#define TEST_DATA_REQ_MSG_ID_V01 0x21
36
37#define TEST_PING_REQ_MAX_MSG_LEN_V01 266
38#define TEST_DATA_REQ_MAX_MSG_LEN_V01 8456
39
40struct test_name_type_v01 {
41 u32 name_len;
42 char name[TEST_MAX_NAME_SIZE_V01];
43};
44
45static struct qmi_elem_info test_name_type_v01_ei[] = {
46 {
47 .data_type = QMI_DATA_LEN,
48 .elem_len = 1,
49 .elem_size = sizeof(u8),
50 .array_type = NO_ARRAY,
51 .tlv_type = QMI_COMMON_TLV_TYPE,
52 .offset = offsetof(struct test_name_type_v01,
53 name_len),
54 },
55 {
56 .data_type = QMI_UNSIGNED_1_BYTE,
57 .elem_len = TEST_MAX_NAME_SIZE_V01,
58 .elem_size = sizeof(char),
59 .array_type = VAR_LEN_ARRAY,
60 .tlv_type = QMI_COMMON_TLV_TYPE,
61 .offset = offsetof(struct test_name_type_v01,
62 name),
63 },
64 {}
65};
66
67struct test_ping_req_msg_v01 {
68 char ping[4];
69
70 u8 client_name_valid;
71 struct test_name_type_v01 client_name;
72};
73
74static struct qmi_elem_info test_ping_req_msg_v01_ei[] = {
75 {
76 .data_type = QMI_UNSIGNED_1_BYTE,
77 .elem_len = 4,
78 .elem_size = sizeof(char),
79 .array_type = STATIC_ARRAY,
80 .tlv_type = PING_REQ1_TLV_TYPE,
81 .offset = offsetof(struct test_ping_req_msg_v01,
82 ping),
83 },
84 {
85 .data_type = QMI_OPT_FLAG,
86 .elem_len = 1,
87 .elem_size = sizeof(u8),
88 .array_type = NO_ARRAY,
89 .tlv_type = PING_OPT1_TLV_TYPE,
90 .offset = offsetof(struct test_ping_req_msg_v01,
91 client_name_valid),
92 },
93 {
94 .data_type = QMI_STRUCT,
95 .elem_len = 1,
96 .elem_size = sizeof(struct test_name_type_v01),
97 .array_type = NO_ARRAY,
98 .tlv_type = PING_OPT1_TLV_TYPE,
99 .offset = offsetof(struct test_ping_req_msg_v01,
100 client_name),
101 .ei_array = test_name_type_v01_ei,
102 },
103 {}
104};
105
106struct test_ping_resp_msg_v01 {
107 struct qmi_response_type_v01 resp;
108
109 u8 pong_valid;
110 char pong[4];
111
112 u8 service_name_valid;
113 struct test_name_type_v01 service_name;
114};
115
116static struct qmi_elem_info test_ping_resp_msg_v01_ei[] = {
117 {
118 .data_type = QMI_STRUCT,
119 .elem_len = 1,
120 .elem_size = sizeof(struct qmi_response_type_v01),
121 .array_type = NO_ARRAY,
122 .tlv_type = PING_RESP1_TLV_TYPE,
123 .offset = offsetof(struct test_ping_resp_msg_v01,
124 resp),
125 .ei_array = qmi_response_type_v01_ei,
126 },
127 {
128 .data_type = QMI_OPT_FLAG,
129 .elem_len = 1,
130 .elem_size = sizeof(u8),
131 .array_type = NO_ARRAY,
132 .tlv_type = PING_OPT1_TLV_TYPE,
133 .offset = offsetof(struct test_ping_resp_msg_v01,
134 pong_valid),
135 },
136 {
137 .data_type = QMI_UNSIGNED_1_BYTE,
138 .elem_len = 4,
139 .elem_size = sizeof(char),
140 .array_type = STATIC_ARRAY,
141 .tlv_type = PING_OPT1_TLV_TYPE,
142 .offset = offsetof(struct test_ping_resp_msg_v01,
143 pong),
144 },
145 {
146 .data_type = QMI_OPT_FLAG,
147 .elem_len = 1,
148 .elem_size = sizeof(u8),
149 .array_type = NO_ARRAY,
150 .tlv_type = PING_OPT2_TLV_TYPE,
151 .offset = offsetof(struct test_ping_resp_msg_v01,
152 service_name_valid),
153 },
154 {
155 .data_type = QMI_STRUCT,
156 .elem_len = 1,
157 .elem_size = sizeof(struct test_name_type_v01),
158 .array_type = NO_ARRAY,
159 .tlv_type = PING_OPT2_TLV_TYPE,
160 .offset = offsetof(struct test_ping_resp_msg_v01,
161 service_name),
162 .ei_array = test_name_type_v01_ei,
163 },
164 {}
165};
166
167struct test_data_req_msg_v01 {
168 u32 data_len;
169 u8 data[TEST_MED_DATA_SIZE_V01];
170
171 u8 client_name_valid;
172 struct test_name_type_v01 client_name;
173};
174
175static struct qmi_elem_info test_data_req_msg_v01_ei[] = {
176 {
177 .data_type = QMI_DATA_LEN,
178 .elem_len = 1,
179 .elem_size = sizeof(u32),
180 .array_type = NO_ARRAY,
181 .tlv_type = DATA_REQ1_TLV_TYPE,
182 .offset = offsetof(struct test_data_req_msg_v01,
183 data_len),
184 },
185 {
186 .data_type = QMI_UNSIGNED_1_BYTE,
187 .elem_len = TEST_MED_DATA_SIZE_V01,
188 .elem_size = sizeof(u8),
189 .array_type = VAR_LEN_ARRAY,
190 .tlv_type = DATA_REQ1_TLV_TYPE,
191 .offset = offsetof(struct test_data_req_msg_v01,
192 data),
193 },
194 {
195 .data_type = QMI_OPT_FLAG,
196 .elem_len = 1,
197 .elem_size = sizeof(u8),
198 .array_type = NO_ARRAY,
199 .tlv_type = DATA_OPT1_TLV_TYPE,
200 .offset = offsetof(struct test_data_req_msg_v01,
201 client_name_valid),
202 },
203 {
204 .data_type = QMI_STRUCT,
205 .elem_len = 1,
206 .elem_size = sizeof(struct test_name_type_v01),
207 .array_type = NO_ARRAY,
208 .tlv_type = DATA_OPT1_TLV_TYPE,
209 .offset = offsetof(struct test_data_req_msg_v01,
210 client_name),
211 .ei_array = test_name_type_v01_ei,
212 },
213 {}
214};
215
216struct test_data_resp_msg_v01 {
217 struct qmi_response_type_v01 resp;
218
219 u8 data_valid;
220 u32 data_len;
221 u8 data[TEST_MED_DATA_SIZE_V01];
222
223 u8 service_name_valid;
224 struct test_name_type_v01 service_name;
225};
226
227static struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
228 {
229 .data_type = QMI_STRUCT,
230 .elem_len = 1,
231 .elem_size = sizeof(struct qmi_response_type_v01),
232 .array_type = NO_ARRAY,
233 .tlv_type = DATA_RESP1_TLV_TYPE,
234 .offset = offsetof(struct test_data_resp_msg_v01,
235 resp),
236 .ei_array = qmi_response_type_v01_ei,
237 },
238 {
239 .data_type = QMI_OPT_FLAG,
240 .elem_len = 1,
241 .elem_size = sizeof(u8),
242 .array_type = NO_ARRAY,
243 .tlv_type = DATA_OPT1_TLV_TYPE,
244 .offset = offsetof(struct test_data_resp_msg_v01,
245 data_valid),
246 },
247 {
248 .data_type = QMI_DATA_LEN,
249 .elem_len = 1,
250 .elem_size = sizeof(u32),
251 .array_type = NO_ARRAY,
252 .tlv_type = DATA_OPT1_TLV_TYPE,
253 .offset = offsetof(struct test_data_resp_msg_v01,
254 data_len),
255 },
256 {
257 .data_type = QMI_UNSIGNED_1_BYTE,
258 .elem_len = TEST_MED_DATA_SIZE_V01,
259 .elem_size = sizeof(u8),
260 .array_type = VAR_LEN_ARRAY,
261 .tlv_type = DATA_OPT1_TLV_TYPE,
262 .offset = offsetof(struct test_data_resp_msg_v01,
263 data),
264 },
265 {
266 .data_type = QMI_OPT_FLAG,
267 .elem_len = 1,
268 .elem_size = sizeof(u8),
269 .array_type = NO_ARRAY,
270 .tlv_type = DATA_OPT2_TLV_TYPE,
271 .offset = offsetof(struct test_data_resp_msg_v01,
272 service_name_valid),
273 },
274 {
275 .data_type = QMI_STRUCT,
276 .elem_len = 1,
277 .elem_size = sizeof(struct test_name_type_v01),
278 .array_type = NO_ARRAY,
279 .tlv_type = DATA_OPT2_TLV_TYPE,
280 .offset = offsetof(struct test_data_resp_msg_v01,
281 service_name),
282 .ei_array = test_name_type_v01_ei,
283 },
284 {}
285};
286
287/*
288 * ping_write() - ping_pong debugfs file write handler
289 * @file: debugfs file context
290 * @user_buf: reference to the user data (ignored)
291 * @count: number of bytes in @user_buf
292 * @ppos: offset in @file to write
293 *
294 * This function allows user space to send out a ping_pong QMI encoded message
295 * to the associated remote test service and will return with the result of the
296 * transaction. It serves as an example of how to provide a custom response
297 * handler.
298 *
299 * Return: @count, or negative errno on failure.
300 */
301static ssize_t ping_write(struct file *file, const char __user *user_buf,
302 size_t count, loff_t *ppos)
303{
304 struct qmi_handle *qmi = file->private_data;
305 struct test_ping_req_msg_v01 req = {};
306 struct qmi_txn txn;
307 int ret;
308
309 memcpy(req.ping, "ping", sizeof(req.ping));
310
311 ret = qmi_txn_init(qmi, &txn, NULL, NULL);
312 if (ret < 0)
313 return ret;
314
315 ret = qmi_send_request(qmi, NULL, &txn,
316 TEST_PING_REQ_MSG_ID_V01,
317 TEST_PING_REQ_MAX_MSG_LEN_V01,
318 test_ping_req_msg_v01_ei, &req);
319 if (ret < 0) {
320 qmi_txn_cancel(&txn);
321 return ret;
322 }
323
324 ret = qmi_txn_wait(&txn, 5 * HZ);
325 if (ret < 0)
326 count = ret;
327
328 return count;
329}
330
331static const struct file_operations ping_fops = {
332 .open = simple_open,
333 .write = ping_write,
334};
335
336static void ping_pong_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
337 struct qmi_txn *txn, const void *data)
338{
339 const struct test_ping_resp_msg_v01 *resp = data;
340
341 if (!txn) {
342 pr_err("spurious ping response\n");
343 return;
344 }
345
346 if (resp->resp.result == QMI_RESULT_FAILURE_V01)
347 txn->result = -ENXIO;
348 else if (!resp->pong_valid || memcmp(resp->pong, "pong", 4))
349 txn->result = -EINVAL;
350
351 complete(&txn->completion);
352}
353
354/*
355 * data_write() - data debugfs file write handler
356 * @file: debugfs file context
357 * @user_buf: reference to the user data
358 * @count: number of bytes in @user_buf
359 * @ppos: offset in @file to write
360 *
361 * This function allows user space to send out a data QMI encoded message to
362 * the associated remote test service and will return with the result of the
363 * transaction. It serves as an example of how to have the QMI helpers decode a
364 * transaction response into a provided object automatically.
365 *
366 * Return: @count, or negative errno on failure.
367 */
368static ssize_t data_write(struct file *file, const char __user *user_buf,
369 size_t count, loff_t *ppos)
370
371{
372 struct qmi_handle *qmi = file->private_data;
373 struct test_data_resp_msg_v01 *resp;
374 struct test_data_req_msg_v01 *req;
375 struct qmi_txn txn;
376 int ret;
377
378 req = kzalloc(sizeof(*req), GFP_KERNEL);
379 if (!req)
380 return -ENOMEM;
381
382 resp = kzalloc(sizeof(*resp), GFP_KERNEL);
383 if (!resp) {
384 kfree(req);
385 return -ENOMEM;
386 }
387
388 req->data_len = min_t(size_t, sizeof(req->data), count);
389 if (copy_from_user(req->data, user_buf, req->data_len)) {
390 ret = -EFAULT;
391 goto out;
392 }
393
394 ret = qmi_txn_init(qmi, &txn, test_data_resp_msg_v01_ei, resp);
395 if (ret < 0)
396 goto out;
397
398 ret = qmi_send_request(qmi, NULL, &txn,
399 TEST_DATA_REQ_MSG_ID_V01,
400 TEST_DATA_REQ_MAX_MSG_LEN_V01,
401 test_data_req_msg_v01_ei, req);
402 if (ret < 0) {
403 qmi_txn_cancel(&txn);
404 goto out;
405 }
406
407 ret = qmi_txn_wait(&txn, 5 * HZ);
408 if (ret < 0) {
409 goto out;
410 } else if (!resp->data_valid ||
411 resp->data_len != req->data_len ||
412 memcmp(resp->data, req->data, req->data_len)) {
413 pr_err("response data doesn't match expectation\n");
414 ret = -EINVAL;
415 goto out;
416 }
417
418 ret = count;
419
420out:
421 kfree(resp);
422 kfree(req);
423
424 return ret;
425}
426
427static const struct file_operations data_fops = {
428 .open = simple_open,
429 .write = data_write,
430};
431
432static struct qmi_msg_handler qmi_sample_handlers[] = {
433 {
434 .type = QMI_RESPONSE,
435 .msg_id = TEST_PING_REQ_MSG_ID_V01,
436 .ei = test_ping_resp_msg_v01_ei,
437 .decoded_size = sizeof(struct test_ping_req_msg_v01),
438 .fn = ping_pong_cb
439 },
440 {}
441};
442
443struct qmi_sample {
444 struct qmi_handle qmi;
445
446 struct dentry *de_dir;
447 struct dentry *de_data;
448 struct dentry *de_ping;
449};
450
451static struct dentry *qmi_debug_dir;
452
453static int qmi_sample_probe(struct platform_device *pdev)
454{
455 struct sockaddr_qrtr *sq;
456 struct qmi_sample *sample;
457 char path[20];
458 int ret;
459
460 sample = devm_kzalloc(&pdev->dev, sizeof(*sample), GFP_KERNEL);
461 if (!sample)
462 return -ENOMEM;
463
464 ret = qmi_handle_init(&sample->qmi, TEST_DATA_REQ_MAX_MSG_LEN_V01,
465 NULL,
466 qmi_sample_handlers);
467 if (ret < 0)
468 return ret;
469
470 sq = dev_get_platdata(&pdev->dev);
471 ret = kernel_connect(sample->qmi.sock, (struct sockaddr *)sq,
472 sizeof(*sq), 0);
473 if (ret < 0) {
474 pr_err("failed to connect to remote service port\n");
475 goto err_release_qmi_handle;
476 }
477
478 snprintf(path, sizeof(path), "%d:%d", sq->sq_node, sq->sq_port);
479
480 sample->de_dir = debugfs_create_dir(path, qmi_debug_dir);
481 if (IS_ERR(sample->de_dir)) {
482 ret = PTR_ERR(sample->de_dir);
483 goto err_release_qmi_handle;
484 }
485
486 sample->de_data = debugfs_create_file("data", 0600, sample->de_dir,
487 sample, &data_fops);
488 if (IS_ERR(sample->de_data)) {
489 ret = PTR_ERR(sample->de_data);
490 goto err_remove_de_dir;
491 }
492
493 sample->de_ping = debugfs_create_file("ping", 0600, sample->de_dir,
494 sample, &ping_fops);
495 if (IS_ERR(sample->de_ping)) {
496 ret = PTR_ERR(sample->de_ping);
497 goto err_remove_de_data;
498 }
499
500 platform_set_drvdata(pdev, sample);
501
502 return 0;
503
504err_remove_de_data:
505 debugfs_remove(sample->de_data);
506err_remove_de_dir:
507 debugfs_remove(sample->de_dir);
508err_release_qmi_handle:
509 qmi_handle_release(&sample->qmi);
510
511 return ret;
512}
513
514static int qmi_sample_remove(struct platform_device *pdev)
515{
516 struct qmi_sample *sample = platform_get_drvdata(pdev);
517
518 debugfs_remove(sample->de_ping);
519 debugfs_remove(sample->de_data);
520 debugfs_remove(sample->de_dir);
521
522 qmi_handle_release(&sample->qmi);
523
524 return 0;
525}
526
527static struct platform_driver qmi_sample_driver = {
528 .probe = qmi_sample_probe,
529 .remove = qmi_sample_remove,
530 .driver = {
531 .name = "qmi_sample_client",
532 },
533};
534
535static int qmi_sample_new_server(struct qmi_handle *qmi,
536 struct qmi_service *service)
537{
538 struct platform_device *pdev;
539 struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port };
540 int ret;
541
542 pdev = platform_device_alloc("qmi_sample_client", PLATFORM_DEVID_AUTO);
543 if (!pdev)
544 return -ENOMEM;
545
546 ret = platform_device_add_data(pdev, &sq, sizeof(sq));
547 if (ret)
548 goto err_put_device;
549
550 ret = platform_device_add(pdev);
551 if (ret)
552 goto err_put_device;
553
554 service->priv = pdev;
555
556 return 0;
557
558err_put_device:
559 platform_device_put(pdev);
560
561 return ret;
562}
563
564static void qmi_sample_del_server(struct qmi_handle *qmi,
565 struct qmi_service *service)
566{
567 struct platform_device *pdev = service->priv;
568
569 platform_device_unregister(pdev);
570}
571
572static struct qmi_handle lookup_client;
573
574static struct qmi_ops lookup_ops = {
575 .new_server = qmi_sample_new_server,
576 .del_server = qmi_sample_del_server,
577};
578
579static int qmi_sample_init(void)
580{
581 int ret;
582
583 qmi_debug_dir = debugfs_create_dir("qmi_sample", NULL);
584 if (IS_ERR(qmi_debug_dir)) {
585 pr_err("failed to create qmi_sample dir\n");
586 return PTR_ERR(qmi_debug_dir);
587 }
588
589 ret = platform_driver_register(&qmi_sample_driver);
590 if (ret)
591 goto err_remove_debug_dir;
592
593 ret = qmi_handle_init(&lookup_client, 0, &lookup_ops, NULL);
594 if (ret < 0)
595 goto err_unregister_driver;
596
597 qmi_add_lookup(&lookup_client, 15, 0, 0);
598
599 return 0;
600
601err_unregister_driver:
602 platform_driver_unregister(&qmi_sample_driver);
603err_remove_debug_dir:
604 debugfs_remove(qmi_debug_dir);
605
606 return ret;
607}
608
609static void qmi_sample_exit(void)
610{
611 qmi_handle_release(&lookup_client);
612
613 platform_driver_unregister(&qmi_sample_driver);
614
615 debugfs_remove(qmi_debug_dir);
616}
617
618module_init(qmi_sample_init);
619module_exit(qmi_sample_exit);
620
621MODULE_DESCRIPTION("Sample QMI client driver");
622MODULE_LICENSE("GPL v2");