Nov 11, 2014

Linux kernel startup

I've been looking at the Linux kernel after a 10 year hiatus, so I started again at the very beginning: when the kernel is just starting.

Reset vector takes you to architecture specific assembly

This section is likely to lose the audience unless explained carefully.  I will use the ARM assembly, because I am playing around with the Zedboard.

WIP.

C "main"

As we just saw, there is a lot assembly code that sets up the interrupt vectors, stack, and the C runtime environment, but let's start when the C code begins to run.  This IBM article (written for x86) is a good read, but it still does not have enough detail on how the drivers are loaded in the beginning, so I went to the <kernel>/init/main.c:start_kernel(), which matches the dmesg lines very well:


start_kernel()dmesg
boot_cpu_init();Booting Linux on physical CPU 0x0
page_address_init();
pr_notice("%s", linux_banner);Linux version 3.15.0 (henry@Zotac64) (gcc version 4.8.3 (Buildroot 2014.08) ) #3 SMP PREEMPT Sun Oct 12 16:55:23 PDT 2014
setup_arch(&command_line);CPU: ARMv7 Processor [413fc090] revision 0 (ARMv7), cr=18c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
Machine model: Xilinx Zynq ZED
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);bootconsole [earlycon0] enabled
setup_nr_cpu_ids();cma: CMA: reserved 128 MiB at 17800000...
setup_per_cpu_areas();PERCPU: Embedded 8 pages/cpu @dfb9e000 s8448 r8192 d16128 u32768
smp_prepare_boot_cpu();pcpu-alloc: s8448 r8192 d16128 u32768 alloc=8*4096

pcpu-alloc: [0] 0 [0] 1
smp_prepare_boot_cpu();Built 1 zonelists in Zone order, mobility grouping on. Total pages: 130048
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
parse_early_param()
Kernel command line: console=ttyPS0,115200 ip=192.168.1.9:192.168.1.2:192.168.1.1:255.255.255.0 root=/dev/nfs nfsroot=192.168.1.2:/export/root/zed rw earlyprintk
jump_label_init();
setup_log_buf(0);
pidhash_init();PID hash table entries: 2048 (order: 1, 8192 bytes)
vfs_caches_init_early();Dentry cache hash table entries: 65536 (order: 6, 262144 bytes)

Inode-cache hash table entries: 32768 (order: 5, 131072 bytes)
sort_main_extable();
trap_init();
mm_init();Memory: 381504K/524288K available (4498K kernel code, 254K rwdata, 1684K rodata, 204K init, 146K bss, 142784K reserved, 0K highmem)

Virtual kernel memory layout: …
sched_init();
preempt_disable();
idr_init_cache();
rcu_init();Preemptible hierarchical RCU implementation...
tick_nohz_init();
context_tracking_init();
radix_tree_init();
early_irq_init();
init_IRQ();NR_IRQS:16 nr_irqs:16 16
tick_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();zynq_clock_init: clkc starts at e0802100...
sched_clock_postinit();sched_clock: 16 bits at 54kHz, resolution 18432ns, wraps every 1207951633ns
perf_event_init();
profile_init();
call_function_init();


local_irq_enable();
kmem_cache_init_late();
console_init();Console: colour dummy device 80x30
…
calibrate_delay();Calibrating delay loop... 1332.01 BogoMIPS (lpj=6660096)
pidmap_init();pid_max: default: 32768 minimum: 301
…
proc_caches_init();L310 cache controller enabled

l2x0: 8 ways, CACHE_ID 0x410000c8, AUX_CTRL 0x72760000, Cache size: 512 kB
check_bugs();CPU: Testing write buffer coherency: ok
sfi_init_late();
…
ftrace_init();
rest_init();

In rest_init(), all constructors are called, then the init process is forked off.  The forked off init process will execute later (in parallel with the rest of the kernel init) in its own thread that starts from a software interrupt.  Finally, as in all embedded program, the kernel goes into an infinite idle loop, putting the processor to sleep as much as possible, to save power.  As a proof, when you halt the processor at a random time while the kernel is running, I usually get this stack trace:

ARM Cortex-A9 MPCore #0 (Suspended)
0xc0020428 cpu_v7_do_idle(): arch/arm/mm/proc-v7.S, line 74
0xc0013d1c arm_cpuidle_simple_enter(): arch/arm/kernel/cpuidle.c, line 18
0xc03d11a4 cpuidle_enter_state(): drivers/cpuidle/cpuidle.c, line 104
0xc03d1298 cpuidle_enter(): drivers/cpuidle/cpuidle.c, line 159
0xc0060ab0 cpu_startup_entry(): kernel/sched/idle.c, line 154
0xc05578e4 rest_init(): init/main.c, line 397
0xc07dbba4 start_kernel(): init/main.c, line 652
0x00008074
...

