Device Tree Debugging

DTB-DTS conversion (Linux kernel)

  • Convert DTS file to DTB
  • ./scripts/dtc/dtc -I dts -O dtb -o .dtb .dts
  • Convert DTB file to DTS

  • ./scripts/dtc/dtc -I dtb -O dts -o .dts .dtb

 U-boot FDT commands

  • print fdt address
  • print $fdt_addr_r (or $fdt_addr)
  • Set FDT node property
  • fdt addr $fdt_addr
    fdt set /testnode testprop testvalue
  • remove FDT node property
  • fdt addr $fdt_addr
    fdt rm /testnode testprop
  • Remove DT node
  • fdt rm /testnode
  • List FDT nodes
  • fdt list                         /* All nodes */
    fdt list /cpus                   /* All nodes under cups node */
  • Create node
  • fdt mknode / testnode

References:

How to check Memory Leak in linux kernel

  • Compile kernel with
    CONFIG_DEBUG_KMEMLEAK=y
    #CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y
    CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=4096
    
  • Mount debugfs (if it is not mounted)
    mount -t debugfs nodev /sys/kernel/debug/
    
  • Check memory leaks
    # echo scan > /sys/kernel/debug/kmemleak
    # cat /sys/kernel/debug/kmemleak
    

Example Trace:

unreferenced object 0xffff88021481d600 (size 16):
comm “swapper/0”, pid 1, jiffies 4294667622 (age 9531.310s)
hex dump (first 16 bytes):
70 6e 70 20 30 30 3a 30 31 00 38 81 ff ff ff ff pnp 00:01.8…..
backtrace:
[<ffffffff811619cf>] create_object+0xff/0x2a0
[<ffffffff8189ba63>] kmemleak_alloc+0x23/0x40
[<ffffffff8115bf37>] kmem_cache_alloc_trace+0xb7/0x160
[<ffffffff81395fe9>] reserve_range+0x59/0xf0
[<ffffffff81396102>] system_pnp_probe+0x82/0x90
[<ffffffff81392447>] pnp_device_probe+0x67/0xd0
[<ffffffff814c2a7f>] driver_probe_device+0x13f/0x2a0
[<ffffffff814c2ea5>] __device_attach_driver+0x85/0xa0
[<ffffffff814c0fa7>] bus_for_each_drv+0x57/0x90
[<ffffffff814c28d0>] __device_attach+0xb0/0xf0
[<ffffffff814c2ece>] device_initial_probe+0xe/0x10
[<ffffffff814c1f85>] bus_probe_device+0x95/0xa0
[<ffffffff814c00ce>] device_add+0x36e/0x570
[<ffffffff814c0469>] device_register+0x19/0x20
[<ffffffff813913d6>] __pnp_add_device+0x96/0xe0
[<ffffffff8139145b>] pnp_add_device+0x3b/0x100

MISC DRIVER

Misc driver is the simplest character driver. I am giving  a very simple example of misc driver.

Example misc driver creates a /dev/miscdev node which can be read or write. When read it reads kernel buffer data and when written, it updates kernel data with new data.

More information regarding misc driver can be found at http://www.linuxjournal.com/article/2920


#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>

static char kern_data[] = "This is kernel data\n";

static struct miscdevice misc_dev;

static int misc_open(struct inode *inode, struct file *filp)
{
	pr_info("%s() misc device open\n", __func__);
	return 0;
}

static int misc_release(struct inode *inode, struct file *filp)
{
	pr_info("%s() misc device release\n", __func__);
	return 0;
}

static ssize_t misc_read(struct file *filp, char __user *buf,
					size_t count, loff_t *f_pos)
{
	pr_info("%s() misc device read\n", __func__);

	return simple_read_from_buffer(buf, strlen(kern_data), f_pos,
						kern_data, strlen(kern_data));
}

static ssize_t misc_write(struct file *filp, const char __user *buf,
				size_t count, loff_t *f_pos)
{
	int nbytes;

	pr_info("%s() misc device write\n", __func__);

	nbytes = simple_write_to_buffer(kern_data, sizeof(kern_data),
							f_pos, buf, count);
	kern_data[count] = '';

	return count;
}

static const struct file_operations misc_fops = {
	.owner = THIS_MODULE,
	.read = misc_read,
	.write = misc_write,
	.open = misc_open,
	.release = misc_release,
};

static struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "miscdev",
	.fops = &misc_fops,
};

static __init int misc_driver_init(void)
{
	int rc = 0;

	pr_info("%s()\n", __func__);

	rc = misc_register(&misc_dev);
	if (rc) {
		pr_err("%s() misc_register() fail %d\n", __func__, rc);
		return rc;
	}

	pr_info("%s() misc dev minor number = %u\n", __func__, misc_dev.minor);

	return rc;
}

static __exit void misc_driver_exit(void)
{
	pr_info("%s()\n", __func__);

	misc_deregister(&misc_dev);
}

module_init(misc_driver_init);
module_exit(misc_driver_exit);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Simple Misc Character Driver");
MODULE_LICENSE("GPL");

Output:
=======

$ cat /dev/miscdev
This is kernel data

$ echo "This is new data" > /dev/miscdev

$ cat /dev/miscdev
This is new data

Difference between mutex vs spinlocks

