Device Drivers, Part 7: Generic Hardware Access in Linux

Accessing hardware in Linux

This article, which is part of the series on Linux device drivers, talks about accessing hardware in Linux.

Shweta was all jubilant about her character driver achievements, as she entered the Linux device drivers laboratory on the second floor of her college. Many of her classmates had already read her blog and commented on her expertise. And today was a chance to show off at another level. Till now, it was all software — but today’s lab was on accessing hardware in Linux.

In the lab, students are expected to learn “by experiment” how to access different kinds of hardware in Linux, on various architectures, over multiple lab sessions. Members of the lab staff are usually reluctant to let students work on the hardware straight away without any experience — so they had prepared some presentations for the students (available here).

Generic hardware interfacing

As every one settled down in the laboratory, lab expert Priti started with an introduction to hardware interfacing in Linux. Skipping the theoretical details, the first interesting slide was about generic architecture-transparent hardware interfacing (see Figure 1).

Hardware mapping

Figure 1: Hardware mapping

The basic assumption is that the architecture is 32-bit. For others, the memory map would change accordingly. For a 32-bit address bus, the address/memory map ranges from 0 (0x00000000) to “232 – 1″ (0xFFFFFFFF). An architecture-independent layout of this memory map would be like what’s shown in Figure 1 — memory (RAM) and device regions (registers and memories of devices) mapped in an interleaved fashion. These addresses actually are architecture-dependent. For example, in an x86 architecture, the initial 3 GB (0x00000000 to 0xBFFFFFFF) is typically for RAM, and the later 1GB (0xC0000000 to 0xFFFFFFFF) for device maps. However, if the RAM is less, say 2GB, device maps could start from 2GB (0x80000000).

Run cat /proc/iomem to list the memory map on your system. Run cat /proc/meminfo to get the approximate RAM size on your system. Refer to Figure 2 for a snapshot.

Physical and bus addresses on an x86 system

Figure 2: Physical and bus addresses on an x86 system

Irrespective of the actual values, the addresses referring to RAM are termed as physical addresses, and those referring to device maps as bus addresses, since these devices are always mapped through some architecture-specific bus — for example, the PCI bus in the x86 architecture, the AMBA bus in ARM architectures, the SuperHyway bus in SuperH architectures, etc.

All the architecture-dependent values of these physical and bus addresses are either dynamically configurable, or are to be obtained from the data-sheets (i.e., hardware manuals) of the corresponding architecture processors/controllers. The interesting part is that in Linux, none of these are directly accessible, but are to be mapped to virtual addresses and then accessed through them — thus making the RAM and device accesses generic enough. The corresponding APIs (prototyped in <asm/io.h>) for mapping and unmapping the device bus addresses to virtual addresses are:

void *ioremap(unsigned long device_bus_address, unsigned long device_region_size);
void iounmap(void *virt_addr);

Once mapped to virtual addresses, it depends on the device datasheet as to which set of device registers and/or device memory to read from or write into, by adding their offsets to the virtual address returned by ioremap(). For that, the following are the APIs (also prototyped in <asm/io.h>):

unsigned int ioread8(void *virt_addr);
unsigned int ioread16(void *virt_addr);
unsigned int ioread32(void *virt_addr);
unsigned int iowrite8(u8 value, void *virt_addr);
unsigned int iowrite16(u16 value, void *virt_addr);
unsigned int iowrite32(u32 value, void *virt_addr);

Accessing the video RAM of ‘DOS’ days

After this first set of information, students were directed for the live experiments. The suggested initial experiment was with the video RAM of “DOS” days, to understand the usage of the above APIs.

Shweta got onto the system and went through /proc/iomem (as in Figure 2) and got the video RAM address, ranging from 0x000A0000 to 0x000BFFFF. She added the above APIs, with appropriate parameters, into the constructor and destructor of her existing “null” driver, to convert it into a “vram” driver. Then she added the user access to the video RAM through read and write calls of the “vram” driver; here’s her new file — video_ram.c:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <asm/io.h>

#define VRAM_BASE 0x000A0000
#define VRAM_SIZE 0x00020000