The way to idle the CPU is to call WFI with IRQ disabled:
ENTRY(cpu_v7_do_idle)
dsb @ WFI may enter a low-power mode
wfi
mov pc, lr
ENDPROC(cpu_v7_do_idle)

__initcall: how everything else that is static (vs. dynamically inserted) in the kernel is initialized

"simple-bus" (AKA the top level platform bus) is created in init_machine() --> of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL), very early in kernel initialization.  The platform bus/dev children of the top level simple-bus are then created recursively at that time, and calls bus_probe_device(), which triggers the bus <--> device match check, and if so a platform device (struct device) is probed (the .probe function is called) in <kernel>/drivers/base/platform.c:platform_drv_probe().  The important thing is to remember that probe() happens when the device driver and the device's name matches, according to MODULE_DEVICE_TABLE.

rest_init() forks off a kernel thread to run kernel_init() --> kernel_init_freeable() --> do_basic_setup():

/*
 * Ok, the machine is now initialized. None of the devices
 * have been touched yet, but the CPU subsystem is up and
 * running, and memory and process management works.
 *
 * Now we can finally start doing some real work..
 */
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
shmem_init();
driver_init();
init_irq_proc();
do_ctors();
usermodehelper_enable();
do_initcalls();
random_int_secret_init();
}

The initialization order within <kernel>/drivers/base/init.c:driver_init() subroutines shed some light on the device model dependency: for example, dev and devices are the both right under /sys, and block and char are in turn below /sys/dev.

void __init driver_init(void) {
  /* These are the core pieces */
  devtmpfs_init();
  devices_init();
  buses_init();
  classes_init();
  firmware_init();
  hypervisor_init();

  /* These are also core pieces, but must come after the * core core pieces. */
  platform_bus_init();
  cpu_dev_init();
  memory_dev_init();
  container_dev_init();
}

All __init functions have a level, so that the kernel can stage the initializations:

static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}

These levels have suggestive names:

static char *initcall_level_names[] __initdata = {
  "early", "core", "postcore", "arch", "subsys", "fs", "device", "late",
};

__initcalls appearing in on this Zedboard HW:

Kernel codedmesg
arch_hw_breakpoint_init()found 5 (+1 reserved) breakpoint and 1 watchpoint registers
zynq_ocm_init()zynq-ocm f800c000.ps7-ocmc: ZYNQ OCM pool: 256 KiB @ 0xe0880000
customize_machine()For mach-zynq, calls zynq_init_machine(). Supposed to act on device tree information
usb_init()usbcore: registered new interface driver usbfs...
inet_initNET: Registered protocol family 2

TCP established hash table entries: 4096 (order: 2, 16384 bytes)
pl330_probedma-pl330 f8003000.ps7-dma: Loaded driver for PL330 DMAC-2364208...

What a clever idea: if a module (or whatever with an init function complying to the kernel's __initcall declaration protocol) is statically compiled into the kernel, the kernel will call the registered function, without knowing apriori the full list of such functions.

<kernel>/arch/arm/mach-zynq/common.c enforces that the DTS is compatible field is "xlnx,zynq-7000" with the zynq_dt_match string array:

DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
.smp = smp_ops(zynq_smp_ops),
.map_io = zynq_map_io,
.init_irq = zynq_irq_init,
.init_machine = zynq_init_machine,
.init_late = zynq_init_late,
.init_time = zynq_timer_init,
.dt_compat = zynq_dt_match,
.reserve = zynq_memory_init,
.restart = zynq_system_reset,
MACHINE_END

The machine dt_compat is checked while parsing the DTB, very early in start_kernel(), in setup_arch() --> setup_machine_fdt().  The device tree actually becomes a tree (from a flattened blob) in setup_arch() --> unflatten_device_tree(), where machine specific setup hooks like .init_machine() run.

Platform drivers are for HW that will not dynamically come and go into a Linux system, such as the video and audio controllers in a tablet.  In makes sense to statically pull in those code necessary through the __initcall magic discussed above.  For example:

/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)

An example is a MDIO driver for an Ethernet MAC:

static struct platform_driver fsl_pq_mdio_driver = {
.driver = {
.name = "fsl-pq_mdio",
.owner = THIS_MODULE,
.of_match_table = fsl_pq_mdio_match,
},
.probe = fsl_pq_mdio_probe,
.remove = fsl_pq_mdio_remove,
};

module_platform_driver(fsl_pq_mdio_driver);

Loading firmware into devices