Copied From: http://algorithmsandme.com/2014/01/locking-mutex-vs-spinlocks/
What is spin-lock? Spin lock is a mechanism in which the process needing a resource poll the lock on resource till it gets it. It is also called as busy-waiting. Process will be busy in a loop till it gets the resource.
So we get the first difference there itself, the way both wait for the resource lock. In case of mutex, process goes in wait state and does not use processor while in spin-lock, process keeps on looping and hence uses the processor even while waiting.
Second difference comes from : when should we use spin-lock or mutex?
Spin-locks are best used when a piece of code cannot go to sleep state. Best example of such code would be interrupts request handlers.
Mutexes are best used in user space program where sleeping of process does not affect the system in catastrophic way.
Spin locks make sense when we have multi-processor systems or we have uni-processor system with preemption enabled, spin locks used in uni-processor systems without preemption enabled, may lead to deadlock where process goes on spinning forever. Following table summarizes the above points.

Mutex Vs Spinlock

Criteria
Mutex
Spinlock
Mechanism
Test for lock.
If available, use the resource
If not, go to wait queue
Test for lock.
If available, use the resource.
If not, loop again and test the lock till you get the lock
When to use
Used when putting process is not harmful like user space programs.
Use when there will be considerable time before process gets the lock.
Used when process should not be put to sleep like Interrupt service routines.
Use when lock will be granted in reasonably short time.
Drawbacks
Incur process context switch and scheduling cost.
Processor is busy doing nothing till lock is granted, wasting CPU cycles.

LINUX KERNEL LOCKING

Some of them are:

  • seq_lock
  • RCU (Read-Copy-Update)

SEQ_LOCK:
==========
Seqlocks work in situations where the resource to be protected is small, simple, and frequently accessed, and where write access is rare but must be fast. Essentially, they work by allowing readers free access to the resource but requiring those readers to check for collisions with writers and, when such a collision happens, retry their access. Seqlocks generally cannot be used to protect data structures involving pointers, because the reader may be following a pointer that is invalid while the writer is changing the data structure.

How it works:
——————

  • Reader obtains sequence value before reading
  • Reader reads value
  • Readers compares obtained sequence value with current value
  • If there is mismatch, read access is retried
unsigned int seq;
do {
seq = read_seqbegin(&seq_lock);
/* Do what you need to do */
} while read_seqretry(&seq_lock, seq);

– Writers need to get seqlock before writing data. Writers can use below callbacks (as well as other variants based on requirements) to lock and unlock seqlock.

void write_seqlock(seqlock_t *lock);
void write_sequnlock(seqlock_t *lock);

If it is required to use seqlock from IRQ, IRQ-safe versions can be used.

unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);

References:
http://www.makelinux.net/ldd3/chp-5-sect-7

Read-Copy-Update:
================

Provides high performance when used at proper place:

  • There are many constrains on data structures which RCU can protect.
  • Optimized for situations where reads are common and writes are rare
  • The resources being protected should be accessed via pointer
  • All references to those resources must be held only by atomic code

When data structure need to be changed, writing thread makes a copy of current
data, changes data in copy and change the read pointer.

  • On the reader side,
    – rcu_read_lock();
    – data = dereference_data_using_rcu_callback(…);
    – do_something_with_data(data);
    – rcu_read_unlock();
  • On Write side,- allocate memory for new data
    – copy old data to new memory
    – update data in new memory
    – update rcu data pointer using rcu callback
    – register a RCU call for to free old data memory (Alternate is
    synchronize_rcu() but this will block thread until all readers are done)

How to make sure that all readers are done before freeling old memory:
————————————————————————————————

  1. This can be done using synchronize_rcu() callback which will block thread
    until all readers are done.
  2. call_rcu() through which a callback can be registered will be invoked after
    the completion of all RCU read-side critical sections that might be
    referencing that data item.

References:

http://www.rdrop.com/users/paulmck/rclock/intro/rclock_intro.html
http://www.rdrop.com/~paulmck/RCU/whatisRCU.html

How to Change Linux Kernel dmesg buffer size

Printk is implemented by using a ring buffer in the kernel with a size of __LOG_BUF_LEN bytes where __LOG_BUF_LEN equals (1 << CONFIG_LOG_BUF_SHIFT).

dmesg buffer size can be changed using “CONFIG_LOG_BUF_SHIFT” Kconfig.

As per kernel source (4.1.0), default value of this config is 17 (i.e. 1 << 17 = 128KB). The range for this config is 12 to 21.

Addign  below line in defconfig sets

Note: If invalid value (out of range value) is set, default value is taken.

CONFIG_LOG_BUF_SHIFT can be changed from menuconfig or from defconfig as below:

CONFIG_LOG_BUF_SHIFT=21

Building and Installing linux kernel

It is always exciting to add something (at least a print message) in kernel and see your own kernel image booting… Well it is not difficult task. Following few steps you can build and install kernel.

  • Find proper defconfig according to your system configuration. For x86-64 bit architecture, x86_64_defconfig is used.
  • Configure kernel
    # make ARCH=x86 x86_64_defconfig
  • Compile kernel
    # make ARCH=x86
  • Install Kernel
    # sudo make modules_install
    # sudo make install

This command automatically updates grub and adds entry for new installed kernel. TO be safer, it is recommanded that you add some delay in grub.cfg so that if newly installed kernel cannot boot due to some reason, you can get a chance to boot using other kernel. If this delay is 0, grub will boot default kernel without prompt to select kernel.

+ set timeout=5
  ### END /etc/grub.d/30_os-prober ###

You can also set timeout in /etc/default/grub

GRUB_TIMEOUT=10