Classic A thorough understanding of ioremap mappings in the Linux kernel __linux

Source: Internet
Author: User
Tags ranges volatile

Almost every peripheral is done by reading and writing registers on the device, typically including control registers, state registers, and data registers, and the registers of the peripherals are often continuously addressed. Depending on the CPU architecture, the CPU has two ways to address IO ports:

(1) I/O mapping method (i/o-mapped)

Typically, such as the X86 processor specifically implements a separate address space for peripherals, known as "I/O address space" or "I/O port space", the CPU accesses the address cells in this space through specialized I/O directives such as X86 in and out directives.

(2) Memory mapping Method (memory-mapped)

RISC instruction system CPU (such as ARM, PowerPC, etc.) usually only achieve a physical address space, peripheral I/O ports become part of memory. At this point, the CPU can access the peripheral I/O port as if it were accessing a memory unit without having to set up specialized peripheral I/O directives.

However, the differences in hardware implementations are completely transparent to the software, and driver developers can view the memory-mapped I/O ports and peripheral memory as "I/O memory" resources.

In general, when the system is running, the physical address of the peripheral I/O memory resources is known and determined by the hardware design. However, CPUs typically do not have predefined virtual address ranges for the physical addresses of these known peripheral I/O memory resources. Instead of accessing the I/O memory resources directly through physical addresses, the drivers must map them to the core virtual address space (through the page table), and then to the core virtual address ranges that are derived from the mappings, Access these I/O memory resources through an inbound instruction. Linux declares the function Ioremap () in the IO.h header file to map the physical address of the I/O memory resource to the core virtual address space (3GB-4GB), as follows:

void * IOREMAP (unsigned long phys_addr, unsigned long size, unsigned long flags);

The Iounmap function is used to cancel the mappings made by Ioremap () as follows:

void Iounmap (void * addr);

Both of these functions are implemented in the Mm/ioremap.c file.

After mapping the physical address of the I/O memory resource to the core virtual address, we can theoretically read and write the I/O memory resource as if it were read and write RAM. To ensure cross-platform portability of drivers, we should use specific functions in Linux to access I/O memory resources, not through pointers to core virtual addresses. As with the x86 platform, the read/write I/O functions are as follows:

#define READB (Addr) (* (volatile unsigned char *) __io_virt (addr))
#define READW (Addr) (* (volatile unsigned short *) __io_virt (addr))
#define READL (Addr) (* (volatile unsigned int *) __io_virt (addr))

#define WRITEB (B,ADDR) (* (volatile unsigned char *) __io_virt (addr) = (b))
#define WRITEW (B,ADDR) (* (volatile unsigned short *) __io_virt (addr) = (b))
#define Writel (B,ADDR) (* (volatile unsigned int *) __io_virt (addr) = (b))

#define MEMSET_IO (A,b,c) memset (__io_virt (a), (b), (c))
#define MEMCPY_FROMIO (A,b,c) memcpy ((a), __io_virt (b), (c))
#define MEMCPY_TOIO (A,b,c) memcpy (__io_virt (a), (b), (c))

Finally, we emphasize the implementation of the MMAP function in the driver. Mapping a device with mmap means that a segment of the user's space is associated with the device's memory, which allows the program to read or write to the assigned address range, which is actually access to the device.

The author in the Linux source code contains "Ioremap" text search, found that the real occurrence of the ioremap place is quite small. So I traced to find the physical address of the I/O operation to the real location of the virtual address, found that Linux has an alternative to ioremap, but this conversion process is indispensable.

For example, once again, we pick a small segment of the s3c2410 arm chip RTC (real clock) drive:

