Powered By Blogger

Thursday, March 16, 2017

User space memory access from the Linux kernel

Linux memory

In Linux, user memory and kernel memory are independent and implemented in separate address spaces. The address spaces are virtualized, meaning that the addresses are abstracted from physical memory (through a process detailed shortly). Because the address spaces are virtualized, many can exist. In fact, the kernel itself resides in one address space, and each process resides in its own address space. These address spaces consist of virtual memory addresses, permitting many processes with independent address spaces to refer to a considerably smaller physical address space (the physical memory in the machine). Not only is this convenient, but it's also secure, because each address space is independent and isolated and therefore secure.
But there's a cost associated with this security. Because each process (and the kernel) can have identical addresses that refer to different regions of physical memory, it's not immediately possible to share memory. Luckily, a few solutions exist. User processes can share memory through the Portable Operating System Interface for UNIX® (POSIX) shared memory mechanism (shmem), with the caveat that each process may have a different virtual address that refers to the same region of physical memory.
The mapping of virtual memory to physical memory occurs through page tables, which are implemented in the underlying hardware (see Figure 1). The hardware itself provides the mapping, but the kernel manages the tables and their configuration. Note that as shown here, a process may have a large address space, but it is sparse, meaning that small regions (pages) of the address space refer to physical memory through the page tables. This permits a process to have a massive address space that is defined only for the pages that are needed at any given time.
Page tables provide the mapping from virtual addresses to physical addresses
Figure 1. Page tables provide the mapping from virtual addresses to physical addresses

