diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-10 15:09:27 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-10 15:09:27 -0400 |
commit | 92589cbdda677a84ca5e485e1083c7d3bdcfc7b9 (patch) | |
tree | 3ba481707efe11a28d2cb27f8bacfde4224ad6cd /samples | |
parent | 9ab89c407d904c284558bbcd285eb3baef9d8c07 (diff) | |
parent | 730b2ad8f72898029160a6832141ba954122a0c8 (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/Kconfig | 10 | ||||
-rw-r--r-- | samples/Makefile | 2 | ||||
-rw-r--r-- | samples/qmi/Makefile | 1 | ||||
-rw-r--r-- | samples/qmi/qmi_sample_client.c | 622 |
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 | ||
65 | config 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 | |||
65 | config SAMPLE_RPMSG_CLIENT | 75 | config 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 @@ | |||
3 | obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ | 3 | obj-$(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 | |||
40 | struct test_name_type_v01 { | ||
41 | u32 name_len; | ||
42 | char name[TEST_MAX_NAME_SIZE_V01]; | ||
43 | }; | ||
44 | |||
45 | static 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 | |||
67 | struct 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 | |||
74 | static 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 | |||
106 | struct 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 | |||
116 | static 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 | |||
167 | struct 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 | |||
175 | static 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 | |||
216 | struct 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 | |||
227 | static 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 | */ | ||
301 | static 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 | |||
331 | static const struct file_operations ping_fops = { | ||
332 | .open = simple_open, | ||
333 | .write = ping_write, | ||
334 | }; | ||
335 | |||
336 | static 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 | */ | ||
368 | static 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 | |||
420 | out: | ||
421 | kfree(resp); | ||
422 | kfree(req); | ||
423 | |||
424 | return ret; | ||
425 | } | ||
426 | |||
427 | static const struct file_operations data_fops = { | ||
428 | .open = simple_open, | ||
429 | .write = data_write, | ||
430 | }; | ||
431 | |||
432 | static 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 | |||
443 | struct 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 | |||
451 | static struct dentry *qmi_debug_dir; | ||
452 | |||
453 | static 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 | |||
504 | err_remove_de_data: | ||
505 | debugfs_remove(sample->de_data); | ||
506 | err_remove_de_dir: | ||
507 | debugfs_remove(sample->de_dir); | ||
508 | err_release_qmi_handle: | ||
509 | qmi_handle_release(&sample->qmi); | ||
510 | |||
511 | return ret; | ||
512 | } | ||
513 | |||
514 | static 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 | |||
527 | static 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 | |||
535 | static 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 | |||
558 | err_put_device: | ||
559 | platform_device_put(pdev); | ||
560 | |||
561 | return ret; | ||
562 | } | ||
563 | |||
564 | static 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 | |||
572 | static struct qmi_handle lookup_client; | ||
573 | |||
574 | static struct qmi_ops lookup_ops = { | ||
575 | .new_server = qmi_sample_new_server, | ||
576 | .del_server = qmi_sample_del_server, | ||
577 | }; | ||
578 | |||
579 | static 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 | |||
601 | err_unregister_driver: | ||
602 | platform_driver_unregister(&qmi_sample_driver); | ||
603 | err_remove_debug_dir: | ||
604 | debugfs_remove(qmi_debug_dir); | ||
605 | |||
606 | return ret; | ||
607 | } | ||
608 | |||
609 | static 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 | |||
618 | module_init(qmi_sample_init); | ||
619 | module_exit(qmi_sample_exit); | ||
620 | |||
621 | MODULE_DESCRIPTION("Sample QMI client driver"); | ||
622 | MODULE_LICENSE("GPL v2"); | ||