Here is a bunch of trivia for Linux and sometimes for Jetsons…
Every Linux system (and indeed, even original *NIX) are based on C. In fact, the POSIX specification is more or less how the C language was created and introduced.
System calls are something users rarely ever need to consider, nor even most developers. A system call is just a C-based interface to the kernel itself. If you perform a command that is in user space and doesn’t do anything special, then system calls might not even be used. If the command in any way requires a driver, for example, reading from a disk, then a system call is performed, and the driver receives its instructions through that (after security). A corollary in user space is for calls to libraries.
You can install package ltrace (sudo apt-get install ltrace) on any Linux system to trace library calls. You can install package strace (sudo apt-get install strace) on any Linux system to trace system calls. If a program is statically linked, and thus does not use external libraries, then ltrace will not show anything; if a program links to a library, but the action does not use the library, then once again, ltrace will show nothing. Similar for strace, it will show nothing if no system call occurs, but this is exceedingly rare. Examples:
sudo strace ls
sudo ltrace ls
Drivers are more or less what controls all system calls (in layers). GPIO pins are controlled by drivers, and so a user space program which alters a GPIO pin should show something related to it via strace. However, Jetsons (and most embedded systems) have multiple possible uses for several pins, and this is normally provided setup via the device tree. Any device which can self-report is plug-n-play, and there is a hot plug layer (including udev) to deal with pairing a driver to the device, but non-plug-n-play devices (including GPIO) use a device tree for setup.
You can think of a device tree as a set of arguments passed to each driver as it loads. One of the
typical arguments passed is the physical address of the device, plus anything else the driver needs to be “generic” with respect to different hardware vendors. Normally you don’t need to change this on a Jetson, but if you want to permanently change a GPIO setup rather than at each boot, you might do so. There is some GPIO setup software in /opt to aid this, but a lot of people will download a spreadsheet known as the “PINMUX spreadsheet” for their particular Jetson model and L4T release (L4T is just what you call Ubuntu after it gets the NVIDIA drivers added; this is what gets flashed to a Jetson). The PINMUX spreadsheet has a listing of all of the configurable pin options on the Jetson, and if you enable macros and change something, e.g., you set a particular GPIO pin to become input or output, and then run the spreadsheet macro (requires enabling the macro), it will generate a compatible device tree. You can load that device tree onto the Jetson and it will simply be set up for GPIO the way you want. Until you do that it mostly cannot be compatible with RPi software.
It is unlikely you are going to want or need to write an actual driver. Most likely you will write code that uses the GPIO or other kernel features. Reading or writing GPIO is often as simple as a file read or write. There is a saying in all of *NIX that everything is a file. This is sort of true since drivers expose many features as device special files. Those files look and behave like a file, but they exist only in RAM, and are an extension to the driver. Reading or writing behavior is defined by the driver (plus possibly security). Most of what you see in these directories are pseudo files:
Remember the earlier mentioned device tree? You can see a reflection of the currently running kernel’s device tree by exploring “/proc/device-tree”. You can’t change anything there, it is a kernel driver showing you the loaded tree, but you can explore it and you can create an actual tree from this (the device tree compiler, or dtc, is available even though you might have to “sudo apt-get install device-tree-compiler”). Example to create a source code version of the device tree this way:
dtc -I fs -O dts -o extracted.dts /proc/device-tree
Another bit of interesting trivia: The kernel itself is the first program. The kernel has process ID 0. The first program the kernel runs (and in fact the only program directly started by the kernel) is init (which on Ubuntu is a symbolic link to “systemd”, a configurable init, but simple bash scripts could also be run…in the old days this is how it worked). All systemd is PID 1. Everything spawned by systemd ends up going through a “fork and exec” to create new processes, which are child PIDs of systemd. This inclu
des GUIs, login shells, so on. If you kill PID 1, the system will shut down or reboot. Similar for PID 0. User ID 0 is tied to user root, and it is root that can make such decisions.
bash scripts are very easy to get the hang of. They are human readable plain text. Many many examples exist on the web. Many people interact with GPIO via read or write of GPIO (files exposed by the driver), and do so in either bash or python, or C read/write.
When a driver has more function than just what a normal file operation can perform it uses the C function “ioctl”, short for “I/O Control”. This results in an indexed operation via system call to the driver, and each ioctl is custom to the driver. If the driver is set up, then probably you don’t need to call ioctl. An example of a common ioctl is if a program in any language were to change the speed or settings of a serial UART (you cannot write to a serial UART to change its speed, indeed, that would output the writ to the other side; an ioctl though can change the UART, and the details depend on the specific UART, including the driver, and perhaps brand of hardware underlying the driver).
If you have a brand new piece of hardware, and it plugs into the PCIe bus, or it is custom USB, then you might write an actual driver. This is done entirely in C.