Hardware IO in linux driver

Outline

  1. Functions that handle resource allocation for memory regions:

    struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
    void release_mem_region(unsigned long start, unsigned long len);
    int check_mem_region(unsigned long start, unsigned long len);
    
    # allocated memory is shown at /proc/iomem
    
  2. ioremap remaps a physical address range into the processor’s virtual address space, making it available to the kernel. iounmap frees the mapping when it is no longer needed.

    #include <asm/io.h>
    void *ioremap(unsigned long phys_addr, unsigned long size);
    void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
    void iounmap(void *virt_addr);
    
  3. Accessor functions that are used to work with I/O memory.:

    #include <asm/io.h>
    unsigned int ioread8(void *addr);
    unsigned int ioread16(void *addr);
    unsigned int ioread32(void *addr);
    void iowrite8(u8 value, void *addr);
    void iowrite16(u16 value, void *addr);
    void iowrite32(u32 value, void *addr);
    
  4. “Repeating” versions of the I/O memory primitives.:

    void ioread8_rep(void *addr, void *buf, unsigned long count);
    void ioread16_rep(void *addr, void *buf, unsigned long count);
    void ioread32_rep(void *addr, void *buf, unsigned long count);
    void iowrite8_rep(void *addr, const void *buf, unsigned long count);
    void iowrite16_rep(void *addr, const void *buf, unsigned long count);
    void iowrite32_rep(void *addr, const void *buf, unsigned long count);
    

request_mem_region

  1. resource

iomem_resource, ioport_resource:

// kernel/resource.c
struct resource iomem_resource = {
    .name   = "PCI mem",
    .start  = 0,
    .end    = -1,
    .flags  = IORESOURCE_MEM,
};
EXPORT_SYMBOL(iomem_resource);

struct resource:

// include/linux/ioport.h
struct resource {
    // memory address?
    resource_size_t start;
    resource_size_t end;

    const char *name;
    unsigned long flags;
    unsigned long desc;

    // left-child right-sibling tree
    struct resource *parent, *sibling, *child;
};
  1. request API

    • request_mem_region() wrapper:

      #define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)
      #define request_region(start,n,name)     __request_region(&ioport_resource, (start), (n), (name), 0)
      
    • __request_region()

      • alloc_resource(): allocate struct resource and return

      • __request_resource(): add new resource to the child of parent resource.

    • alloc/free resource

      • alloc_resource(): allocate struct resource and return

          1. allocate from freelist(bootmem_resource_free) + memset()

          1. kzalloc()

      • free_resource(): (1) kfree() (2) free to freelist(bootmem_resource_free)

        • PageSlab(virt_to_head_page(res))

  2. /proc/iomem:

    // kernel/resource.c
    __initcall(ioresources_init);
    static int __init ioresources_init(void)
    {
        proc_create("ioports", 0, NULL, &proc_ioports_operations);
        proc_create("iomem", 0, NULL, &proc_iomem_operations);
        return 0;
    }
    
    static const struct file_operations proc_iomem_operations = {
        .open       = iomem_open,
        .read       = seq_read,
        .llseek     = seq_lseek,
        .release    = seq_release,
    };
    
    static const struct seq_operations resource_op = {
        .start  = r_start,
        .next   = r_next,
        .stop   = r_stop,
        .show   = r_show,
    };
    
  3. reserve at init API

    // kernel/resource.c
    __setup("reserve=", reserve_setup);
    #define MAXRESERVE 4
    static int __init reserve_setup(char *str)
    {
        static int reserved;
        static struct resource reserve[MAXRESERVE];
    
        for (;;) {
            unsigned int io_start, io_num;
            int x = reserved;
    
            if (get_option (&str, &io_start) != 2)
                break;
            if (get_option (&str, &io_num)   == 0)
                break;
            if (x < MAXRESERVE) {
                struct resource *res = reserve + x;
                res->name = "reserved";
                res->start = io_start;
                res->end = io_start + io_num - 1;
                res->flags = IORESOURCE_BUSY;
                res->desc = IORES_DESC_NONE;
                res->child = NULL;
                if (request_resource(res->start >= 0x10000 ? &iomem_resource : &ioport_resource, res) == 0)
                    reserved = x+1;
             }
        }
        return 1;
    }
    

resource:

kernel/resource.c
- struct resource ioport_resource, iomem_resource
- proc_ioport, proc_iomem // seq_operations traverse resource tree?
- request, release, find
- devm version
- __init reserve_setup


include/linux/resource.h
include/uapi/linux/resource.h

bootmem:

mm/bootmem.c
include/linux/bootmem.h

// More
drivers/base/memory.c: The probe routines leave the pages reserved, just as the bootmem code does.
drivers/base/dma-contiguous.c: called by arch specific code once the early allocator (memblock or bootmem)
drivers/base/platform.c: #include<linux/bootmem.h>

ioremap internal

callstack (x86):

void __iomem *ioremap() at arch/x86/include/asm/io.h
void __iomem *ioremap_nocache() at arch/x86/mm/ioremap.c
void __iomem *__ioremap_caller() at arch/x86/mm/ioremap.c
int ioremap_page_range() at lib/ioremap.c

callstack (arm64):

//

ioremap_page_range(addr, end, phys_addr, prot):

// lib/ioremap.c
map vaddr=(addr, end) to paddr=(phys_addr, phys_addr + end-addr), pgtable permission is prot.
use vaddr to find correct pte(range of ptes), than set pte = (paddr, prot).

compare to remap_pfn_range() in mm/memory.c

misc:

pgd = pgd_offset_k(addr)
pud = pud_alloc(&init_mm, pgd, addr);
pmd = pmd_alloc(&init_mm, pud, addr);
pte = pte_alloc_kernel(pmd, addr)     => pte_offset_kernel(pmd, addr)

// include/asm-generic/pgtable.h
next = pgd_addr_end(addr, end)
next = pud_addr_end(addr, end);
next = pmd_addr_end(addr, end);

pud_set_huge(pud, phys_addr + addr, prot)
pmd_set_huge(pmd, phys_addr + addr, prot)

in Linux 4.7:

include/asm-generic/io.h
include/asm-generic/iomap.h

include/linux/ioport.h
include/linux/io-mapping.h ??

include/linux/device.h
drivers/base/devres.c
lib/devres.c

devm: device resource management

devm_ioremap_resource() consists of

  • devm_request_mem_region() => __request_region() // p.s. request_mem_region() => __request_region()

  • devm_ioremap() => ioremap()

More about devm_request_mem_region()

  • => __devm_request_region() => devres_alloc() + __request_region()

Reference

device resource management

reference