When you stop to think about it, a computer system is composed of many peripheral HW that is only commanded by the device drivers through well defined messaging protocols, but in fact are otherwise
independent embedded HW, controlled by its own CPU--often 8-bit/16-bit microcontrollers but increasingly more sophisticated DSP and even a reconfigurable FPGA or CPU/FPGA hybrids--running its own program--called firmware generically.  Because these uC/uP/DSP/FPGA usually have its own non-volatile memory to store the program/data, their FW usually do NOT need to be programmed during the kernel startup.  The only real reasons to do so are:
  • To upgrade the FW/data, for new behavior
  • Program memory is corrupt (perhaps during the upgrade?) and needs to be reverted to a known good version.
Either way, FW in consumer grade computers are rarely updated--because the users either don't care or don't know how to.  A way to work around this "human obstacle" is for the kernel (the device driver, really) to ALWAYS push the firmware on startup.  Since the FW management is the responsibility of corresponding device driver rather than the kernel proper, I hesitated about discussing the topic in this blog entry.  But since the kernel offers the firmware API request_firmware(), perhaps many device drivers handle the FW upload similarly.  I attached Xilinx SDK to the running kernel on Zedboard, the set a HW breakpoint on request_firmware() in the  (see my other blog entry for how), and pressed the CPU reset button to see the CPU halt on request_firmware() entry, apparently from the ADAU1761 HDMI audio device driver:

ARM Cortex-A9 MPCore #0 (Breakpoint)
0xc02f1fd0 request_firmware(): drivers/base/firmware_class.c, line 1162
0xc048baa4 _process_sigma_firmware(): sound/soc/codecs/sigmadsp.c, line 135
0xc048bc6c process_sigma_firmware_regmap(): ...und/soc/codecs/sigmadsp-regmap.c, line 30
0xc048b194 adau17x1_load_firmware(): sound/soc/codecs/adau17x1.c, line 761
0xc048b8b4 adau1761_codec_probe(): sound/soc/codecs/adau1761.c, line 702
0xc047c004 soc_probe_codec(): sound/soc/soc-core.c, line 1151
0xc047d010 snd_soc_register_card(): sound/soc/soc-core.c, line 1353
0xc048c5ec zed_adau1761_probe(): sound/soc/adi/zed_adau1761.c, line 131
0xc02e2c28 platform_drv_probe(): drivers/base/platform.c, line 491
0xc02e1028 driver_probe_device(): drivers/base/dd.c, line 302
0xc02e12d8 __driver_attach(): drivers/base/dd.c, line 477
0xc02df2fc bus_for_each_dev(): drivers/base/bus.c, line 311
0xc02e0aec driver_attach(): drivers/base/dd.c, line 496
0xc02e0734 bus_add_driver(): drivers/base/bus.c, line 692
0xc02e198c driver_register(): drivers/base/driver.c, line 167
0xc02e2bf8 __platform_driver_register(): drivers/base/platform.c, line 546
0xc080e148 zed_adau1761_card_driver_init(): sound/soc/adi/zed_adau1761.c, line 159
0xc00089bc do_one_initcall(): init/main.c, line 696
0xc07dbcf4 kernel_init_freeable(): init/main.c, line 762
0xc0557904 kernel_init(): init/main.c, line 840
0xc000ed78 ret_from_fork(): arch/arm/kernel/entry-common.S, line 91

