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