1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/ni_routes.c |
4 | * Route information for NI boards. |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License as published by |
11 | * the Free Software Foundation; either version 2 of the License, or |
12 | * (at your option) any later version. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | */ |
19 | |
20 | #include <linux/module.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/bsearch.h> |
23 | #include <linux/sort.h> |
24 | #include <linux/comedi.h> |
25 | |
26 | #include "ni_routes.h" |
27 | #include "ni_routing/ni_route_values.h" |
28 | #include "ni_routing/ni_device_routes.h" |
29 | |
30 | /* |
31 | * This is defined in ni_routing/ni_route_values.h: |
32 | * #define B(x) ((x) - NI_NAMES_BASE) |
33 | */ |
34 | |
35 | /* |
36 | * These are defined in ni_routing/ni_route_values.h to identify clearly |
37 | * elements of the table that were set. In other words, entries that are zero |
38 | * are invalid. To get the value to use for the register, one must mask out the |
39 | * high bit. |
40 | * |
41 | * #define V(x) ((x) | 0x80) |
42 | * |
43 | * #define UNMARK(x) ((x) & (~(0x80))) |
44 | * |
45 | */ |
46 | |
47 | /* Helper for accessing data. */ |
48 | #define RVi(table, src, dest) ((table)[(dest) * NI_NUM_NAMES + (src)]) |
49 | |
50 | /* |
51 | * Find the route values for a device family. |
52 | */ |
53 | static const u8 *ni_find_route_values(const char *device_family) |
54 | { |
55 | const u8 *rv = NULL; |
56 | int i; |
57 | |
58 | for (i = 0; ni_all_route_values[i]; ++i) { |
59 | if (!strcmp(ni_all_route_values[i]->family, device_family)) { |
60 | rv = &ni_all_route_values[i]->register_values[0][0]; |
61 | break; |
62 | } |
63 | } |
64 | return rv; |
65 | } |
66 | |
67 | /* |
68 | * Find the valid routes for a board. |
69 | */ |
70 | static const struct ni_device_routes * |
71 | ni_find_valid_routes(const char *board_name) |
72 | { |
73 | const struct ni_device_routes *dr = NULL; |
74 | int i; |
75 | |
76 | for (i = 0; ni_device_routes_list[i]; ++i) { |
77 | if (!strcmp(ni_device_routes_list[i]->device, board_name)) { |
78 | dr = ni_device_routes_list[i]; |
79 | break; |
80 | } |
81 | } |
82 | return dr; |
83 | } |
84 | |
85 | /* |
86 | * Find the proper route_values and ni_device_routes tables for this particular |
87 | * device. Possibly try an alternate board name if device routes not found |
88 | * for the actual board name. |
89 | * |
90 | * Return: -ENODATA if either was not found; 0 if both were found. |
91 | */ |
92 | static int ni_find_device_routes(const char *device_family, |
93 | const char *board_name, |
94 | const char *alt_board_name, |
95 | struct ni_route_tables *tables) |
96 | { |
97 | const struct ni_device_routes *dr; |
98 | const u8 *rv; |
99 | |
100 | /* First, find the register_values table for this device family */ |
101 | rv = ni_find_route_values(device_family); |
102 | |
103 | /* Second, find the set of routes valid for this device. */ |
104 | dr = ni_find_valid_routes(board_name); |
105 | if (!dr && alt_board_name) |
106 | dr = ni_find_valid_routes(board_name: alt_board_name); |
107 | |
108 | tables->route_values = rv; |
109 | tables->valid_routes = dr; |
110 | |
111 | if (!rv || !dr) |
112 | return -ENODATA; |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | /** |
118 | * ni_assign_device_routes() - Assign the proper lookup table for NI signal |
119 | * routing to the specified NI device. |
120 | * @device_family: Device family name (determines route values). |
121 | * @board_name: Board name (determines set of routes). |
122 | * @alt_board_name: Optional alternate board name to try on failure. |
123 | * @tables: Pointer to assigned routing information. |
124 | * |
125 | * Finds the route values for the device family and the set of valid routes |
126 | * for the board. If valid routes could not be found for the actual board |
127 | * name and an alternate board name has been specified, try that one. |
128 | * |
129 | * On failure, the assigned routing information may be partially filled |
130 | * (for example, with the route values but not the set of valid routes). |
131 | * |
132 | * Return: -ENODATA if assignment was not successful; 0 if successful. |
133 | */ |
134 | int ni_assign_device_routes(const char *device_family, |
135 | const char *board_name, |
136 | const char *alt_board_name, |
137 | struct ni_route_tables *tables) |
138 | { |
139 | memset(tables, 0, sizeof(struct ni_route_tables)); |
140 | return ni_find_device_routes(device_family, board_name, alt_board_name, |
141 | tables); |
142 | } |
143 | EXPORT_SYMBOL_GPL(ni_assign_device_routes); |
144 | |
145 | /** |
146 | * ni_count_valid_routes() - Count the number of valid routes. |
147 | * @tables: Routing tables for which to count all valid routes. |
148 | */ |
149 | unsigned int ni_count_valid_routes(const struct ni_route_tables *tables) |
150 | { |
151 | int total = 0; |
152 | int i; |
153 | |
154 | for (i = 0; i < tables->valid_routes->n_route_sets; ++i) { |
155 | const struct ni_route_set *R = &tables->valid_routes->routes[i]; |
156 | int j; |
157 | |
158 | for (j = 0; j < R->n_src; ++j) { |
159 | const int src = R->src[j]; |
160 | const int dest = R->dest; |
161 | const u8 *rv = tables->route_values; |
162 | |
163 | if (RVi(rv, B(src), B(dest))) |
164 | /* direct routing is valid */ |
165 | ++total; |
166 | else if (channel_is_rtsi(channel: dest) && |
167 | (RVi(rv, B(src), B(NI_RGOUT0)) || |
168 | RVi(rv, B(src), B(NI_RTSI_BRD(0))) || |
169 | RVi(rv, B(src), B(NI_RTSI_BRD(1))) || |
170 | RVi(rv, B(src), B(NI_RTSI_BRD(2))) || |
171 | RVi(rv, B(src), B(NI_RTSI_BRD(3))))) { |
172 | ++total; |
173 | } |
174 | } |
175 | } |
176 | return total; |
177 | } |
178 | EXPORT_SYMBOL_GPL(ni_count_valid_routes); |
179 | |
180 | /** |
181 | * ni_get_valid_routes() - Implements INSN_DEVICE_CONFIG_GET_ROUTES. |
182 | * @tables: pointer to relevant set of routing tables. |
183 | * @n_pairs: Number of pairs for which memory is allocated by the user. If |
184 | * the user specifies '0', only the number of available pairs is |
185 | * returned. |
186 | * @pair_data: Pointer to memory allocated to return pairs back to user. Each |
187 | * even, odd indexed member of this array will hold source, |
188 | * destination of a route pair respectively. |
189 | * |
190 | * Return: the number of valid routes if n_pairs == 0; otherwise, the number of |
191 | * valid routes copied. |
192 | */ |
193 | unsigned int ni_get_valid_routes(const struct ni_route_tables *tables, |
194 | unsigned int n_pairs, |
195 | unsigned int *pair_data) |
196 | { |
197 | unsigned int n_valid = ni_count_valid_routes(tables); |
198 | int i; |
199 | |
200 | if (n_pairs == 0 || n_valid == 0) |
201 | return n_valid; |
202 | |
203 | if (!pair_data) |
204 | return 0; |
205 | |
206 | n_valid = 0; |
207 | |
208 | for (i = 0; i < tables->valid_routes->n_route_sets; ++i) { |
209 | const struct ni_route_set *R = &tables->valid_routes->routes[i]; |
210 | int j; |
211 | |
212 | for (j = 0; j < R->n_src; ++j) { |
213 | const int src = R->src[j]; |
214 | const int dest = R->dest; |
215 | bool valid = false; |
216 | const u8 *rv = tables->route_values; |
217 | |
218 | if (RVi(rv, B(src), B(dest))) |
219 | /* direct routing is valid */ |
220 | valid = true; |
221 | else if (channel_is_rtsi(channel: dest) && |
222 | (RVi(rv, B(src), B(NI_RGOUT0)) || |
223 | RVi(rv, B(src), B(NI_RTSI_BRD(0))) || |
224 | RVi(rv, B(src), B(NI_RTSI_BRD(1))) || |
225 | RVi(rv, B(src), B(NI_RTSI_BRD(2))) || |
226 | RVi(rv, B(src), B(NI_RTSI_BRD(3))))) { |
227 | /* indirect routing also valid */ |
228 | valid = true; |
229 | } |
230 | |
231 | if (valid) { |
232 | pair_data[2 * n_valid] = src; |
233 | pair_data[2 * n_valid + 1] = dest; |
234 | ++n_valid; |
235 | } |
236 | |
237 | if (n_valid >= n_pairs) |
238 | return n_valid; |
239 | } |
240 | } |
241 | return n_valid; |
242 | } |
243 | EXPORT_SYMBOL_GPL(ni_get_valid_routes); |
244 | |
245 | /* |
246 | * List of NI global signal names that, as destinations, are only routeable |
247 | * indirectly through the *_arg elements of the comedi_cmd structure. |
248 | */ |
249 | static const int NI_CMD_DESTS[] = { |
250 | NI_AI_SampleClock, |
251 | NI_AI_StartTrigger, |
252 | NI_AI_ConvertClock, |
253 | NI_AO_SampleClock, |
254 | NI_AO_StartTrigger, |
255 | NI_DI_SampleClock, |
256 | NI_DO_SampleClock, |
257 | }; |
258 | |
259 | /** |
260 | * ni_is_cmd_dest() - Determine whether the given destination is only |
261 | * configurable via a comedi_cmd struct. |
262 | * @dest: Destination to test. |
263 | */ |
264 | bool ni_is_cmd_dest(int dest) |
265 | { |
266 | int i; |
267 | |
268 | for (i = 0; i < ARRAY_SIZE(NI_CMD_DESTS); ++i) |
269 | if (NI_CMD_DESTS[i] == dest) |
270 | return true; |
271 | return false; |
272 | } |
273 | EXPORT_SYMBOL_GPL(ni_is_cmd_dest); |
274 | |
275 | /* **** BEGIN Routes sort routines **** */ |
276 | static int _ni_sort_destcmp(const void *va, const void *vb) |
277 | { |
278 | const struct ni_route_set *a = va; |
279 | const struct ni_route_set *b = vb; |
280 | |
281 | if (a->dest < b->dest) |
282 | return -1; |
283 | else if (a->dest > b->dest) |
284 | return 1; |
285 | return 0; |
286 | } |
287 | |
288 | static int _ni_sort_srccmp(const void *vsrc0, const void *vsrc1) |
289 | { |
290 | const int *src0 = vsrc0; |
291 | const int *src1 = vsrc1; |
292 | |
293 | if (*src0 < *src1) |
294 | return -1; |
295 | else if (*src0 > *src1) |
296 | return 1; |
297 | return 0; |
298 | } |
299 | |
300 | /** |
301 | * ni_sort_device_routes() - Sort the list of valid device signal routes in |
302 | * preparation for use. |
303 | * @valid_routes: pointer to ni_device_routes struct to sort. |
304 | */ |
305 | void ni_sort_device_routes(struct ni_device_routes *valid_routes) |
306 | { |
307 | unsigned int n; |
308 | |
309 | /* 1. Count and set the number of ni_route_set objects. */ |
310 | valid_routes->n_route_sets = 0; |
311 | while (valid_routes->routes[valid_routes->n_route_sets].dest != 0) |
312 | ++valid_routes->n_route_sets; |
313 | |
314 | /* 2. sort all ni_route_set objects by destination. */ |
315 | sort(base: valid_routes->routes, num: valid_routes->n_route_sets, |
316 | size: sizeof(struct ni_route_set), cmp_func: _ni_sort_destcmp, NULL); |
317 | |
318 | /* 3. Loop through each route_set for sorting. */ |
319 | for (n = 0; n < valid_routes->n_route_sets; ++n) { |
320 | struct ni_route_set *rs = &valid_routes->routes[n]; |
321 | |
322 | /* 3a. Count and set the number of sources. */ |
323 | rs->n_src = 0; |
324 | while (rs->src[rs->n_src]) |
325 | ++rs->n_src; |
326 | |
327 | /* 3a. Sort sources. */ |
328 | sort(base: valid_routes->routes[n].src, num: valid_routes->routes[n].n_src, |
329 | size: sizeof(int), cmp_func: _ni_sort_srccmp, NULL); |
330 | } |
331 | } |
332 | EXPORT_SYMBOL_GPL(ni_sort_device_routes); |
333 | |
334 | /* sort all valid device signal routes in prep for use */ |
335 | static void ni_sort_all_device_routes(void) |
336 | { |
337 | unsigned int i; |
338 | |
339 | for (i = 0; ni_device_routes_list[i]; ++i) |
340 | ni_sort_device_routes(ni_device_routes_list[i]); |
341 | } |
342 | |
343 | /* **** BEGIN Routes search routines **** */ |
344 | static int _ni_bsearch_destcmp(const void *vkey, const void *velt) |
345 | { |
346 | const int *key = vkey; |
347 | const struct ni_route_set *elt = velt; |
348 | |
349 | if (*key < elt->dest) |
350 | return -1; |
351 | else if (*key > elt->dest) |
352 | return 1; |
353 | return 0; |
354 | } |
355 | |
356 | static int _ni_bsearch_srccmp(const void *vkey, const void *velt) |
357 | { |
358 | const int *key = vkey; |
359 | const int *elt = velt; |
360 | |
361 | if (*key < *elt) |
362 | return -1; |
363 | else if (*key > *elt) |
364 | return 1; |
365 | return 0; |
366 | } |
367 | |
368 | /** |
369 | * ni_find_route_set() - Finds the proper route set with the specified |
370 | * destination. |
371 | * @destination: Destination of which to search for the route set. |
372 | * @valid_routes: Pointer to device routes within which to search. |
373 | * |
374 | * Return: NULL if no route_set is found with the specified @destination; |
375 | * otherwise, a pointer to the route_set if found. |
376 | */ |
377 | const struct ni_route_set * |
378 | ni_find_route_set(const int destination, |
379 | const struct ni_device_routes *valid_routes) |
380 | { |
381 | return bsearch(key: &destination, base: valid_routes->routes, |
382 | num: valid_routes->n_route_sets, size: sizeof(struct ni_route_set), |
383 | cmp: _ni_bsearch_destcmp); |
384 | } |
385 | EXPORT_SYMBOL_GPL(ni_find_route_set); |
386 | |
387 | /* |
388 | * ni_route_set_has_source() - Determines whether the given source is in |
389 | * included given route_set. |
390 | * |
391 | * Return: true if found; false otherwise. |
392 | */ |
393 | bool ni_route_set_has_source(const struct ni_route_set *routes, |
394 | const int source) |
395 | { |
396 | if (!bsearch(key: &source, base: routes->src, num: routes->n_src, size: sizeof(int), |
397 | cmp: _ni_bsearch_srccmp)) |
398 | return false; |
399 | return true; |
400 | } |
401 | EXPORT_SYMBOL_GPL(ni_route_set_has_source); |
402 | |
403 | /** |
404 | * ni_lookup_route_register() - Look up a register value for a particular route |
405 | * without checking whether the route is valid for |
406 | * the particular device. |
407 | * @src: global-identifier for route source |
408 | * @dest: global-identifier for route destination |
409 | * @tables: pointer to relevant set of routing tables. |
410 | * |
411 | * Return: -EINVAL if the specified route is not valid for this device family. |
412 | */ |
413 | s8 ni_lookup_route_register(int src, int dest, |
414 | const struct ni_route_tables *tables) |
415 | { |
416 | s8 regval; |
417 | |
418 | /* |
419 | * Be sure to use the B() macro to subtract off the NI_NAMES_BASE before |
420 | * indexing into the route_values array. |
421 | */ |
422 | src = B(src); |
423 | dest = B(dest); |
424 | if (src < 0 || src >= NI_NUM_NAMES || dest < 0 || dest >= NI_NUM_NAMES) |
425 | return -EINVAL; |
426 | regval = RVi(tables->route_values, src, dest); |
427 | if (!regval) |
428 | return -EINVAL; |
429 | /* mask out the valid-value marking bit */ |
430 | return UNMARK(regval); |
431 | } |
432 | EXPORT_SYMBOL_GPL(ni_lookup_route_register); |
433 | |
434 | /** |
435 | * ni_route_to_register() - Validates and converts the specified signal route |
436 | * (src-->dest) to the value used at the appropriate |
437 | * register. |
438 | * @src: global-identifier for route source |
439 | * @dest: global-identifier for route destination |
440 | * @tables: pointer to relevant set of routing tables. |
441 | * |
442 | * Generally speaking, most routes require the first six bits and a few require |
443 | * 7 bits. Special handling is given for the return value when the route is to |
444 | * be handled by the RTSI sub-device. In this case, the returned register may |
445 | * not be sufficient to define the entire route path, but rather may only |
446 | * indicate the intermediate route. For example, if the route must go through |
447 | * the RGOUT0 pin, the (src->RGOUT0) register value will be returned. |
448 | * Similarly, if the route must go through the NI_RTSI_BRD lines, the BIT(6) |
449 | * will be set: |
450 | * |
451 | * if route does not need RTSI_BRD lines: |
452 | * bits 0:7 : register value |
453 | * for a route that must go through RGOUT0 pin, this will be equal |
454 | * to the (src->RGOUT0) register value. |
455 | * else: * route is (src->RTSI_BRD(x), RTSI_BRD(x)->TRIGGER_LINE(i)) * |
456 | * bits 0:5 : zero |
457 | * bits 6 : set to 1 |
458 | * bits 7:7 : zero |
459 | * |
460 | * Return: register value to be used for source at destination with special |
461 | * cases given above; Otherwise, -1 if the specified route is not valid for |
462 | * this particular device. |
463 | */ |
464 | s8 ni_route_to_register(const int src, const int dest, |
465 | const struct ni_route_tables *tables) |
466 | { |
467 | const struct ni_route_set *routes = |
468 | ni_find_route_set(dest, tables->valid_routes); |
469 | const u8 *rv; |
470 | s8 regval; |
471 | |
472 | /* first check to see if source is listed with bunch of destinations. */ |
473 | if (!routes) |
474 | return -1; |
475 | /* 2nd, check to see if destination is in list of source's targets. */ |
476 | if (!ni_route_set_has_source(routes, src)) |
477 | return -1; |
478 | /* |
479 | * finally, check to see if we know how to route... |
480 | * Be sure to use the B() macro to subtract off the NI_NAMES_BASE before |
481 | * indexing into the route_values array. |
482 | */ |
483 | rv = tables->route_values; |
484 | regval = RVi(rv, B(src), B(dest)); |
485 | |
486 | /* |
487 | * if we did not validate the route, we'll see if we can route through |
488 | * one of the muxes |
489 | */ |
490 | if (!regval && channel_is_rtsi(channel: dest)) { |
491 | regval = RVi(rv, B(src), B(NI_RGOUT0)); |
492 | if (!regval && (RVi(rv, B(src), B(NI_RTSI_BRD(0))) || |
493 | RVi(rv, B(src), B(NI_RTSI_BRD(1))) || |
494 | RVi(rv, B(src), B(NI_RTSI_BRD(2))) || |
495 | RVi(rv, B(src), B(NI_RTSI_BRD(3))))) |
496 | regval = BIT(6); |
497 | } |
498 | |
499 | if (!regval) |
500 | return -1; |
501 | /* mask out the valid-value marking bit */ |
502 | return UNMARK(regval); |
503 | } |
504 | EXPORT_SYMBOL_GPL(ni_route_to_register); |
505 | |
506 | /* |
507 | * ni_find_route_source() - Finds the signal source corresponding to a signal |
508 | * route (src-->dest) of the specified routing register |
509 | * value and the specified route destination on the |
510 | * specified device. |
511 | * |
512 | * Note that this function does _not_ validate the source based on device |
513 | * routes. |
514 | * |
515 | * Return: The NI signal value (e.g. NI_PFI(0) or PXI_Clk10) if found. |
516 | * If the source was not found (i.e. the register value is not |
517 | * valid for any routes to the destination), -EINVAL is returned. |
518 | */ |
519 | int ni_find_route_source(const u8 src_sel_reg_value, int dest, |
520 | const struct ni_route_tables *tables) |
521 | { |
522 | int src; |
523 | |
524 | if (!tables->route_values) |
525 | return -EINVAL; |
526 | |
527 | dest = B(dest); /* subtract NI names offset */ |
528 | /* ensure we are not going to under/over run the route value table */ |
529 | if (dest < 0 || dest >= NI_NUM_NAMES) |
530 | return -EINVAL; |
531 | for (src = 0; src < NI_NUM_NAMES; ++src) |
532 | if (RVi(tables->route_values, src, dest) == |
533 | V(src_sel_reg_value)) |
534 | return src + NI_NAMES_BASE; |
535 | return -EINVAL; |
536 | } |
537 | EXPORT_SYMBOL_GPL(ni_find_route_source); |
538 | |
539 | /* **** END Routes search routines **** */ |
540 | |
541 | /* **** BEGIN simple module entry/exit functions **** */ |
542 | static int __init ni_routes_module_init(void) |
543 | { |
544 | ni_sort_all_device_routes(); |
545 | return 0; |
546 | } |
547 | |
548 | static void __exit ni_routes_module_exit(void) |
549 | { |
550 | } |
551 | |
552 | module_init(ni_routes_module_init); |
553 | module_exit(ni_routes_module_exit); |
554 | |
555 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
556 | MODULE_DESCRIPTION("Comedi helper for routing signals-->terminals for NI" ); |
557 | MODULE_LICENSE("GPL" ); |
558 | /* **** END simple module entry/exit functions **** */ |
559 | |