commenting out a printk statement causes crash in a linux device driver test

Solution for commenting out a printk statement causes crash in a linux device driver test
is Given Below:

I’m seeing a weird case in a simple linux driver test(arm64).
The user program calls ioctl of a device driver and passes array ‘arg’ of uint64_t as argument. By the way, arg[2] contains a pointer to a variable in the app. Below is the code snippet.

    case SetRunParameters:
        copy_from_user(args, (void __user *)arg, 8*3);
        offs = args[2] % PAGE_SIZE;
        down_read(&current->mm->mmap_sem);
        res = get_user_pages( (unsigned long)args[2], 1, 1, &pages, NULL);
        if (res) {
            kv_page_addr = kmap(pages);
            kv_addr = ((unsigned long long int)(kv_page_addr)+offs);
            args[2] = page_to_phys(pages) + offset; // args[2] changed to physical
        }
        else {
            printk("get_user_pages failed!n");
        }
        up_read(&current->mm->mmap_sem);
        *(vaddr + REG_IOCTL_ARG/4) = virt_to_phys(args);  // from axpu_regs.h
        printk("ldd:writing %x at %pxn",cmdx,vaddr + REG_IOCTL_CMD/4); // <== line 248. not ok w/o this printk line why?..
        *(vaddr + REG_IOCTL_CMD/4) = cmdx;  // this command is different from ioctl cmd!
        put_page(pages); //page_cache_release(page);
        break;
    case ...

I have marked line 248 in above code. If I comment out the printk there, a trap occurs and the virtual machine collapses(I’m doing this on a qemu virtual machine). The cmdx is a integer value set according to the ioctl command from the app, and vaddr is the virtual address of the device (obtained from ioremap). If I keep the printk, it works as I expect. What case can make this happen? (cache or tlb?)

Accessing memory-mapped registers by simple C constructs such as *(vaddr + REG_IOCTL_ARG/4) is a bad idea. You might get away with it on some platforms if the access is volatile-qualified, but it won’t work reliably or at all on some platforms. The proper way to access memory-mapped registers is via the functions declared by #include <asm/io.h> or #include <linux/io.h>. These will take care of any arch-specific requirements to ensure that writes are properly ordered as far as the CPU is concerned1.

The functions for memory-mapped register access are described in the Linux kernel documentation under Bus-Independent Device Accesses.

This code:

        *(vaddr + REG_IOCTL_ARG/4) = virt_to_phys(args);
        *(vaddr + REG_IOCTL_CMD/4) = cmdx;

can be rewritten as:

        writel(virt_to_phys(args), vaddr + REG_IOCTL_ARG/4);
        writel(cmdx, vaddr + REG_IOCTL_CMD/4);

1 Write-ordering for specific bus types such as PCI may need extra code to read a register inbetween writes to different registers if the ordering of the register writes is important. That is because writes are “posted” asynchronously to the PCI bus, and the PCI device may process writes to different registers out of order. An intermediate register read will not be handled by the device until all preceding writes have been handled, so it can be used to enforce ordering of posted writes.