static void __iomem *vram;
static dev_t first;
static struct cdev c_dev;
static struct class *cl;

static int my_open(struct inode *i, struct file *f)
{
    return 0;
}
static int my_close(struct inode *i, struct file *f)
{
    return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
    int i;
    u8 byte;

    if (*off >= VRAM_SIZE)
    {
        return 0;
    }
    if (*off + len > VRAM_SIZE)
    {
        len = VRAM_SIZE - *off;
    }
    for (i = 0; i < len; i++)
    {
        byte = ioread8((u8 *)vram + *off + i);
        if (copy_to_user(buf + i, &byte, 1))
        {
            return -EFAULT;
        }
    }
    *off += len;

    return len;
}
static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off)
{
    int i;
    u8 byte;

    if (*off >= VRAM_SIZE)
    {
        return 0;
    }
    if (*off + len > VRAM_SIZE)
    {
        len = VRAM_SIZE - *off;
    }
    for (i = 0; i < len; i++)
    {
        if (copy_from_user(&byte, buf + i, 1))
        {
            return -EFAULT;
        }
        iowrite8(byte, (u8 *)vram + *off + i);
    }
    *off += len;

    return len;
}

static struct file_operations vram_fops =
{
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_close,
    .read = my_read,
    .write = my_write
};

static int __init vram_init(void) /* Constructor */
{
    if ((vram = ioremap(VRAM_BASE, VRAM_SIZE)) == NULL)
    {
        printk(KERN_ERR "Mapping video RAM failed\n");
        return -1;
    }
    if (alloc_chrdev_region(&first, 0, 1, "vram") < 0)
    {
        return -1;
    }
    if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)
    {
        unregister_chrdev_region(first, 1);
        return -1;
    }
    if (device_create(cl, NULL, first, NULL, "vram") == NULL)
    {
        class_destroy(cl);
        unregister_chrdev_region(first, 1);
        return -1;
    }

    cdev_init(&c_dev, &vram_fops);
    if (cdev_add(&c_dev, first, 1) == -1)
    {
        device_destroy(cl, first);
        class_destroy(cl);
        unregister_chrdev_region(first, 1);
        return -1;
    }
    return 0;
}

static void __exit vram_exit(void) /* Destructor */
{
    cdev_del(&c_dev);
    device_destroy(cl, first);
    class_destroy(cl);
    unregister_chrdev_region(first, 1);
    iounmap(vram);
}