static void Get_rtc_time (int alm, struct rtc_time *rtc_tm)
{
SPIN_LOCK_IRQ (&rtc_lock);
if (ALM = = 1) {
Rtc_tm->tm_year = (unsigned char) almyear & msk_rtcyear;
Rtc_tm->tm_mon = (unsigned char) Almmon & Msk_rtcmon;
Rtc_tm->tm_mday = (unsigned char) Almday & Msk_rtcday;
Rtc_tm->tm_hour = (unsigned char) Almhour & Msk_rtchour;
Rtc_tm->tm_min = (unsigned char) almmin & msk_rtcmin;
Rtc_tm->tm_sec = (unsigned char) almsec & msk_rtcsec;
}
else {
Read_rtc_bcd_time:
Rtc_tm->tm_year = (unsigned char) bcdyear & msk_rtcyear;
Rtc_tm->tm_mon = (unsigned char) Bcdmon & Msk_rtcmon;
Rtc_tm->tm_mday = (unsigned char) Bcdday & Msk_rtcday;
Rtc_tm->tm_hour = (unsigned char) Bcdhour & Msk_rtchour;
Rtc_tm->tm_min = (unsigned char) bcdmin & msk_rtcmin;
Rtc_tm->tm_sec = (unsigned char) bcdsec & msk_rtcsec;

if (rtc_tm->tm_sec = = 0) {
/* Re-read all BCD registers in case of Bcdsec is 0.
The manual for more info. */
Goto Read_rtc_bcd_time;
}
}
SPIN_UNLOCK_IRQ (&rtc_lock);

Bcd_to_bin (rtc_tm->tm_year);
Bcd_to_bin (Rtc_tm->tm_mon);
Bcd_to_bin (Rtc_tm->tm_mday);
Bcd_to_bin (Rtc_tm->tm_hour);
Bcd_to_bin (rtc_tm->tm_min);
Bcd_to_bin (RTC_TM->TM_SEC);

/* The epoch of tm_year is 1900/*
Rtc_tm->tm_year + = rtc_leap_year-1900;

/* Tm_mon starts at 0, but RTC Month starts at 1 * *
rtc_tm->tm_mon--;
}

I/O operations seem to operate on almyear, Almmon, almday-defined registers, and what exactly are these macros defined as.

#define Almday BRTC (0x60)
#define Almmon BRTC (0x64)
#define Almyear BRTC (0x68)

With the help of macro BRTC, this macro is defined as:

#define BRTC (NB) __reg (0x57000000 + (NB))

With the help of a macro __reg, __reg is also defined as:

# define __REG (x) io_p2v (x)

The final io_p2v is really "playing" the virtual address and physical address conversion place:

#define IO_P2V (x) ((x) | 0xa0000000)

There is a __preg corresponding to the __reg:

# define __PREG (x) io_v2p (x)

There is a io_v2p corresponding to the IO_P2V:

#define IO_V2P (x) ((x) & ~0xa0000000)

It is obvious that there is no ioremap is secondary, the key question is whether there is no virtual address and physical address conversion.

The following program retains a memory at startup and then uses Ioremap to map it to the kernel virtual space while mapping it to the user's virtual space with Remap_page_range, which can be accessed by both the kernel and the user. If this memory is initialized with "ABCD" at the kernel virtual address, then the user's virtual address can be read:

/************mmap_ioremap.c**************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/wrapper.h>/* for MEM_MAP_ (un) reserve * *
#include <asm/io.h>/* for Virt_to_phys * *
#include <linux/slab.h>/* for Kmalloc and Kfree * *

Module_parm (Mem_start, "I");
Module_parm (mem_size, "I");

static int mem_start = Mem_size = 10;
static char *reserve_virt_addr;
static int major;

int Mmapdrv_open (struct inode *inode, struct file *file);
int mmapdrv_release (struct inode *inode, struct file *file);
int mmapdrv_mmap (struct file *file, struct vm_area_struct *vma);

static struct File_operations Mmapdrv_fops =
{
Owner:this_module, Mmap:mmapdrv_mmap, Open:mmapdrv_open, Release:
Mmapdrv_release,
};

int init_module (void)
{
if ((major = Register_chrdev (0, "Mmapdrv", &mmapdrv_fops)) < 0)
{
PRINTK ("mmapdrv:unable to register character device/n");
Return (-Eio);
}
PRINTK ("mmap Device major =%d/n", major);

PRINTK ("Memory Physical Address 0x%ldm/n", Virt_to_phys (high_memory)/
1024/1024);

RESERVE_VIRT_ADDR = Ioremap (mem_start *1024 * 1024, mem_size *1024 * 1024);
PRINTK ("reserve_virt_addr = 0x%lx/n", (unsigned long) reserve_virt_addr);
if (RESERVE_VIRT_ADDR)
{
int i;
for (i = 0; i < mem_size *1024 * 1024; i = 4)
{
Reserve_virt_addr[i] = ' a ';
Reserve_virt_addr[i + 1] = ' B ';
Reserve_virt_addr[i + 2] = ' C ';
Reserve_virt_addr[i + 3] = ' d ';
}
}
Else
{
Unregister_chrdev (Major, "mmapdrv");
Return-enodev;
}
return 0;
}

/* Remove the module * *
void Cleanup_module (void)
{
if (RESERVE_VIRT_ADDR)
Iounmap (RESERVE_VIRT_ADDR);

Unregister_chrdev (Major, "mmapdrv");
return;
}

int Mmapdrv_open (struct inode *inode, struct file *file)
{
Mod_inc_use_count;
return (0);
}

int mmapdrv_release (struct inode *inode, struct file *file)
{
Mod_dec_use_count;
return (0);
}

int mmapdrv_mmap (struct file *file, struct vm_area_struct *vma)
{
unsigned long offset = Vma->vm_pgoff << page_shift;
unsigned long size = vma->vm_end-vma->vm_start;

if (Size > Mem_size *1024 * 1024)
{
PRINTK ("Size too big/n");
Return (-Enxio);
}

Offset = offset + Mem_start * 1024 * 1024;

/* We don't want to have this area swapped out, lock it * *
Vma->vm_flags |= vm_locked;
if (Remap_page_range (VMA, Vma->vm_start, offset, size, page_shared))
{
PRINTK ("Remap page range failed/n");
Return-enxio;
}
return (0);
}

The function of the Remap_page_range function is to construct a new page table for mapping a physical address, to map the kernel space and user space, with the following prototype:

int Remap_page_range (vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot);

The most typical example of using mmap is the driver of the display card, which maps the memory space directly from the kernel to the user space to provide the memory reading and writing efficiency.

(in the initialization phase of the kernel driver, the physical address is mapped to the kernel virtual space through Ioremap (), and the block ROM is mapped to the user virtual space using Remap_page_range () in the mmap system call of the driver.) This allows both kernel space and user space to access the mapped virtual address. )

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.