Having the ability to sparsely define memory for processes means that the underlying physical memory can be overcommitted. Through a process called paging (though in Linux, it's typically called swap), less-used pages are dynamically moved to a slower storage device (such as a disk) to accommodate other pages that need to be accessed (see Figure 2). This behavior allows the physical memory within the computer to serve pages that an application more readily needs while migrating less-needed pages to disk for improved utilization of the physical memory. Note that some pages can refer to files, in which case, the data can be flushed if dirty (through the page cache) or, if the page is clean, simply discarded.

Swap permits better use of the physical memory space by migrating          less-used pages to slower and less expensive storage
Figure 2. Swap permits better use of the physical memory space by migrating less-used pages to slower and less expensive storage

The process by which a page is selected to swap to storage is called a page-replacement algorithm and can be implemented using a number of algorithms (such as least recently used). This process can occur when a memory location is requested whose page is not in memory (no mapping is present in the memory management unit [MMU]). This event is called a page fault and is detected by hardware (the MMU), and then managed by firmware after a page fault interrupt occurs. See Figure 3 for a illustration of this stack.
Linux provides an interesting implementation of swap that offers some useful characteristics. The Linux swap system permits the creation and use of multiple swap partitions and priorities, which permits a hierarchy of swap over storage devices that provide different performance characteristics (for example, a first-level swap on a solid-state disk [SSD] and a larger, second-level swap space on a slower storage device). Attaching a higher priority to the SSD swap allows it to be used until exhausted; only then would pages be written to the lower-priority (slower) swap partition.
Figure 3. Address spaces and elements of virtual-to-physical address mapping
Virtual-to-physical address mapping

Not all pages are candidates for swapping. Consider kernel code that responds to interrupts or code that manages the page tables and swap logic. These are obvious pages that should never be swapped out and are therefore pinned, or permanently resident in memory. Although kernel pages are not candidates for swapping, user space pages are, but they can be pinned through the mlock (or mlockall) function to lock the page down. This is the purpose behind the user space memory access functions. If the kernel assumed that an address that a user passed was valid and accessible, a kernel panic would eventually occur (for example, because the user page was swapped out, resulting in a page fault in the kernel). This application programming interface (API) ensures that those corner cases are handled properly.

Kernel APIs

Now, let's explore the kernel APIs for manipulating user memory. Note that this covers the kernel and the user space interface, but the next section explores some of the other memory APIs. The user space memory access functions to be explored are listed in Table 1.
Table 1. The User Space Memory Access API
As you would expect, the implementation of these functions can be architecture dependent. For x86 architectures, you can find these functions and symbols defined in ./linux/arch/x86/include/asm/uaccess.h, with source in ./linux/arch/x86/lib/usercopy_32.c and usercopy_64.c.
The role of the data-movement functions is shown in Figure 4 as it relates to the types involved for copy (simple vs. aggregate).
Figure 4. Data movement using the User Space Memory Access API
Data movement using the User Space Memory Access API

The access_ok function

You use the access_ok function to check the validity of the pointer in user space that you intend to access. The caller provides the pointer, which refers to the start of the data block, the size of the block, and the type of access (whether the area is intended to be read or written). The function prototype is defined as:
1
access_ok( type, addr, size );
The type argument can be specified as VERIFY_READ or VERIFY_WRITE. The VERIFY_WRITE symbolic also identifies whether the memory region is readable as well as writable. The function returns non-zero if the region is likely accessible (though access may still result in -EFAULT). This function simply checks that the address is likely in user space, not in the kernel.

The get_user function

To read a simple variable from user space, you use the get_user function. This function is used for simple types such as charand int, but larger data types like structures must use the copy_from_user function, instead. The prototype accepts a variable (to store the data) and an address in user space for the Read operation:
1
get_user( x, ptr );
The get_user function maps to one of two internal functions. Internally, this function determines the size of the variable being accessed (based on the variable provided to store the result) and forms an internal call through __get_user_x. This function returns zero on success. In general, the get_user and put_user functions are faster than their block copy counterparts and should be used if small types are moved.

The put_user function

You use the put_user function to write a simple variable from the kernel into user space. Like get_user, it accepts a variable (which contains the value to write) and a user space address as the write target:
1
put_user( x, ptr );
Like get_user, the put_user function is internally mapped over the put_user_x function and returns 0 on success or -EFAULT on error.

The clear_user function

The clear_user function is used to zero a block of memory in user space. This function takes a pointer in user space and a size to zero, which is defined in bytes:
1
clear_user( ptr, n );
Internally, the clear_user function first checks to see whether the user space pointer is writable (via access_ok), and then invokes an internal function (coded in inline assembly) to perform the Clear operation. This function is optimized as a very tight loop using string instructions with the repeat prefix. It returns the number of bytes that were not clearable or zero if the operation was successful.

The copy_to_user function

The copy_to_user function copies a block of data from the kernel into user space. This function accepts a pointer to a user space buffer, a pointer to a kernel buffer, and a length defined in bytes. The function returns zero on success or non-zero to indicate the number of bytes that weren't transferred.
1
copy_to_user( to, from, n );
After checking the ability to write to the user buffer (through access_ok), the internal function __copy_to_user is invoked, which in turn calls __copy_from_user_inatomic (in ./linux/arch/x86/include/asm/uaccess_XX.h, where XX is 32 or 64 depending on architecture). This function (after determining whether to perform 1, 2 or 4 byte copies) finally calls __copy_to_user_ll, which is where the real work is done. In broken hardware (prior to the i486, where the WP bit was not honored from supervisory mode), the page tables could change at any time, requiring a the desired pages to be pinned into memory so that they could not be swapped out while being addressed. Post i486, the process is nothing more than an optimized copy.

The copy_from_user function

The copy_from_user function copies a block of data from user space into a kernel buffer. it accepts a destination buffer (in kernel space), a source buffer (from user space), and a length defined in bytes. As with copy_to_user, the function returns zero on success and non-zero to indicate a failure to copy some number of bytes.
1
copy_from_user( to, from, n );
The function begins by checking the ability to read from the source buffer in user space (via access_ok), and then calls__copy_from_user and eventually __copy_from_user_ll. From here, depending on architecture, a call is made to copy from the user buffer to a kernel buffer with zeroing (of unavailable bytes). The optimized assembly functions include the ability to manage.

The strnlen_user function

The strnlen_user function is used just like strnlen but assumes that the buffer is available in user space. The strnlen_userfunction takes two arguments: the user space buffer address and the maximum length to check.
1
strnlen_user( src, n );
The strnlen_user function first checks to see that the user buffer is readable through a call to access_ok. If accessible, the strlen function is called, and the max length argument is ignored.

The strncpy_from_user function

The strncpy_from_user function copies a string from user space into a kernel buffer, given a user space source address and max length.
1
strncpy_from_user( dest, src, n );
As a copy from user space, this function first checks that the buffer is readable via access_ok. Similar to copy_from_user, this function is implemented as an optimized assembly function (within ./linux/arch/x86/lib/usercopy_XX.c).

Other schemes for memory mapping

The previous section explored methods for moving data between the kernel and user space (with the kernel initiating the operation). Linux provides a number of other methods that you can use for data movement, both in the kernel and in user space. Although these methods may not necessarily provide identical functionality as described by the user space memory access functions, they are similar in their ability to map memory between address spaces.
In user space, note that because user processes appear in separate address spaces, moving data between them must occur through some form of inter-process communication mechanism. Linux provides a variety of schemes (such as message queues), but most notable is POSIX shared memory (shmem). This mechanism allows a process to create an area of memory, and then share that region with one or more processes. Note that each process can map the shared memory region to different addresses in their respective address spaces. Therefore, relative offset addressing is required.

The mmap function allows a user space application to create a mapping in the virtual address space. This functionality is common in certain classes of device drivers (for performance), allowing physical device memory to be mapped into the virtual address space of the process. Within a driver, the mmap function is implemented through the remap_pfn_range kernel function, which provides a linear mapping of device memory into a user's address space.

Monday, February 20, 2017

Linux PCI Driver Model



PCI Understanding:



  • The Peripheral Component Interconnect Bus (PCI) today is present in a wide variety of microcomputers ranging from Intel-based PC architectures to DEC-Alpha-based work-stations.
  • The CPU communicates with the PCI subsystem using a special chipset known as PCI-Bridge.
  • PCI-Bridge is an intelligent controller, that handles all necessary tasks to transfer data from or to the CPU or the Memory Subsystem.
  • On PCI the addresses and data are transferred as separate chunks over the bus because all bus lines can be used either as address or as data-lines.
  • Each bus device has its own 265byte space of Memory for configuration  purposes that can be accessed through the CONFIG_ADDRESS and the CONFIG_DATA registers.
  • All devices that are known to Linux you will see at /proc/pci.
  • Device resources (I/O addresses, IRQ lines) automatically assigned at boot time, either by the BIOS or by Linux itself (if configured). 
  • To identify a certain device while driver writing you will at least have to know the vendor-id and the device-id that is statically stored in the device configuration block.
  • Driver writers normally need to know only the base address of the device and the IRQ line that the device is using.
  • PCI device configuration information is Little-Endian. Remember that in your drivers. 
  • lspci enumerate all the devices.
          00:16.0 Communication controller: Intel Corporation 6 Series/C200 Series Chipset Family MEI Controller #1 (rev 04)
          00:19.0 Ethernet controller: Intel Corporation 82579V Gigabit Network Connection (rev 05)
          |     |   |
          |     |   |_Function Number
          |     |_PCI Device Number
          |_PCI Bus Number

  • lspci -tv
          -[0000:00]-+-00.0  Intel Corporation 2nd Generation Core Processor Family DRAM Controller
                          +-01.0-[01]--+-00.0  nVidia Corporation GF108 [GeForce GT 430]
                          |            \-00.1  nVidia Corporation GF108 High Definition Audio Controller
             |        |       |        |   
             |        |       |        |_ PCI Bus-1
             |        |       |_ PCI Bridge
             |        |_ PCI Bus-0
             |_ PCI Domain

  • Device configuration can be displayed with lspci -x
  • Standard information found in PCI configurations:
    • Offset 0: Vendor Id
    • Offset 2: Device Id
    • Offset 10: Class Id (network, display, bridge...)
    • Offsets 16 to 39: Base Address Registers (BAR) 0 to 5
    • Offset 44: Subvendor Id
    • Offset 46: Subdevice Id
    • Offsets 64 and up: up to the device manufacturer
    • Kernel sources: these offsets are defined in include/linux/pci_regs.h

  • PCI Device Initialization steps:
    • Enable the device.
    • Request I/O port and I/O memory resources.
    • Set the DMA Mask for both coherent and streaming DMA.
    • Allocate and Initialize shared coherent data.
    • Initialize device Registers.
    • Register IRQ handler.
    • Register to other subsystems (network, video ..)
    • Enable DMA or Processing Engines.
    • Before touching any device registers, the driver should first execute pci_enable_device(). This will:
      • Wake up the device if it was in suspended state.
      • Allocate I/O and memory regions of the device, If not already done by the BIOS.
      • Assign IRQ to the device, If not already done by the BIOS 
    • Enable DMA by calling pci_set_master(). This will:
      • Enable DMA by setting the bus master bit in the PCI_COMMAND register. The device will then be able to act as a master on the address bus.
      • Fix the latency timer value if it's set to something bogus by the BIOS.
      • This enables the PCI_COMMAND bit for Memory ­ Write Invalidate.
      • This also ensures that the cache line size register is set correctly.
    • Accessing configuration registers:
      • Reading:
        • int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
        • int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
        • int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
      • Writing:
        • int pci_write_config_byte(struct pci_dev *dev,  int where, u8 val);
        • int pci_write_config_word(struct pci_dev *dev,  int where, u16 val);
        • int pci_write_config_dword(struct pci_dev *dev,  int where, u32 val);
    • Accessing I/O registers and memory:
      • Each PCI device can have up to 6 I/O or memory regions, described in BAR0 to BAR5.
      • Access the base address of the I/O region:
        • long iobase = pci_resource_start (pdev, bar);
      • Access the I/O region size:
        • long iosize = pci_resource_len (pdev, bar);
      • Reserve the I/O region:
        • pci_request_region(pdev, bar, “my driver”);
    • Use pci_dma_set_mask() to declare any device with more (or less) than 32­bit bus master capability
    • In particular, must be done by drivers for PCI­X and PCIe compliant devices, which use 64 bit DMA.
    • If the device can directly address "consistent memory" in System RAM above 4G physical address, register this by calling pci_set_consistent_dma_mask().
    • You can allocate your cache consistent buffers if you plan to use such buffers. 
    • If needed by the device Set some “capability” fields and do some vendor specific initialization or reset Example: clear pending interrupts.
    • Register interrupt handlers:
      • Need to call request_irq() with the IRQF_SHARED flag, because all PCI IRQ lines can be shared.
      • Registration also enables interrupts, so at this point
        • Make sure that the device is fully initialized and ready to service interrupts.
        • Make sure that the device doesn't have any pending interrupt before calling request_irq(). 
      • Where you actually call request_irq() can actually depend on the type of device and the subsystem it could be part of (network, video, storage...). 
      • Your driver will then have to register to this subsystem.

  • PCI Device Shutdown: 
    • Disable the generation of new interrupts. If you don't, the system will get spurious interrupts, and will eventually disable the IRQ line. Bad for other devices on this line!
    • Release the IRQ.
    • Stop all DMA activity. Needed to be done after IRQs are disabled (could start new DMAs)
    • Release DMA buffers: streaming first and then consistent ones.
    • Unregister from other subsystems
    • Unmap I/O memory and ports with io_unmap().
    • Disable the device with pci_disable_device().
    • Unregister I/O memory and ports. If you don't, you won't be able to reload the driver.