module_init(vram_init);
module_exit(vram_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("Video RAM Driver");

Summing up

Shweta then repeated the usual steps:

  1. Build the “vram” driver (video_ram.ko file) by running make with a changed Makefile.
  2. Load the driver using insmod video_ram.ko.
  3. Write into /dev/vram, say, using echo -n "0123456789" > /dev/vram.
  4. Read the /dev/vram contents using od -t x1 -v /dev/vram | less. (The usual cat /dev/vram can also be used, but that would give all the binary content. od -t x1 shows it as hexadecimal. For more details, run man od.)
  5. Unload the driver using rmmod video_ram.

With half an hour still left for the end of the practical class, Shweta decided to walk around and possibly help somebody else with their experiments.

  • yuva

    Hi Anil ,

    I am reading your article from the beginning . I am happy with your sharing. Your article is really good .

    I have some queries please clarify.

    From the following link i came to know that there are three ways for addressing memory .

    http://tldp.org/LDP/khg/HyperNews/get/devices/addrxlate.html

    I want to understand what is “virtual address” and “bus address” clearly .

    Regards,

    A.Yuvaraj

    • http://sosaysharis.wordpress.com/ Haris Ibrahim K. V.

       Hi Yuva,

      I’m a bit confused regarding the ‘bus address’ terminology. There are ‘address buses’ that carry addresses between different devices. Basically a 16-bit system having 16 parallel wires to carry this. I believe this tech has been improved by multiplexing the addresses and hence using lesser number of wires in parallel.

      Where as ‘virtual address’ refers to the address space the processes have within the virtual memory. The concept of virtual memory can be viewed as ‘enlarging the capacity of RAM’. So the part of the processes that are needed for execution are in the RAM while those parts that are not currently needed will be in their corresponding virtual addresses ready to be mapped onto the RAM.

      My answer is not accurate and can be wrong too. Please discuss and let us reach at the correct answer.

      • http://twitter.com/anil_pugalia Anil Pugalia

        Bus Addresses are the addresses coming on the bus – e.g. on PCI Bus for x86 architecture. They are the real hardware addresses. Virtual addresses are only conceptual. They only exist in the page tables used by the MMU for ultimately getting translated into hardware addresses, which could be bus addresses or physical RAM addresses.

  • yuva

    Hi Anil ,

    I am reading your article from the beginning . I am happy with your sharing. Your article is really good .

    I have some queries please clarify.

    From the following link i came to know that there are three ways for addressing memory .

    http://tldp.org/LDP/khg/HyperNews/get/devices/addrxlate.html

    I want to understand what is “virtual address” and “bus address” clearly .

    Regards,

    A.Yuvaraj

  • http://sosaysharis.wordpress.com/ Haris Ibrahim K. V.

    I believe the meaning of the output of “od -t x1 -v /dev/vram | less” should be explained so as to how it relates with the “0123456789″ that we wrote into our driver.

    • http://www.facebook.com/lokesh.walasepatil Lokesh WalasePatil

      Yes, even I m not understanding what the o/p should be. Pls someone explain it !! :)

      • http://twitter.com/anil_pugalia Anil Pugalia

        Easier way for understanding the same would be to try xxd. Just do the following:
        xxd /dev/vram | less
        What exactly we are doing with all these, is reading & display the vide ram content, in more readable ways, like in hex, in ASCII, etc

  • mrr

    Worth to mention that if you couldn’t find “Video RAM” in /proc/iomem, try to run `lspci -v` and find video device info:

    bash# lspci -v
    00:02.0 VGA compatible controller: Cirrus Logic GD 5446
    Subsystem: Qumranet, Inc. Device 1100
    Flags: fast devsel
    Memory at e0000000 (32-bit, prefetchable) [size=32M]
    Memory at e2000000 (32-bit, non-prefetchable) [size=4K]
    Expansion ROM at e2010000 [disabled] [size=64K]
    Kernel modules: cirrusfb

    and with device number ’00:02.0′ you can find it now in /proc/iomem:

    bash# grep 00:02.0 /proc/iomem
    e0000000-e1ffffff : 0000:00:02.0
    e2000000-e2000fff : 0000:00:02.0
    e2010000-e201ffff : 0000:00:02.0

    Memory at 0xe0000000 is good enough for the article example.

    • http://twitter.com/anil_pugalia Anil Pugalia

      What you pointed out is correct. But we do not want to play & goof up with the ram of the currently being used video card. And that’s where, looking for the unused video ram from the “DOS days”, which is at the location as mentioned in the article.

  • Buk

    Great tutorials but it would be better if you could give the output screen after od -t x1 -v /dev/vram | less command. İ am not sure if i did any mistake because there isn’ t much difference between writing echo 0123456789 and echo 55

    • http://www.facebook.com/anil.pugalia Anil Pugalia

      If there is no change – that’s very much possible, if the underlying system doesn’t have that Video RAM physically, present.

  • sandeep

    excellent work sir please try to upload the same with linux internals also

    • http://www.facebook.com/anil.pugalia Anil Pugalia

      Thanks. And what do you mean by upload same with linux internals?

  • plr

    Hi Anil ,

    I had a doubt regarding the read function .

    ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)

    here len tells the number of bytes to be read .

    and what is loff_t *off field meant for ?

    I thot it is the offset from which the byte has to be read . But
    if (*off >= VRAM_SIZE)
    {
    return 0;
    }

    gives a different meaning .

    here it refers to number of bytes to be read if Im not wrong…

    Please clarify regarding this… I kno its a very small thing but Im stuck right at this point…

    • plr

      hi sir, I understood it myself… Here off is the offset from the base address .
      (off > base address) means its going beyond the mapping for vram ….

      • anil_pugalia

        Yes, you understood correct – (off_from_base_address >= VRAM_SIZE) will go beyond the VRAM mapping.

  • SATHEESARAN

    Hi Anil,

    So whatever we write to this device driver is written to memory of the device [bus address], so how would the visual display unit interprets it and shows in the screen ?

    One more thing is, I could not understand the data when I read from video driver.

    I wrote , “0123456789″ to driver and I read from driver using the command,

    xxd.

    Output goes like this,
    0000000: 0100 0000 3435 3637 3839 0000 0000 0000 ….456789……

    How could I interpret the pattern “0123456789″ from above read input ?

    Thanks,
    Satheesaran

    • http://twitter.com/anil_pugalia Anil Pugalia

      Centre is the ASCII values in hex, and right is the characters.

  • linux_bot

    how is VRAM_SIZE = 0×00020000 is calculated, when subtracted from
    0x000BFFFF – 0x000A0000 = 1ffff

    • anil_pugalia

      You need to add 1 to the result of subtraction. Why? Here’s the logic: If an address starts from A0000 and ends at A0001, then they are 2 addresses and hence the size is 2; and we calculate as follows (A0001 – A0000) + 1 = 2.

  • srikanth

    static void __iomem *vram;
    what is __iomem ? I understood that vram is void pointer, but __iomem signifies what?

    • anil_pugalia

      When tagged with __iomem, it enables that pointer for compiler checks &/or optimizations, relevant for I/O mapped memory.

  • santosh yadav

    hello everyone!

    i am trying to do gpio access and encounter segmentation fault.

    so to overcome it i will have to map the physical address to logical and for that i made use of ioremap().

    but while i am using it the kernel is unable to locate the ‘asm/io.h’ header file

    so what should i do to include the header so that it may work.

    • anil_pugalia

      I assume that you have set up the kernel headers path appropriately. If not, do it. If yes, have you configured the kernel? Which architecture is it for?

  • Jai Shiv

    When I loaded the module ,It was fine.But while writing into it i got the following error.This is snapshot of dmesg.
    Addtional Info: x86 Architecture
    Please help me why this happened.

    ********************************SNAPSHOT *****************************************

    [ 8158.867448] VRAM’s OPENed
    [ 8158.867604] BUG: unable to handle kernel NULL pointer dereference at 00000030
    [ 8158.867666] IP: [] do_sys_open+0×65/0×110
    [ 8158.867692] *pdpt = 0000000031400001 *pde = 0000000000000000
    [ 8158.867710] Oops: 0000 [#3] PREEMPT SMP
    [ 8158.867721] last sysfs file: /sys/devices/pci0000:00/0000:00:11.0/0000:02:03.0/class
    [ 8158.867741] Modules linked in: VIDEO_RAM af_packet snd_ens1371 gameport snd_rawmidi snd_seq_device snd_ac97_codec ac97_bus snd_pcm snd_timer ppdev snd parport_pc soundcore sr_mod joydev parport floppy pcnet32 vmware_balloon snd_page_alloc pcspkr shpchp cdrom sg pci_hotplug i2c_piix4 container ac button autofs4 processor thermal thermal_sys ata_generic mptspi mptscsih mptbase scsi_transport_spi [last unloaded: VIDEO_RAM]
    [ 8158.867828]
    [ 8158.867840] Pid: 8373, comm: od Tainted: G D 2.6.34.14-JAI_KERNEL #1 440BX Desktop Reference Platform/VMware Virtual Platform
    [ 8158.867852] EIP: 0060:[] EFLAGS: 00210207 CPU: 0
    [ 8158.867862] EIP is at do_sys_open+0×65/0×110
    [ 8158.867870] EAX: 00000024 EBX: 00000003 ECX: 000012ed EDX: c081d4c0
    [ 8158.867878] ESI: 00000020 EDI: ffffff9c EBP: f1989f94 ESP: f1989f70
    [ 8158.867886] DS: 007b ES: 007b FS: 00d8 GS: 0033 SS: 0068
    [ 8158.867896] Process od (pid: 8373, ti=f1988000 task=f1448e90 task.ti=f1988000)
    [ 8158.867903] Stack:
    [ 8158.867907] 00000000 00000024 00000000 00000024 c0300ee9 ef8be000 000001b6 00008000
    [ 8158.867923] bf9bf76c f1989fac c03021e9 000001b6 bf9bf76c 08059388 000001b6 f1988000
    [ 8158.867942] c065e31d bf9bf76c 00008000 000001b6 08059388 000001b6 00000008 00000005
    [ 8158.867961] Call Trace:
    [ 8158.867972] [] ? filp_close+0×49/0×70
    [ 8158.867984] [] ? sys_open+0×29/0×40
    [ 8158.867998] [] ? syscall_call+0×7/0xb
    [ 8158.868004] Code: 8b 55 f0 c7 44 24 04 00 00 00 00 89 04 24 89 f8 e8 61 c3 00 00 3d 00 f0 ff ff 89 45 e8 0f 87 9f 00 00 00 8b 45 e8 be 20 00 00 00 78 0c 8b 47 10 89 45 ec 0f b7 40 72 c7 44 24 04 00 00 00 00
    [ 8158.868067] EIP: [] do_sys_open+0×65/0×110 SS:ESP 0068:f1989f70
    [ 8158.868067] CR2: 0000000000000030
    [ 8158.868243] —[ end trace cd712ffc55310a71 ]—

    • JAI SHIV

      Kindly provide some reference to understand this crash dump.
      Please look into the matter ASAP.

      • anil_pugalia

        Are you running Linux over a virtualbox or vm?

        • brajesh

          i am getting the same error {BUG: unable to handle kernel NULL pointer dereference at (null)} and i am running linux over virtual box , s owhats the problem ?

          • brajesh

            and even when i am not running linux over Vbox same error is coming as shown above , please have a look

          • anil_pugalia

            The log above shows that there is something wrong in the open call itself, even before the write has actually happened. Do you also get the crash at the same place?

            And that looks really weird, as open is just an empty call. Have you guys done any changes in the open?

          • brajesh

            open function is working fine i guess cause i have not changed anything in open function . and i can see the open msg in dmesg , pls suggest

          • brajesh

            and i get the crash once i give cmd to write in vram dev file ,

          • anil_pugalia

            I wonder why is the crash log showing as crashed in open.

          • brajesh Sharma

            i am getting different error now sir , immediately after giving echo (write) command a black console comes in front showing error Unable to dereference the NULL pointer

          • anil_pugalia

            That’s the same Oops in a different style. If it is not too serious as earlier, I guess you can switch to the graphics back by any one of the following: Alt-F1, Alt-F2, …, Alt-F8.

  • Ameya

    Hi I am a newbiein device driver and my cat /proc/iomem gives me the following result

    I couldnt find video ram area in it rather it shows pci bus

    so when i insert my device and do cat /dev/vram my pc restarts

    I am using ubuntu 64 bit with amd processor

    00000000-0000ffff : reserved
    00010000-0009f3ff : System RAM
    0009f400-0009ffff : reserved
    000a0000-000bffff : PCI Bus 0000:00
    000c0000-000cffff : Video ROM
    000d0000-000dffff : PCI Bus 0000:00
    000e6000-000fffff : reserved
    000f0000-000fffff : System ROM
    00100000-bff8ffff : System RAM
    01000000-0168dae1 : Kernel code
    0168dae2-01cdbfff : Kernel data
    01dcd000-01f2dfff : Kernel bss
    b4000000-b7ffffff : GART
    bff90000-bff9dfff : ACPI Tables
    bff9e000-bffdffff : ACPI Non-volatile Storage
    bffe0000-bfffffff : reserved
    c0000000-dfffffff : PCI Bus 0000:00
    c0000000-cfffffff : PCI Bus 0000:01
    c0000000-cfffffff : 0000:01:05.0
    d0000000-dfffffff : PCI Bus 0000:02
    d0000000-dfffffff : 0000:02:00.0
    e0000000-efffffff : PCI MMCONFIG 0000 [bus 00-ff]
    e0000000-efffffff : pnp 00:0c
    f0000000-febfffff : PCI Bus 0000:00
    fdf00000-fdffffff : PCI Bus 0000:03
    fdff8000-fdffbfff : 0000:03:00.0
    fdff8000-fdffbfff : r8169
    fdfff000-fdffffff : 0000:03:00.0
    fdfff000-fdffffff : r8169
    fe7f4000-fe7f7fff : 0000:00:14.2
    fe7f4000-fe7f7fff : ICH HD audio
    fe7fa000-fe7fafff : 0000:00:14.5
    fe7fa000-fe7fafff : ohci_hcd
    fe7fb000-fe7fbfff : 0000:00:13.1
    fe7fb000-fe7fbfff : ohci_hcd
    fe7fc000-fe7fcfff : 0000:00:13.0
    fe7fc000-fe7fcfff : ohci_hcd
    fe7fd000-fe7fdfff : 0000:00:12.1
    fe7fd000-fe7fdfff : ohci_hcd
    fe7fe000-fe7fefff : 0000:00:12.0
    fe7fe000-fe7fefff : ohci_hcd
    fe7ff400-fe7ff4ff : 0000:00:13.2
    fe7ff400-fe7ff4ff : ehci_hcd
    fe7ff800-fe7ff8ff : 0000:00:12.2
    fe7ff800-fe7ff8ff : ehci_hcd
    fe7ffc00-fe7fffff : 0000:00:11.0
    fe7ffc00-fe7fffff : ahci
    fe800000-fe9fffff : PCI Bus 0000:01
    fe800000-fe8fffff : 0000:01:05.0
    fe9e8000-fe9ebfff : 0000:01:05.1
    fe9e8000-fe9ebfff : ICH HD audio
    fe9f0000-fe9fffff : 0000:01:05.0
    fea00000-feafffff : PCI Bus 0000:02
    feabc000-feabffff : 0000:02:00.1
    feabc000-feabffff : ICH HD audio
    feac0000-feadffff : 0000:02:00.0
    feae0000-feafffff : 0000:02:00.0
    feb00000-febfffff : PCI Bus 0000:03
    febe0000-febfffff : 0000:03:00.0
    fec00000-fec003ff : IOAPIC 0
    fec10000-fec1001f : pnp 00:0a
    fed00000-fed003ff : HPET 2
    fee00000-fee00fff : Local APIC
    fee00000-fee00fff : pnp 00:09
    ffb80000-ffbfffff : pnp 00:0a
    fff00000-ffffffff : reserved
    100000000-2afffffff : System RAM

    • anil_pugalia

      Possibly the ubuntu you are using is configured to not allow you to access that region.

      • Ameya

        even tried on mint same output

        • anil_pugalia

          You mean to say, it reboots your PC. If yes, its possible that that area is being used for something else in your motherboard. Seems like something to do with hardware changes, unless some distro works. May be try Mageia.

  • Tanmay

    Hello sir,

    I have made the make file like this but it is showing lot’s of error, what changes do i have to made to make the code work properly.

    Below is the make file that i have made what changes i need to do to be able to made the above code work properly.

    MakeFile :-

    obj-m += video_ram.o

    all:

    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

    clean:

    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

    • anil_pugalia

      Makefiles are very particular about the spaces & tabs you put. You have to make sure that all “make …” lines start with a tab and no spaces; also those lines should be immediately after the target line, i.e. “all:”, “clean:”, etc.

  • AMIT KUMAR

    why *off+= len ? ca u please elobtorate its occurance twice in the code ?

    • anil_pugalia

      Once during read & once during write.

  • [email protected]

    @anil Sir…..what is the difference between the atomic context and interrupt context…..and what is the meaning of deadlock or deadlock forever related to this…??

    • anil_pugalia

      This query seems non-relevant to this article.

  • prashant

    i build the above apppliaction ,but when i used od command to see content m getting all ff in outputs

    • anil_pugalia

      Did you try writing as well? If yes, then possibly you do not have the physical legacy video RAM in your system.

All published articles are released under Creative Commons Attribution-NonCommercial 3.0 Unported License, unless otherwise noted.
Open Source For You is powered by WordPress, which gladly sits on top of a CentOS-based LEMP stack.

Creative Commons License.