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