Looking at this stack trace that starts at ret_from_fork() kernel entrypoint (it's assembly) and reading the source code, I realized that:
  • kernel_init_freeable() runs BEFORE the userland init process is started
  • driver inits are at level 6 (counting from 0), which agrees with initcall_level_names seen earlier.
  • There is a kernel parameter called "initcall_debug" that will printk initcall names and the usec taken in the initcall.  Might be useful to figure how where the kernel is dying during startup.
  • adau1761 driver complies with the kernel's sound library--the SoC variant.
  • This (sigma codec) FW is "builtin"; request_firmware() calls fw_get_builtin_firmware()
  • After getting the FW binary, _process_sigma_firmware will perform sanity checks (size, CRC32, magic number in header).
  • If there is a problem, the adau1761 DSP is NOT enabled.

Nov 1, 2014

Understanding /dev/urandom

The Linux /dev/urandom pseudo file generates pseudo random bytes, as you can see in this example, where 4 random bytes is strung together to give an impression of a random ulong:

henry@Zotac64:~$ od -vAn -N4 -tx4 /dev/urandom
 9e4a1c93

The od command dumps file in octal and other formats "-N4" gets 4 bytes, and "-tx4" formats it as an 4 byte hex number.  strace shows that the system call used is read(), to grab 4 bytes:
...
open("/dev/urandom", O_RDONLY)          = 3
read(3, "zv_\22", 4)                    = 4
...

How does it actually work under the hood?  Firstly, it is a mem device, as you can see under /sys folder:

henry@Zotac64:~$ ls -o /sys/class/mem/urandom
... /sys/class/mem/urandom -> ../../devices/virtual/mem/urandom

It is a char mem device (major number 1), as you can see below:

henry@Zotac64:~$ ls -lh --time-style=+ /dev/*random
crw-rw-rw- 1 root root 1, 8  /dev/random
crw-rw-rw- 1 root root 1, 9  /dev/urandom

The memory devices are defined <kernel>/drivers/char/mem.c:

static const struct memdev {
const char *name;
umode_t mode;
const struct file_operations *fops;
struct backing_dev_info *dev_info;
} devlist[] = {
 [3] = { "null", 0666, &null_fops, NULL },
 [5] = { "zero", 0666, &zero_fops, &zero_bdi },
 [7] = { "full", 0666, &full_fops, NULL },
 [8] = { "random", 0666, &random_fops, NULL },
 [9] = { "urandom", 0666, &urandom_fops, NULL },
#ifdef CONFIG_PRINTK
[11] = { "kmsg", 0644, &kmsg_fops, NULL },
#endif
};

The random device driver source is found in <kernel>/drivers/char/random.c:

const struct file_operations urandom_fops = {
.read  = urandom_read,
.write = random_write,
.unlocked_ioctl = random_ioctl,
.fasync = random_fasync,
.llseek = noop_llseek,
};

Note that writing to /dev/urandom is the same as writing to /dev/random, which means that the random pool for random and urandom are same.  I could start diving into the code like I did for /dev/zero, but urandom code is clearly more complicated than /dev/zero, and besides, I can use trace-cmd show me the callgraph.

Detour: Buildroot change for trace-cmd

Firstly, ftrace must be enabled in the kernel config:
  • CONFIG_FTRACE=y
  • CONFIG_FUNCTION_TRACER=y and CONFIG_DYNAMIC_FTRACE=y: need both
  • CONFIG_FTRACE_SYSCALLS=y: I want to see the read system call
  • CONFIG_PSTORE_FTRACE (persistent function trace) may be valuable when debugging a hang, reset, and panic.  But this requires CONFIG_PSTORE (persistent RAM buffer store) platform driver.  But Zynq doesn't have such HW, so it looks like a wishful thinking.
I added the above 3 CONFIGs into <kernel>/arch/arm/configs/zynq_xcomm_adv7511_nfs_defconfig, which I've been using for my Zedboard dorking starting with the NFS RFS post.

Back in Buildroot, trace-cmd itself depends on: BR2_LARGEFILE && BR2_TOOLCHAIN_HAS_THREADS && !BR2_avr32 && BR2_USE_MMU.  Large file support is enabled in Buildroot Toolchain options.  I also enabled trace-cmd package.  I have to blow away <BR2>/output/build and rebuild because the toolchain option change.  After 3 hours, I got the new buildroot RFS, including the kernel image.
  • I copy uImage and the DTB to the TFTP folder as discussed in this blog and this blog.
  • I expand out the new RFS to the NFS exports folder as discussed in this blog

trace-cmd on Zedboard

After the above detour, let's take the trace-cmd out for a spin on the Zedboard
trace-cmd record -p function od -vAn -N4 -tx4 /dev/urandom
...
CPU0 data recorded at offset=0x1b0000
    278528 bytes in size
CPU1 data recorded at offset=0x1f4000
    245760 bytes in size

trace-cmd record emits a binary file--trace.dat by default, but overridable with "-o" option.  kernelshark is a nice attempt at graphical representation of this binary report file, but I find it lacking compared to the Saleae logic analyzer UI I like quite a bit, it just doesn't feel like it's ready for prime time.  Zooming in/out using the scroll wheel, and auto snap to event are just a few of the usability factor issues.  Until kernelshark becomes usable, trace-cmd report is a usable text based alternative.

trace-cmd report | grep -v trace-cmd > urandom_read.txt

Grepping for "random" (command: cut -d " " -f 22,42- urandom_read.txt | grep random) shows:

...
57.740070:             add_interrupt_randomness
57.740733: get_random_int
57.741692:       add_interrupt_randomness
...
</dev/urandom is opened>
57.751332: random_ioctl <strace shows od did NOT call ioctl>
57.751369: urandom_read
57.751424:             add_interrupt_randomness
57.752511:       add_interrupt_randomness
57.753046:             add_interrupt_randomness
 add_device_randomness

Observations:
  • A lot of add_interrupt_randomness()--which is not strange by itself (there is a lot of interrupt on a typical desktop system) but only because they are all occuring in the "od" task (remove the last cut command to see for yourself)
  • get_random_int(), get_random_bytes() called way ahead of (more than 100 ms) the actual urandom_read()
  • random_ioctl() called right before urandom_read()
Adding randomness in the IRQs marked with SA_SAMPLE_RANDOM in request_irq() is covered in Linux Device Drivers, 3rd Edition Interrupt Handling chapter.  The interrupt randomness--to be distinguished from other source of randomness such as device or disk--is based on the source IRQ number and the timing of the interrupt.  urandom_read trace is here:

                   urandom_read
                      extract_entropy_user
                         xfer_secondary_pool
                         account
                            __wake_up
                               _raw_spin_lock_irqsave
                               __wake_up_common
                               _raw_spin_unlock_irqrestore
                            kill_fasync
                         extract_buf
                            _raw_spin_lock_irqsave
                            __mix_pool_bytes
                               _mix_pool_bytes
                            _raw_spin_unlock_irqrestore

Compare to the simple trace for /dev/zero read; most of the complexity is in extracting from nonblocking entry pool (vs. blocking_pool for /dev/random read).  Entropy pool is critical to obtaining numbers that are in fact random.  In fact /dev/random calls wait_event_interruptible() (vs. the fast spinlock we see above) when the entropy pool is depleted, although I am not sure how fast I have to read from /dev/random to trigger this behavior, but for now, let's just press on with urandom.

Appendix: random seed management across reboot

Since the entropy pool is empty on system power-up, the recommendation in random.c is to save the random seed during shutdown, and load it back in during startup, with scripts like these:

shutdown script

echo "Saving random seed..."
random_seed=/var/run/random-seed
touch $random_seed
chmod 600 $random_seed
dd if=/dev/urandom of=$random_seed count=1 bs=512

Startup script

echo "Initializing random number generator..."
random_seed=/var/run/random-seed
if [ -f $random_seed ]; then
cat $random_seed >/dev/urandom
else
touch $random_seed
fi
chmod 600 $random_seed
#Guard against ungraceful shutdown
dd if=/dev/urandom of=$random_seed count=1 bs=512

Backing up a drive on a Ubuntu

One time backup and restore

Just ran out of room on my tiny SSD, so figuring out a migration path using rsync archive mode.  Mounted the SSD on my  Mounted a large HDD from the bygone era for temporary storage, and began copy:

rsync -a /media/henry/<SSD folder> /media/henry/<HDD folder>
Since the SSD is mounted on USB, rsync goes on for a couple of hours.

Copying from the HDD to another, bigger drive should work just like above (but in reverse direction of course):

rsync -a /media/henry/<HDD folder>/. /media/henry/<new SSD folder>

Incremental backup 

Restrict rootfs backup to skip kernel generated fs, and backup the /mnt separately.  The script to run in the backup folder is then is then

sudo cp -al latest `date +%F`

sudo rsync -aAX --delete --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} / latest/rootfs

sudo rsync -aAX --delete /mnt/opt latest

sudo rsync -aAX --delete --exclude={"*.vdi"} /mnt/work latest

I omit the Virtualbox guest's virtual HDD from the backup set, because that file changes every time, and is large.


Oct 31, 2014

Booting up Linux on Zedboard over network

In a previous post, I broke out of the constraints of an SD for the Linux root file system for my Zedboard.  I may start modifying the ARM Linux kernel soon, in which case copying the uImage to the SD card intermediate step seems mildly irritating.  On PCs, the PXE boot is a well-proven technology, and I wrote down how to do it a few years ago.  PXE is an Intel technology, and unavailable for ARM I think, but fortunately, other people (like Sven Anderson) have already figured out how to coax U-Boot download the kernel image over TFTP and then boot it.  I followed his "Zynq design from scratch" blog to setup Kernel netboot, as described below.

TFTP server (tftpd-hpa) on Ubuntu 14.04 LTS

Install the tftpd-hpa on the host:
$ sudo apt-get install tftpd-hpa
The default configuration file (/etc/default/tftpd-hpa) just needs some tweaks (my changes highlighted):

RUN_DAEMON="yes"
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/var/lib/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --ipv4"


For reasons I cannot recall now, I ran the TFTP as its own daemon instead of through inetd.  Because I have bigger fish to fry, I am moving on.  Serving all interfaces on the host is fine because my development box only has 1 NIC.  I originally allowed IPv6 to run on my server, but got fed up with apt-get taking forever to time out against canonical servers and turned off IPv6 on my desktop with this command, by appending the following to my /etc/sysctl.conf,

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

and then running

$ sudo sysctl -p

But turning off IPv6 requires IPv6 to be turned off in the TFTP config file, by changing TFTP_ADDRESS to the value shown above; note that the port number does not change.

The server must serve up the kernel image and device tree, which I put in TFTP_DIRECTORY defined in the config file above.

/var/lib/tftpboot$ sudo cp ~/work/zynq/buildroot/output/images/uImage zedImage
/var/lib/tftpboot$ sudo cp ~/work/zynq/buildroot/output/images/zynq-zed-adv7511.dtb .

No, I still cannot make the ftp server follow symbolic link, sigh...  For sanity test, install tftp on the host, and try "get"-ting one of the above files into your home folder.  So here is a post-build script to ease the pain; change the folders according to your own setup:

sudo rm -rf /export/root/zed/; sudo mkdir /export/root/zed/
sudo tar -C /export/root/zed -xf /mnt/work/zynq/buildroot/output/images/rootfs.tar

sudo cp /mnt/work/zynq/buildroot/output/images/uImage /var/lib/tftpboot/zedImage

Troubleshooting

If TFTP suddenly stops working when you have not changed the target side, then suspect the TFTP server--it often does not start or crashes.  To check,

$ sudo service tftpd-hpa status
ftpd-hpa start/running, process 5930

It should show print a PID if it's really running, as in the above example.  If not, try stopping and restarting

$ sudo service tftpd-hpa restart

Manually boot off TFTP

Logically, U-Boot needs to know 2 things to boot:

  1. What files to download over TFTP, and which memory to stick them.
    1. ipaddr and serverip U-Boot environment variables stay the same as the NFS root file system case.
    2. Leveraging the knowledge gained from the previous post, I will continue to mount the root file system over NFS, so I only need to download the kernel image (uImage) and DTB (because my eval board is ARM).  Copied for convenience, the kernel image should be at 0x3000000, and the device tree image should be at 0x2A00000.
  2. The boot command specifying the memory address containing the kernel.  From previous post, I already have a working boot command.  If I keep the memory addresses the same, I do NOT have to modify the boot command.
So let's focus on downloading the uImage and DTB files to the target.  I stop U-Boot in the serial console during the 3 second wait.  The U-Boot command to download a file from the TFTP server is (surprise!) "tftpboot".  So first, the device tree blob:
zynq-uboot> tftpboot 0x2A00000 192.168.1.2:zynq-zed-adv7511.dtb

The the kernel image itself:
zynq-uboot> tftpboot 0x3000000 192.168.1.2:zedImage
Then we can simply boot from memory:

zynq-uboot> bootm
...
Welcome to Zeboard!

zed login:

Codify the success as U-Boot sdboot script

Remember that the FSBL is still necessary to program the FPGA bitstream and kick off U-Boot (the 2nd stage boot loader).  All these files are still on the SD card, so when U-Boot loads, it will find itself in sdboot mode.   There is a matching environment variable "sdboot" which I configured in the previous post to load the uImage and the DTB files from SD's BOOT partition, and kick off the bootm command (used above).  So for TFTP boot, I just have to modify this sdboot environment variable to what worked above:

setenv serverip 192.168.1.2
setenv ipaddr 192.168.1.9
setenv kernel_image "zedImage"
setenv devicetree_image "zynq-zed-adv7511.dtb"

setenv sdboot 'if mmcinfo; then run uenvboot; echo Copying files over TFTP to RAM && tftpboot 0x2A00000 ${serverip}:${devicetree_image} && tftpboot 0x3000000 ${serverip}:${kernel_image} && bootm 0x3000000 - 0x2A00000; fi'

saveenv
boot

Enjoy!

Oct 30, 2014

Understanding /dev/zero

The Linux /dev/zero pseudo file generates "0" bytes, as you can see in this example, where 4 zero bytes is strung together to give an impression of a ulong = 0:

henry@Zotac64:~$ od -vAn -N4 -tx4 /dev/zero
 00000000

The od command dumps file in octal and other formats "-N4" gets 4 bytes, and "-tx4" formats it as an 4 byte hex number.  strace shows that the system call used is read(), to grab 4 bytes:
...
open("/dev/zero", O_RDONLY)          = 3
read(3, "zv_\22", 4)                 = 4
...

How does it actually work under the hood?  Firstly, it is a mem device, as you can see under /sys folder:

henry@Zotac64:~$ ls -o /sys/class/mem/zero
... /sys/class/mem/zero -> ../../devices/virtual/mem/zero

It is a char mem device (major number 1), as you can see below:

henry@Zotac64:~$ ls -lh --time-style=+ /dev/zero
crw-rw-rw- 1 root root 1, 5  /dev/zero

Browsing the kernel source

The memory devices are defined <kernel>/drivers/char/mem.c:

static const struct memdev {
const char *name;
umode_t mode;
const struct file_operations *fops;
struct backing_dev_info *dev_info;
} devlist[] = {
... [3] = { "null", 0666, &null_fops, NULL },
... [5] = { "zero", 0666, &zero_fops, &zero_bdi },
[7] = { "full", 0666, &full_fops, NULL },
[8] = { "random", 0666, &random_fops, NULL },
[9] = { "urandom", 0666, &urandom_fops, NULL },
#ifdef CONFIG_PRINTK
[11] = { "kmsg", 0644, &kmsg_fops, NULL },
#endif
};

Note that the array indices ARE the minor device number appearing in the /dev/ folder.  The actual device creation is done en-mass in chr_dev_init():

err = bdi_init(&zero_bdi);
...
mem_class = class_create(THIS_MODULE, "mem");
...

for (minor = 1; minor < ARRAY_SIZE(devlist); minor++) {
...
device_create(mem_class, NULL, MKDEV(MEM_MAJOR, minor),

     NULL, devlist[minor].name);
...

device_create() registers it with sysfs, as we can check in /sys/class:

henry@Zotac64:~$ ls /sys/class/mem/
full  kmsg  mem  null  port  random  urandom  zero

chr_dev_init() is probably called by an indirection through the fs_initcall(chr_dev_init) section declaration, which inserts the function with a predefined pattern for the linker:

#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn; \
LTO_REFERENCE_INITCALL(__initcall_##fn##id)

The above macro will expand out to

static initcall_t __initcall_chr_dev_init5 __attribute((__section__(".initcall_chr_dev_init5.init"))) = chr_dev_init

that is, there is a static function pointer pointing to chr_dev_init.

/dev/zero file ops are in mem.c also:

static const struct file_operations zero_fops = {
.llseek = zero_lseek,
.read = read_zero,
.write = write_zero,
.aio_read = aio_read_zero,
.aio_write = aio_write_zero,
.mmap = mmap_zero,
};

For "zero", open/release ops are unnecessary, so the fops does NOT include them.  The zero file does not need to seek or write, so they are defined off appropriately:

#define zero_lseek null_lseek

#define write_zero write_null

#define aio_write_zero aio_write_null

The read clears the user buffer using either slow byte operation for < 64 bytes, or wholesale memset; for larger buffer, the clearing happens in chunks of PAGE_SIZE.

static ssize_t read_zero(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
...
if (!access_ok(VERIFY_WRITE, buf, count))

return -EFAULT;


while (count) {
unwritten = __clear_user(buf, chunk);
written += chunk - unwritten;
if (unwritten)
break;
if (signal_pending(current))
return written ? written : -ERESTARTSYS;
buf += chunk;
count -= chunk;
cond_resched();

}
return written ? written : -EFAULT;

}

cond_resched() allows an important task to preempt this thread--all part of being a considerate citizen.

Although I don't understand fully what it means to memory map a character device, it IS possible to mmap zero for shared mapping:



static int mmap_zero(struct file *file, struct vm_area_struct *vma) {
#ifndef CONFIG_MMU
    return -ENOSYS;
#endif
    if (vma->vm_flags & VM_SHARED)
        return shmem_zero_setup(vma);
    return 0;
}

Trace this, baby!

Let's make this blog entry a bit more interesting by tracing the interaction with the /dev/zero file.  The Ubuntu 12.04 LTS kernel I am running on my PC has some FTRACE capability, according to the config file saved in /boot"

henry@Zotac64:~$ grep FTRACE /boot/config-3.13.0-37-generic 
CONFIG_KPROBES_ON_FTRACE=y
CONFIG_HAVE_KPROBES_ON_FTRACE=y
# CONFIG_PSTORE_FTRACE is not set
CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
CONFIG_FTRACE=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_DYNAMIC_FTRACE=y
CONFIG_DYNAMIC_FTRACE_WITH_REGS=y
CONFIG_FTRACE_MCOUNT_RECORD=y
# CONFIG_FTRACE_STARTUP_TEST is not set

Low level tracing

According to the ftrace author Steven Rostedt (also this more advanced topic), CONFIG_DYNAMIC_FTRACE allows me to tap into any of the events in /sys/kernel/debug/tracing/available_events, which numbers over 1000 on my PC kernel!

# wc -l /sys/kernel/debug/tracing/available_events 
1114 /sys/kernel/debug/tracing/available_events

Of those, the read and write system calls (as shown in strace above) might be interesting:

syscalls:sys_exit_read
syscalls:sys_enter_read
syscalls:sys_exit_write
syscalls:sys_enter_write

Writing to trace buffer can be turned on/off by writing 1/0 to a file /sys/kernel/debug/tracing/tracing_on, but tracing itself is changed by writing the desired tracer to /sys/kernel/debug/tracing/current_tracer, which is nop on system startup:

# echo function_graph > current_tracer

function_graph tracer is more full featured than the simpler function tracer.

To synchronize the events with the user space, ftrace exposes a file /sys/kernel/debug/tracing/trace_marker, that can be written to like this example:

[tracing]# echo hello world > trace_marker
[tracing]# cat trace
# tracer: nop
#
#           TASK-PID    CPU#    TIMESTAMP  FUNCTION
#              | |       |          |         |
           <...>-3718  [001]  5546.183420: 0: hello world

If I want to do the equivalent from within the kernel, use the function trace_printk().  I found that what I wrote to the trace_marker does NOT show up in trace when I use function_graph tracer.

While tracing is enabled, the result can be viewed either in /sys/kernel/debug/tracing/per_cpu/cpu[0-9]/trace, or rolled up in /sys/kernel/debug/tracing/trace (might be confusing to look at).  To view the file AFTER tracing is turned off, you have to copy the trace into another file.

Putting everything together, a way to briefly trace reading 4 bytes from /dev/zero would be:

echo read_zero > set_ftrace_filter

echo 1 > tracing_on
echo function > current_tracer
echo "begin read zero" > trace_marker
od -vAn -N4 -tx4 /dev/zero
echo "end read zero" > trace_marker
echo 0 > tracing_on
cp trace /home/henry/read_zero.ftrace
echo nop > current_tracer
chown henry /home/henry/read_zero.ftrace
# echo > set_ftrace_filter # Undo the filter

Then look at the result in /sys/kernel/debug/tracing/trace

echo nop > current_tracer

Make it easy on your self: trace-cmd

I found that Steven Rosted wrote another another tool to ease data collection: trace-cmd.  

trace-cmd record -p function od -vAn -N4 -tx4 /dev/zero

The "-l" argument is the filter, and "-e" is the events; both can be repeated.  The rest of the command is the command to call: what you want to trace (in this example, it would be the string starting with "od").  To look at the result, I played around with kernelshark, but I found it more confusing than just the text output from trace-cmd report:

trace-cmd report | grep -v 'trace-cmd' > read_zero.txt

It shows that we fly through the open and then read of the /dev/zero in a few microseconds:

od-4878  [000] 22634.564764: sys_enter:            NR 2 ...
od-4878  [000] 22634.564764: sys_enter_open:...
...
od-4878  [000] 22634.564771: do_sys_open:          [FAILED TO PARSE] filename=/dev/zero flags=32768 mode=438
...
od-4878  [000] 22634.564772: sys_exit:             NR 2 = 3
od-4878  [000] 22634.564772: sys_exit_open:        0x3
...
od-4878  [000] 22634.564782: sys_enter:            NR 0 (3, 22612f0, 4, 7ffff7fd18c0, 1, 3)
od-4878  [000] 22634.564782: sys_enter_read: ...
od-4878  [000] 22634.564784: function:             read_zero
od-4878  [000] 22634.564784: sys_exit:             NR 0 = 4
od-4878  [000] 22634.564785: sys_exit_read:        0x4

The whole read took 3 us (22634.564785 - 22634.564782)!  The read() system call number is apparently 3.  If I remove the filter option, I can see the call graph like this example:

trace-cmd record -p function -e "irq:*" od -vAn -N4 -tx4 /dev/zero
25182.162030:              SyS_read
25182.162031:                 fget_light
25182.162031:                 vfs_read
25182.162031:                    rw_verify_area
25182.162031:                       security_file_permission
25182.162032:                          apparmor_file_permission
25182.162032:                             common_file_perm
25182.162032:                                aa_file_perm
25182.162032:                          __fsnotify_parent
25182.162032:                          fsnotify
25182.162033:                    read_zero
25182.162033:                       read_zero.part.5
25182.162033:                          __clear_user
25182.162033:                          _cond_resched
25182.162034:                    __fsnotify_parent
25182.162034:                    fsnotify

This call graph matches the source code we browsed earlier.  But I am disappointed about not seeing any interrupts so far; what does it look like in ftrace?  I think that system call involves raising a SWI (vector 11 on ARM), which handled in assembly (for ARM: <kernel>/arch/arm/kernel/entry-common.S, vector_swi function, which should call sys_read but calls sys_ni_syscall if system call is not implmented).  That sys_read is linked (through clever assembly NR_syscalls numbering in entry-common.S) to the read function defined in <kernel>/fs/read_write.c:

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
...

Conclusions

  • The read system call sure is fast at least when it does not actually have to read anything.
  • Tentatively, I cannot see the SWI interrupt through ftrace.