This post contains some transcribed notes from my PhD on installing Xenomai 2 with Ubuntu to our robot control computer. The installation was done on a Dell Optiplex 990 desktop having 16GB of RAM and an Intel Core i7-2600 CPU @ 3.40GHz x 8 cores, as well as two 128GB Samsung SSD’s. This machine was responsible for communication via Ethernet (UDP) with processors onboard the robot which each control/report sensor data from individual joints. The robot also had one or more Microstrain IMUs which were used for state estimation and report accelerometer/gyroscope data at a high rate (1000Hz) via USB.
These instructions are out of date at this point since Xenomai 3 has been released, but I figured maybe someone will stumble across this and find it somewhat useful especially for installing on older systems. The notes also walk through installing RTnet for real-time Ethernet networking and usb4rt for real-time USB 1.1 communication, as well as real-time serial and some other machine setup.
First, install Ubuntu 12.04 following the usual install instructions with manual partitioning. For the Ubuntu installation, 100GB of the first drive was dedicated to the operating system (mount point /) and the rest was left as swap space. On the second drive, 75GB was dedicated to user home directories and the rest was left to backup space.
As root download Xenomai (as a Debian package) and the Linux Kernel (I used a vanilla kernel and put all the sources in /usr/src). For details on debian packages, see this page.
cd /usr/src
# download xenomai
wget -O - http://download.gna.org/xenomai/stable/xenomai-2.6.3.tar.bz2 | tar -jxf -
cd xenomai-2.6.3
# create a new debian changelog entry and build the packages in the parent directory
DEBEMAIL="your@email" DEBFULLNAME="Your Name" debchange -v 2.6.3 Release 2.6.3
debuild -uc -us
# install the resulting packages
cd ..
dpkg -i libxenomai1_2.6.3_amd64.deb libxenomai-dev_2.6.3_amd64.deb xenomai-doc_2.6.3_all.deb xenomai-kernel-source_2.6.3_all.deb xenomai-runtime_2.6.3_amd64.deb
The packages contain, respectively:
The list of Xenomai-supported Linux kernels can be found in /usr/src/xenomai-kernel-source/ksrc/arch/x86/patches/
- here, we use kernel version 3.8.13 from kernel.org. Download and unzip this kernel and then patch it with xenomai as shown below.
Note that for this version we needed to apply a patch to the Xenomai source code to fix a latency issue created by running
xeno latency
in parallel withwatch /proc/xenomai/stat
- see this thread for more details. Maybe not relevant for other versions, omitted from instructions.
# first we download the linux kernel
cd /usr/src
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.8.13.tar.bz2
tar -jxf linux-3.8.13.tar.bz2
# create symbolic links
ln -s /usr/src/linux-3.8.13 linux
ln -s /usr/src/xenomai-2.6.3 xenomai
# finally, patch the linux kernel for xenomai (making sure that the adeos patch specified matches the linux kernel version)
/usr/src/xenomai/scripts/prepare-kernel.sh --linux=/usr/src/linux --ipipe=/usr/src/xenomai/ksrc/arch/x86/patches/ipipe-core-3.8.13-x86-4.patch
You can use my kernel config if you want - copy it to /usr/src/linux/.config
and then configure the kernel by doing the following:
cd /usr/src/linux
# copy the old kernel config file (optional)
mv ~/Downloads/config_kernel_3.8.13_xeno_2.6.3 /usr/src/linux/.config
# run the kernel config GUI and double-check options below
make menuconfig
If you copied my config file then all the options suggested below should already be set - you should double-check them to be sure. In the menuconfig GUI, scrolling to the option and hitting keys Y, N, or M will Enable, Disable, or Enable as a kernel module that option, respectively. If there is no asterisk in the brackets, the option is already disabled. Brackets like [] mean the option can be enabled with Y, while brackets like <> mean it can either be enabled with Y or enabled to be built as a kernel module with M.
A Note on Kernel Modules
When the kernel is compiled, modules (sometimes also called “drivers”) are either built as loadable (=m) or built-in (=y) and are marked as such in the kernel config file
/usr/src/KERNELNAME/.config
. While loadable modules can be unloaded and even blacklisted at boot time by editing/etc/modprobe.d/blacklist
, there is no simple way to unload a built-in module without first recompiling the kernel to make it loadable. This means that anything you might wish to load/unload on the fly should be marked with (=m).While
modprobe -l
lists all loadable modules, you can check the file/lib/modules/KERNELNAME/modules.builtin
to find built-in modules. You can get information about a module, including its location, usingmodinfo MODULENAME
. You can check if particular modules are loaded withlsmod
and load/unload them withinsmod
/rmmod
or usingmodprobe
. Note that all modules are actually installed in/lib/modules/KERNELNAME/
and modules can be loaded at boot time by adding them to/etc/modules
.
The kernel configuration options to check are listed below. You can always edit the .config
directly if you mess up in the menuconfig GUI, and see this page for more configuration details.
.config
file after being done with menuconfig because I couldn’t find the option)These and more Xenomai installation details can be found in the official Xenomai install guide and this install configuration page.
Once configuration is done, you can exit the GUI and save the configuration file and compile the kernel as follows:
# compile the kernel (Concurrency level is the number of cores - do not use make -j)
CONCURRENCY_LEVEL=8 CLEAN_SOURCE=no fakeroot make-kpkg --initrd --append-to-version -ipipe-xenomai-2.6.3 --revision 1.0 kernel_image kernel_headers
This may take some time (on the order of tens of minutes). Once compilation completes, in /usr/src
you should have two new debian packages: linux-image-3.8.13-ipipe-xenomai-2.6.3_1.0_amd64.deb
and linux-headers-3.8.13-ipipe-xenomai-2.6.3_1.0_amd64.deb
corresponding the kernel source and headers, respectively.
To access RTOS-specific terminal commands as a non-root user, add your user to the xenomai group by issuing:
usermod -a -G xenomai YOURUSERNAME
and then modify the grub config which determines bootloader options at /etc/default/grub
to look as follows:
# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
# info -f grub -n 'Simple configuration'
GRUB_DEFAULT="2>0"
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash xeno_nucleus.xenomai_gid=125"
GRUB_CMDLINE_LINUX=""
# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"
# Uncomment to disable graphical terminal (grub-pc only)
#GRUB_TERMINAL=console
# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480
# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true
# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"
# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"
The necessary changes are:
Save /etc/default/grub
, then run
update-grub
to update the bootloader settings. The next time you reboot, the machine should boot into the Xenomai kernel instead of the existing linux kernel - if not, you’ll need to modify the GRUB_DEFAULT="2>0"
line in the bootloader config.
Finally, you must install the kernel with:
cd /usr/src/
dpkg -i linux-image-3.8.13-ipipe-xenomai-2.6.3_1.0_amd64.deb
To test the installation was successful, reboot the machine and select the appropriate kernel with grub. After booting a correctly configured kernel, run dmesg | grep Xenomai
and you should see log entries similar these:
[ 2.023815] I-pipe: head domain Xenomai registered.
[ 2.023890] Xenomai: hal/x86_64 started.
[ 2.023912] Xenomai: scheduling class idle registered.
[ 2.023913] Xenomai: scheduling class rt registered.
[ 2.024856] Xenomai: real-time nucleus v2.6.3 (Lies and Truths) loaded.
[ 2.024857] Xenomai: debug mode enabled.
[ 2.025042] Xenomai: SMI-enabled chipset found, but SMI workaround disabled
[ 2.025116] Xenomai: starting native API services.
[ 2.025117] Xenomai: starting POSIX services.
[ 2.025130] Xenomai: starting RTDM services.
If you do not, you may have accidentally booted into the wrong kernel; run uname -r
to check the currently booted kernel. If you have any issues with the Xenomai installation at this point, consult the official troubleshooting guide.
Note that if you wish to recompile the kernel in the future - for example, with different config parameters - you can use
dpkg --list | grep linux-image
to print which kernels have been installed. Then, use dpkg --purge KERNELNAME
to remove the old kernel and proceed with the kernel config step for the new kernel. For example, the remove the kernel we’re just installed, one would use dpkg --purge linux-image-3.8.13-ipipe-xenomai-2.6.3
.
It’s important to verify that the RTOS has consistently small latencies for a periodic task, since this is the reason we choose to use it for robot control. Assuming you’ve added your user to the Xenomai group as above, run xeno latency
to run a simple built-in latency test. Since graphics settings were seen to be related to latency issues, play a bit with the graphics by opening/closing/moving around windows while the test runs. You should see results like:
== Sampling period: 100 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT| 00:00:01 (periodic user-mode task, 100 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD| 1.615| 1.923| 9.846| 0| 0| 1.615| 9.846
RTD| 1.615| 1.923| 9.692| 0| 0| 1.615| 9.846
RTD| 1.538| 1.923| 10.230| 0| 0| 1.538| 10.230
RTD| 1.615| 1.923| 10.384| 0| 0| 1.538| 10.384
RTD| 1.615| 1.923| 11.230| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 9.923| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 9.923| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 11.076| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 10.538| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 11.076| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 10.615| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 10.076| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 9.923| 0| 0| 1.538| 11.230
RTD| 1.538| 1.923| 10.538| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 10.923| 0| 0| 1.538| 11.230
RTD| 1.538| 1.923| 10.153| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 9.615| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 10.769| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 9.153| 0| 0| 1.538| 11.230
RTD| 1.538| 1.923| 10.307| 0| 0| 1.538| 11.230
RTD| 1.615| 1.923| 9.538| 0| 0| 1.538| 11.230
RTT| 00:00:22 (periodic user-mode task, 100 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD| 1.615| 1.923| 11.384| 0| 0| 1.538| 11.384
RTD| 1.615| 1.923| 10.076| 0| 0| 1.538| 11.384
RTD| 1.538| 1.923| 9.538| 0| 0| 1.538| 11.384
---|-----------|-----------|-----------|--------|------|-------------------------
RTS| 1.538| 1.923| 11.384| 0| 0| 00:00:25/00:00:25
Normal values for the max latency lat max are from around 0-10 (microseconds) but this will vary with hardware. Anything above, say, 40 (microseconds) is an indicator of something bad happening. It’s especially important to verify that xeno latency
outputs reasonable latencies while doing other RTOS tasks, so we need to run a few more tests.
In Xenomai, the file /proc/xenomai/stat
is updated to show task performance and kernel mode - the mode is very important because we want to be sure our tasks remain in real-time mode where they have highest priority. To check this, we run:
watch -n 0.1 cat /proc/xenomai/stat
to print the contents of the file every 0.1 seconds. However, there have been reported issues of this monitoring causing spikes in latencies, so we simultaneously run xeno latency
and check the test results while doing so. If you see a spike when you start watching, then you may need to search around for a patch for your kernel.
The second latency test to run is to start your robot simulation/control environment and move the windows around while running xeno latency
. If there are no latency spikes then your setup is stable.
There are known issues with the NVidia ‘NICE’ graphics drivers. They generate latency spikes when you start up opengl windows.
On our Dell machines, we had issues with the Intel onboard graphics chip and proprietary driver (i915) causing latency spikes when running programs using X-window. The graphics chip specs are listed below.
00:02.0 VGA compatible controller: Intel Corporation 2nd Generation Core Processor Family Integrated Graphics Controller (rev 09) (prog-if 00 [VGA controller])
Subsystem: Dell Device 047e
Flags: fast devsel, IRQ 16
Memory at e0c00000 (64-bit, non-prefetchable) [size=4M]
Memory at d0000000 (64-bit, prefetchable) [size=256M]
I/O ports at 6000 [size=64]
Expansion ROM at <unassigned> [disabled]
Capabilities: [90] MSI: Enable- Count=1/1 Maskable- 64bit-
Capabilities: [d0] Power Management version 2
Capabilities: [a4] PCI Advanced Features
Kernel modules: i915
One solution in such a case is to use an external graphics card; we were able to resolve the issue using an AMD card and associated drivers instead. However, we also lacked the PCI slot space in these small machines for a graphics card once we installed ethernet and USB 1.1 cards needed for other real-time drivers. As a result, we ended up removing the external graphics card and using the Xorg VESA drivers with the above Intel motherboard graphics chip. This was accomplished by adding the parameter nomodeset
to the grub command line arguments (right after “quiet splash”) in the file /etc/default/grub
. If you use this workaround, you may also need to add the line GRUB_GFXMODE=1280x1024x24
later in this file in order to get decent resolution on a larger monitor. Graphics issues like this seem common.
If you run into issues such as an infinite black/purple screen preventing you from reaching a login prompt, it may be due to the fact that you’re booting Ubuntu from an SSD. One workaround is to add a delay by adding eg sleep 2
in the file /etc/init/lightdm.conf
above the line exec lightdm
. A better solution is to switch to using gdm which seems to work fine with SSDs. This is apparently a common problem which happens randomly. If all else fails, drop to a virtual terminal as root (CTRL+ALT+F1) and run service lightdm restart.
To switch to gdm, first install it and then run dpkg-reconfigure gdm
and then service lightdm stop
and finally service gdm start
.
Our robot, like many others, uses Ethernet (UDP) for network communication between the control computer and the robot’s embedded motor controllers. In order to guarantee that commands sent (and sensor data read back) at 1Khz don’t get preempted by other lower-priority tasks, we employ RTnet, an Open-Source hard real-time network protocol stack for Xenomai and RTAI (real-time Linux extensions).
Download and RT-NET (I used the master branch from sourceforge) as follows:
# again as root
cd /usr/src
git clone git://git.code.sf.net/p/rtnet/code rtnet
cd rtnet
git checkout 7c8ba10513fe7b63873f753ab22d340bf44119a2 # this was a specific version we needed, may not be necessary in general
Next, configure the installation in the same way you configured the linux kernel using make menuconfig
as shown below, optionally copying this pre-configured rtnet_config configuration file to /usr/src/rtnet/.config
to start from. In the configuration setup you should make sure to do the following:
rtcap.ko
) in Add-OnsRun the following to configure and install rtnet:
# install the old config file (optional)
mv ~/Downloads/rtnet_config /usr/src/rtnet/.config
#configure rtnet as needed and then compile and install
make menuconfig
make && make install
# copy the rtnet udev config
cp tools/00-rtnet.rules /etc/udev/rules.d/
Warning! At this point, you will lose network connectivity temporarily as you manually configure networking to use the real-time drivers. This will be remedied once you edit the following two files and reboot.
First you’ll need to remove the linux automatic network manager so that it doesn’t interfere with the manual network configuration we do below.
apt-get remove network-manager
Now, setup the network interfaces and udev rules by editing the file /etc/udev/rules.d/70-persistent-net.rules
.
The robot control computer has a 2-port Intel Corporation PRO/1000 PT Dual Port Server Adapter ethernet card ethernet card which we’ll use for RTnet, and 1 motherboard Ethernet port we’ll use for non-real-time networking (internet access). First, we must figure out how to address each of these ports. We can find their MAC addresses using the lshw
command for network hardware:
lshw -C network
to list network hardware which is currently installed. On the robot control computer this outputs (abbreviated):
*-network:0 DISABLED
description: Ethernet interface
product: 82571EB Gigabit Ethernet Controller
vendor: Intel Corporation
physical id: 0
bus info: pci@0000:01:00.0
logical name: eth1
version: 06
serial: 00:15:17:dc:5d:9c
.
.
.
*-network:1 DISABLED
description: Ethernet interface
product: 82571EB Gigabit Ethernet Controller
vendor: Intel Corporation
physical id: 0.1
bus info: pci@0000:01:00.1
logical name: eth2
version: 06
serial: 00:15:17:dc:5d:9d
.
.
.
*-network
description: Ethernet interface
product: 82579LM Gigabit Network Connection
vendor: Intel Corporation
physical id: 19
bus info: pci@0000:00:19.0
logical name: eth0
version: 04
serial: 78:2b:cb:9e:38:fa
.
.
.
The external ethernet card is clearly the 82571EB Gigabit Ethernet Controller. The “serial” lines indicate the MAC addresses 00:15:17:dc:5d:9c
and 00:15:17:dc:5d:9d
for these interfaces; we will use these for RTNet. The motherboard interface is 78:2b:cb:9e:38:fa
, and we will use this for non-RT ethernet.
Note the MAC addresses of the ports you wish to make real-time compatible and reorder/name them in /etc/udev/rules.d/70-persistent-net.rules
as eth0
, eth1
, and so on starting with the lowest MAC address. Note the MAC address of the port you wish to use for non-RT ethernet and number it ethX+1
(the next highest number available). After reordering and changing eth
assignments, this file looks like:
# This file was automatically generated by the /lib/udev/write_net_rules
# program, run by the persistent-net-generator.rules rules file.
#
# You can modify it, as long as you keep each rule on a single
# line, and change only the value of the NAME= key.
# PCI device 0x8086:/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 (e1000e)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:15:17:dc:5d:9c", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="e\
th*", NAME="eth0"
# PCI device 0x8086:/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.1 (e1000e)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:15:17:dc:5d:9d", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="e\
th*", NAME="eth1"
# PCI device 0x8086:/sys/devices/pci0000:00/0000:00:19.0 (e1000e)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="78:2b:cb:9e:38:fa", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="e\
th*", NAME="eth2"
Now, since we uninstalled the network manager, we need to manually configure the non-real-time interface eth2
. This is done by editing the usual networking file /etc/network/interfaces
to look as shown below:
auto lo
iface lo inet loopback
auto eth2
iface eth2 inet dhcp
Now, reboot the machine and run ifconfig
to monitor interface status. You should see that eth2
is up and you again have internet connectivity!
RTnet must be started using a script we write to load the desired real-time networking modules and set up the real-time routing table. We wrote a shell script called load_rtnet
which is use with source /usr/local/rtnet/load_rtnet MODE
where MODE
can be start, stop or restart. The full script is given below (abbreviated for clarity):
#! /bin/sh
RTNETPATH=/usr/local/rtnet
case "$1" in
start)
# First unload non-RT modules (required) and load real-time modules
echo "Starting rtnet from $RTNETPATH"
ifconfig eth2 down
rmmod e1000e
sleep 1
insmod $RTNETPATH/modules/rtnet.ko
sleep 1
insmod $RTNETPATH/modules/rtipv4.ko
insmod $RTNETPATH/modules/rtcfg.ko
insmod $RTNETPATH/modules/rtpacket.ko
insmod $RTNETPATH/modules/rtudp.ko
insmod $RTNETPATH/modules/rt_e1000.ko cards=1,1 # for a quad-port Ethernet card, use cards=1,1,1,1 to enable all its ports in RTnet
sleep 1
insmod /usr/local/rtnet/modules/rtcap.ko
# Set up the real-time interfaces rteth0 and rteth1 for the PCI Ethernet ports
echo "Setting up rtifconfig..."
$RTNETPATH/sbin/rtifconfig rteth0 up 192.168.1.100 netmask 255.255.255.0 hw ether 00:15:17:dc:5d:9c
$RTNETPATH/sbin/rtifconfig rteth1 up 192.168.4.100 netmask 255.255.255.0 hw ether 00:15:17:dc:5d:9d
$RTNETPATH/sbin/rtifconfig
# Set up routes from port rteth0 to other networked hardware using their IP and MAC addresses
echo "Setting up rtroute..."
$RTNETPATH/sbin/rtroute add 192.168.1.22 0:0:0:12:34:6B dev rteth0
$RTNETPATH/sbin/rtroute add 192.168.1.23 0:0:0:12:34:6C dev rteth0
.
.
.
# Set up routes from port rteth1 to other networked hardware using their IP and MAC addresses
$RTNETPATH/sbin/rtroute add 192.168.3.8 0:0:0:32:34:5D dev rteth1
$RTNETPATH/sbin/rtroute add 192.168.3.9 0:0:0:32:34:5E dev rteth1
.
.
.
# Bring up the real-time interfaces we just set up
ifconfig rteth0 up 192.168.1.100 netmask 255.255.255.0 hw ether 00:15:17:dc:5d:9c
ifconfig rteth1 up 192.168.4.100 netmask 255.255.255.0 hw ether 00:15:17:dc:5d:9d
ifconfig
# Re-load non-RT modules
echo "Loading non-rt ethernet..."
modprobe e1000e
sleep 1
ifconfig eth2 up
;;
restart)
$RTNETPATH/load_rtnet stop
$RTNETPATH/load_rtnet start
;;
stop)
echo "Stopping rtnet from $RTNETPATH"
echo "Shutting down rtifconfig..."
$RTNETPATH/sbin/rtifconfig rteth0 down
$RTNETPATH/sbin/rtifconfig rteth1 down
rmmod rtcap.ko
rmmod rt_e1000.ko
sleep 1
rmmod rtpacket.ko
rmmod rtcfg.ko
rmmod rtudp.ko
rmmod rtipv4.ko
#rmmod rtloopback.ko
rmmod rtnet.ko
;;
*)
echo $"usage: $0 {start|stop|restart}"
exit 3
;;
esac
:
The above script basically unloads non-real-time networking modules/interface eth2, loads the real-time modules, sets up the real-time interfaces rteth0 and rteth1 , sets up routes for these real-time interfaces, and re-loads the non-real-time interface eth2.
Run the script and then run dmesg
to confirm that you see output like (abbreviated):
[ 953.206279] *** RTnet 0.9.13 - built on Nov 5 2014 14:49:59 ***
[ 953.206282] RTnet: initialising real-time networking
.
.
.
[ 954.260555] e1000: 0000:01:00.0: e1000_probe: (PCI Express:2.5Gb/s:Width x4) 00:15:17:dc:5d:9c
[ 954.336507] RTnet: registered rteth0
[ 954.336508] e1000: rteth0: e1000_probe: Intel(R) PRO/1000 Network Connection
[ 954.384213] e1000: 0000:01:00.1: e1000_probe: (PCI Express:2.5Gb/s:Width x4) 00:15:17:dc:5d:9d
[ 954.460152] RTnet: registered rteth1
[ 954.460153] e1000: rteth1: e1000_probe: Intel(R) PRO/1000 Network Connection
.
.
.
[ 961.879152] e1000e: eth2 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: Rx/Tx
[ 961.879186] IPv6: ADDRCONF(NETDEV_CHANGE): eth2: link becomes ready
Very importantly, check that the MAC addresses in the lines preceding RTnet: registered rteth0
and RTnet: registered rteth1
match those used in the load_rtnet
script. If they do not, change them to match, otherwise the association between port name and MAC address will be wrong no messages will actually get sent. This must be checked because RTNet doesn’t necessarily choose interfaces in MAC adress order! Likewise, the real-time interfaces may not necessarily be in physical order on the Ethernet card! You can check by plugging a cable into one port and checking dmesg
for a line like rt_e1000: rteth0 NIC Link is Up 100 Mbps Full Duplex
.
To run the RTnet loading script at startup, we must copy it to /etc/init.d/
which is where all startup scripts reside. To ensure the script gets run, we must create symbolic links to it in each of the /etc/rcN.d/
directories.
Briefly, rc stands for run control and N is from 0-6+S, and the numbers refer to different runlevels the system can be initialized into. When a linux machine is started, the kernel loads the root filesystem and reads
/etc/init/rc-sysinit.conf
(in Ubuntu) to know which runlevel N it should boot into. All scripts linked to in the corresponding/etc/rcN.d
directory are then run.Linux distributions define these differently, but most reserve levels 0 for shutdown and 6 for reboot; the user can generally modify levels 2-5. In Ubuntu, the GUI interface defaults to 2. The special runlevel S is for scripts before the default runlevel, and should only be used if you need to run something before all other runlevels.
To run single commands simply during the boot process, add them to the script
/etc/rc.local
which gets run at the end of each multiuser runlevel (traditionally 2-5).See this page for more information on linux runlevels and this page for Ubuntu-specific information.
We set up runlevels for the RTnet loading script by creating symbolic links to it in the rcN.d
directories with following sequence of commands:
cp /usr/local/rtnet/load_rtnet /etc/init.d
cd /etc/rc0.d
ln -s ../init.d/load_rtnet K20load_rtnet && cd ../rc1.d
ln -s ../init.d/load_rtnet K20load_rtnet && cd ../rc2.d
ln -s ../init.d/load_rtnet S20load_rtnet && cd ../rc3.d
ln -s ../init.d/load_rtnet S20load_rtnet && cd ../rc4.d
ln -s ../init.d/load_rtnet S20load_rtnet && cd ../rc5.d
ln -s ../init.d/load_rtnet S20load_rtnet && cd ../rc6.d
ln -s ../init.d/load_rtnet K20load_rtnet && cd ../rcS.d
ln -s ../init.d/load_rtnet K20load_rtnet
In the link names, K denotes that the service should be killed and S denotes that it should be started; service is killed in levels 0, 1, 6 and S and started in all other levels. Note that start is passed to the script when the link starts with S and stop is passed when the link starts with K, hence why we set up the shell script as such. The number that follows is service priority from 01-99, with 01 being highest priority. We choose 20 somewhat arbitrarily since we have no other service to be run.
While our robot’s motor controllers were networked with Ethernet, the IMUs onboard for state estimation (and potentially other sensors) are USB. We use a special real-time USB driver called usb4rt which unfortunately only works with USB 1.1 ports/compatible devices. This worked with the older Microstrain 3DM-GX3-25 IMU we used, however for most sensors USB 1.1 is not an option.
In order to use real-time USB support, you must have a USB 1.1 compatible card ie one which is UHCI-based since usb4rt only provides a UHCI driver at this time. Any PCI-based USB card made by Intel/VIA will work. Of course, you need PCI space for both the real-time ethernet card and for the USB card and potentially also an external graphics card (3 PCI slots total). Real-time USB 2.0 support is not yet available in Xenomai.
At this time, the real-time USB 1.1 drivers have been tested only with the Microstrain 3dm-gx3-25 and 3dm-gx3-45 IMUs; in theory, any other USB 1.1-compatible device can be used as well. Here we focus on using these IMUs.
The USB protocol is a master/slave system controlled by a single host. This means USB devices cannot directly talk to one another which alleviates issues of collision avoidance, etc. USB 1.0 was released in 1996, developed by a number of companies. USB 1.1 was released in 1998 and allows for a max bandwidth of 12Mbits/s in Full Speed mode but in reality is about 8.5Mbits/s under ideal conditions due to overhead (and may be even slower). There is a Low Speed mode limited to 1.5Mbit/s. USB 2.0 was released in 2000 and featured a Hi Speed mode operating at 480Mbit/s (in reality, 280Mbit/s or 35Mbyte/s due to overhead) and a legacy USB 1.1 Full Speed mode. USB 3.0 was released in 2008 and added a Super Speed mode for 5Gbit/s (realistically, 4Gbit/s or 500Mbyte/s).
The motherboard provides a USB host controller to act as master using a Host Controller Driver (HCD) to interface with the hardware-level Host Controller Interface (HCI). For USB 1.1, there were two HCIs in use, depending on hardware: Compaq’s Open Host Controller Interface (OHCI) and Intel’s proprietary Universal Host Controller Interface (UHCI). UHCI requires simpler hardware and thus relies on the controller more heavily, increasing CPU load slightly. Intel/VIA used UHCI while most other companies used OHCI. For USB 2.0, a single HCD called Enhanced Host Controller Interface (EHCI) was developed. On early setups, EHCI was used for Hi Speed mode and either OHCI or UHCI was used for legacy modes. On modern setups, EHCI implements OHCI/UHCI virtually to indirectly provide legacy support (this is possible because all USB ports are routed through a Rate Matching Hub or RMH). USB 3.0 uses the Extensible Host Controller Interface (XHCI) and supports all the above modes.
The HCD you use depends on the HCI your controller hardware uses; use lspci -v | grep HCI
to list controllers and their HCIs and choose your HCD accordingly. On newer kernels, there exist ehci-hcd
, uhci-hcd
and ohci-hcd
; you can check if any of these modules are loaded with lsmod | grep hcd
and load/unload them with insmod
/rmmod
or using modprobe
. Note that all modules are actually installed in /lib/modules/KERNELNAME
and modules can be loaded at boot time by adding them to /etc/modules
. You can get information about a module (including its location) using modinfo MODULENAME
.
Physically, a computer has a small number of USB ports; however, the standard supports up to 127 devices, so hubs are used to extend the number of ports. In theory, self-powered USB devices can pull 500mA per device. When a USB device is attached, it is assigned a unique device number (1-127) and its device descriptor (which contains device information) is read.
Devices are assigned classes, one of which is Human Interface Device (HID) for peripherals such as a keyboard, mouse, etc. To get a detailed device list use lsusb -v
, and for information about a single device, use eg lsusb -D /proc/usb/bus/001/005
for device 5 on bus 1. You can easily see assigned device numbers in the kernel log with dmesg | grep USB
.
After installing your USB 1.1-compatible card, you can then install usb4rt with:
cd /usr/src/
# clone the repository
git clone git@gitlab.tuebingen.mpg.de:nrotella/usb4rt.git
# stay on master branch
cd usb4rt
# configure the driver (the last option points to the xenomai install directory; it would be /usr/xenomai by default but since we used debian packages this isn't the right place to look)
./configure --enable-drv-cdc-acm --enable-bandw-reclam --with-xeno-user-dir=/usr/xenomai
# build and install
make && make install
Next, we need to unbind the UHCI driver from the 1.1-compatible card and load the real-time drivers in their stead. Run dmesg | grep uhci
and you should see output which looks like:
[ 2.101228] uhci_hcd: USB Universal Host Controller Interface driver
[ 2.101248] uhci_hcd 0000:05:00.0: setting latency timer to 64
[ 2.101252] uhci_hcd 0000:05:00.0: UHCI Host Controller
[ 2.101255] uhci_hcd 0000:05:00.0: new USB bus registered, assigned bus number 1
[ 2.101288] uhci_hcd 0000:05:00.0: irq 16, io base 0x00004020
[ 2.101318] usb usb1: Manufacturer: Linux 3.8.13-ipipe-xenomai-2.6.3 uhci_hcd
[ 2.101421] uhci_hcd 0000:05:00.1: setting latency timer to 64
[ 2.101425] uhci_hcd 0000:05:00.1: UHCI Host Controller
[ 2.101428] uhci_hcd 0000:05:00.1: new USB bus registered, assigned bus number 2
[ 2.101461] uhci_hcd 0000:05:00.1: irq 17, io base 0x00004000
[ 2.101492] usb usb2: Manufacturer: Linux 3.8.13-ipipe-xenomai-2.6.3 uhci_hcd
This tells you which USB hubs and PCI addresses your USB 1.1-compatible card is attached to and the IRQ (interrupt) lines it’s using with the non-RT uhci_hcd driver. The reason there are two UHCI Host Controllers is because the USB 1.1 ports on the card got split over two hubs/PCI addresses/IRQs here. To figure out which ports correspond to which hub, plug in a device and look for a line in dmesg such as:
[ 6689.424925] usb 2-1: new full-speed USB device number 9 using uhci_hcd
The usb 2-1 line means that the device was attached to hub 2 at port 1. If you move the device around the USB ports on the card, you’ll see this message change - this is a simple way to tell which USB ports get mapped to which hubs.
To confirm this IRQ assignment, run cat proc/interrupts
which should produce output like (truncated):
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
16: 546 0 0 91 0 0 0 0 IO-APIC-fasteoi uhci_hcd:usb1, ehci_hcd:usb3, nouveau
17: 932 0 69308 0 0 0 0 0 IO-APIC-fasteoi uhci_hcd:usb2, ehci_hcd:usb4, snd_hda_intel
The first column is the IRQ (interrupt) number, and the last column shows the modules which use that interrupt. Here we see the uhci_hcd
modules, which we need to replace with usb4rt
, split across IRQs 16 and 17.
The problem in our case is that we cannot have anything but USB modules on each of these interrupts, so we need to resolve this IRQ sharing. If you’re very lucky, you’ll see nothing other than UHCI controllers on their own IRQ lines; in this case you don’t have to worry about IRQ sharing and you can skip this section. Otherwise, follow the Xenomai guide to IRQ sharing and try something like what we did below.
Some machine architectures allow you to assign IRQs in the BIOS, in which case you can assign the USB 1.1 card its own IRQ to prevent conflicts. Most machines don’t allow you to do this, though - you’re stuck with the above IRQ assignments. In our case, we have the USB hub3 EHCI and graphics drivers on IRQ 16, and USB hub4 EHCI and sound drivers on IRQ 17. We now have two choices:
In our case we went with the first option because we needed to use the onboard graphics chip and VESA driver anyway (in order to open up a PCI slot for the USB 1.1 card and resolve latency issues). This is just an example of how to resolve tricky IRQ issues.
As for RTnet above, we write a shell script load_rtusb
to set up real-time USB, first by unloading the non-RT drivers and then loading the usb4rt modules. Once you chmod
it to be executable, usage is source load_rtusb MODE
with MODE being stop/start/restart.
This example is specific to our use-case for the USB IMU. You will need to change the PCI addresses in the unbind
and bind
lines as discussed above, as well as change the vendor
and product
IDs based on lsusb -v
output.
#! /bin/sh
RTUSBPATH=/usr/local/usb4rt/
case "$1" in
start)
# Unbind the non-RT USB drivers
echo "Starting usb4rt from $RTUSBPATH"
echo -n "0000:05:00.0" > /sys/bus/pci/drivers/uhci_hcd/unbind
echo -n "0000:00:1a.0" > /sys/bus/pci/drivers/ehci-pci/unbind
sleep 1
# Load the usb4rt drivers
insmod /usr/local/usb4rt/modules/rt_usbcore.ko
sleep 1
insmod $RTUSBPATH/modules/rt_uhci_hcd.ko
insmod $RTUSBPATH/modules/rt_cdc_acm.ko vendor=0x199b product=0x3065 start_index=0 # specific to the Microstrain IMU
;;
restart)
$RTUSBPATH/load_rtusb stop
$RTUSBPATH/load_rtusb start
;;
stop)
echo "Stopping usb4rt from $RTUSBPATH"
rmmod rt_cdc_acm
rmmod rt_uhci_hcd
rmmod rt_usbcore
sleep 1
echo -n "0000:05:00.0" > /sys/bus/pci/drivers/uhci_hcd/bind
echo -n "0000:00:1a.0" > /sys/bus/pci/drivers/ehci-pci/bind
;;
*)
echo $"usage: $0 {start|stop|restart}"
exit 3
;;
esac
:
You should see similar output as below (truncated) in the system log with dmesg
after running the script:
[15247.217455] uhci_hcd 0000:05:00.0: remove, state 1
[15247.217462] usb usb1: USB disconnect, device number 1
[15247.217609] uhci_hcd 0000:05:00.0: USB bus 1 deregistered
[15247.217647] ehci-pci 0000:00:1a.0: remove, state 1
[15247.217652] usb usb3: USB disconnect, device number 1
[15247.217653] usb 3-1: USB disconnect, device number 2
[15247.224546] ehci-pci 0000:00:1a.0: USB bus 3 deregistered
[15274.708025] ********** Realtime USB-Core Module 0.0.5 ***********
[15274.708028] RT-USBCORE: Max 16 Controller
.
.
.
[15274.708042] RT-USBCORE: Loading Completed (1152 Byte allocated)
[15275.706935] ********* Realtime Driver for Universal Host Controller 0.0.5 **********
[15275.706939] RT-UHC-Driver: Searching for Universal-Host-Controller
[15275.706943] USB Universal Host Controller found : Vendor = 0x1106, Device = 0x3038, IRQ = 16, IO-Port = 0x00002020 (32 Bytes)
[15275.706944] RT-UHC-Driver: Request IO-Port @ 0x00002020 (32 Byte) for UHC[0] ... [OK]
[15275.706947] RT-UHC-Driver: Request RTDM IRQ 16 ... [OK]
.
.
.
[15276.185863] --- LIST USB-DEVICES -------------------------
[15276.185865] No HCD VENDOR PROD CLS SCLS PROT ->CTRL CTRL-> ->BULK BULK-> ->INT INT-> ->ISOC ISOC-> STATE SPEED USED
[15276.185867] 001 000 0x199b 0x3065 0x02 0x00 0x00 0x0001 0x0001 0x0002 0x0008 0x0004 0x0000 0x0000 0x0000 00000 00002 X
[15276.185868] --- END USB-DEVICES --------------------------
[15276.185869] rt_cdc_acm[0]: device found, vendor=0x199b, product=0x3065
The last line indicating the rt_cdc_acm
driver is loaded is confirmation that it worked. Similar to RTnet interface rteth0
, you should also find the device rtser0
now exists in the folder /proc/xenomai/rtdm
as shown:
root@perseus:~# cat /proc/xenomai/rtdm/rtser0/information
driver: rt_cdc_acm
version: 0.5.0
peripheral: USB CDC ACM
provider: USB4RT
class: 2
sub-class: -1
flags: EXCLUSIVE NAMED_DEVICE
lock count: 0
If you see a line in dmesg
such as
[ 602.383262] Xenomai: xnintr_irq_handler: IRQ16 not handled. Disabling IRQ line.
at this point, you likely still have an IRQ sharing issue. Again, consult the Xenomai IRQ sharing guide. Otherwise, let’s confirm there is no IRQ sharing happening.
First run the usual linux cat /proc/interrupts
command and check that the IRQ line previously used for uhci_hcd
has been cleared when those non-real-time USB modules were removed, then check Xenomai’s IRQ assignment with cat /proc/xenomai/irq
to confirm that same IRQ (for us, 16) now has rt_uhci
loaded:
IRQ CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
16: 1000 0 0 0 0 0 0 0 rt_uhci
16640: 1007875 1032112 1434126 1527423 266541 420504 264432 313195 [timer]
16641: 2 1 1 1 1 1 1 3 [reschedule]
16642: 0 1 1 1 1 1 1 1 [timer-ipi]
16643: 0 0 0 0 0 0 0 0 [sync]
16707: 8 0 1 0 0 0 0 0 [virtual]
Again, as we did for the RTnet script, we copy the usb4rt script to /etc/init.d/
and make links in /etc/rcX.d
as follows:
cp /usr/local/usb4rt/load_rtusb /etc/init.d
cd /etc/rc0.d
ln -s ../init.d/load_rtusb K20load_rtusb && cd ../rc1.d
ln -s ../init.d/load_rtusb K20load_rtusb && cd ../rc2.d
ln -s ../init.d/load_rtusb S20load_rtusb && cd ../rc3.d
ln -s ../init.d/load_rtusb S20load_rtusb && cd ../rc4.d
ln -s ../init.d/load_rtusb S20load_rtusb && cd ../rc5.d
ln -s ../init.d/load_rtusb S20load_rtusb && cd ../rc6.d
ln -s ../init.d/load_rtusb K20load_rtusb && cd ../rcS.d
ln -s ../init.d/load_rtusb K20load_rtusb
Using usb4rt with more than one device is simple: just change the line:
insmod $RTUSBPATH/modules/rt_cdc_acm.ko vendor=0x199b product=0x3065 start_index=0
to, for example:
insmod $RTUSBPATH/modules/rt_cdc_acm.ko vendor=0x199b,0x199b product=0x3065,0x3065 start_index=0
where we have told the driver that two devices will be used. According to the driver, 16 devices can be added in this way.
Once the driver has been loaded, you can interact with the USB device at the rtser0
interface in much the same way you have interacted with a non-real-time device like /dev/ttyACM0
or /dev/ttyUSB0
.
If for some reason the above instructions did not work, you can find long output with common debug enabled when drivers have been successfully loaded here. You may also try the following useful commands to find the problem:
lsusb
to list USB devices, then choose the one you’re interested in and use lsusb -vd MANUFACTURER:DEVICE
to get more details about it/etc/udev/
, similar to 70-persistent-net-rules.d
(the “70” specifies the order in which these rules are used)Also, note that older versions of usb4rt on the branch cdc-acm required you to limit your system RAM to 4GB. Failing to do this created communication issues because data got read into memory which the driver didn’t know about, causing the read command to never return. To limit the memory, simply edit /etc/default/grub
and add mem=4G
to the GRUB_CMDLINE_LINUX_DEFAULT
line. This should not be necessary for the current version of usb4rt on master branch.
Real-time serial communication in Xenomai is much simpler to set up and use than RTnet or usb4rt. To load the real-time serial driver in Xenomai, issue tthe following commands:
setserial /dev/ttyS0 uart none
modprobe xeno_16550A io=0x3f8 irq=4 start_index=1
To unload the RTSerial driver and revert to the non-RT driver:
setserial /dev/ttyS0 uart 16550A
Here are some other notes which I wrote down during the above setup which may be useful.
If you reach a scary “grub rescue” prompt with the error “invalid arch independent ELF magic,” fear not. This indicates that your grub install got messed up, but your new Ubuntu installation and data are fine. This may have happened if you chose the wrong USB boot (UEFI instead of Legacy) from the BIOS; if the following short procedure doesn’t work, you can try reinstalling and selecting this boot option instead. Reboot the machine (hard reset if necessary) and insert the install USB again, boot from the USB in the BIOS and now choose “Try Ubuntu.” Note that the grub rescue prompt may prevent you from reaching the BIOS; you probably just didn’t hit F12 (or whatever key) at the right time. I managed to get to the BIOS every time by repeatedly tapping F12 repeatedly as soon as the boot process began. Once you manage to get to a desktop using the Try Ubuntu option, open a terminal and run the following commands, replacing /dev/sdXY with the mounting point of / chosen in your installation (for example, if / is mounted at /dev/sdb1 then X=b, Y=1):
sudo mount /dev/sdXY /mnt
sudo grub-install --boot-directory=/mnt /dev/sdX
Reboot and everything should work normally again! If not, you may also have to install the grub-efi-amd64
package from the live CD. If you absolutely cannot reach the BIOS to get to the live CD, it should also be possible to boot directly from the grub rescue prompt (check online for commands).