How Control Registers are accessed in Linux - linux

i have been reading Linux source code ported on to a propriety platform based on ARM Cortex -A7 MPCore - NEON Architecture
The code below shows how a control register of a module is modified up on calling an API
drivers\module\module-specific_file.c
static inline void API(....)
{
if (set)
__raw_writel(msk, (void *)((u32) (reg) + 0x1000));
else
{
__raw_writel(msk, (void *)((u32) (reg) + 0x2000));
}
}
in the above code reg is actually a virtual address and msk is a mask lets say x7FF and set is a parameter passed which conveys request of set/clear
but actually my doubt is how does modifying the control register address is modifying the value to be written to register ... ?? further if i look at the api which is been called it looks like below
arch\arm\include\asm\Io.h
static inline void __raw_writel(u32 val, volatile void __iomem *addr)
{
asm volatile("str %1, %0"
: "+Qo" (*(volatile u32 __force *)addr)
: "r" (val));
}
if any one have come across this kind of accessing control registers please let me know how actually content of registers are modified by modifying the virtual address.

As I know, registers are mapped in virtual address space. Have a look at /proc/iomem for example, you'll see mapped registers addresses and what hardware they are belong. The addresses of registers are strictly determined by hardware developers.
Typically, to map register address one should call ioremap function. It returns the address. Often this address is the base address, where the register addresses begin. To write something in register you just need to write the data in this address plus the resister offset.
E.g.: On ARM PXA320, there is a set of LCD registers. Somewhere ion driver code one may see - ioremap(BASE_ADDR, SIZE_OF_ADDR_SET);
In order to write __raw_write(value, base_addr + offset) function is used.

Related

Arm EL0 Address Translation

I want to translate a virtual user level address (EL0) to a physical address.
Arm provides an instruction AT to translate a virtual address to a physical address. This instruction should be called by privileged level (EL1), so I write a linux kernel module to execute this instruction. I pass a user level address (EL0) virt_addr through ioctl() to kernel module, and execute instruction AT.
According to the instruction manual, I execute the instruction by this way:
// S1E0R: S1 means stage 1, E0 means EL0 and R means read.
asm volatile("AT S1E0R, %[_addr_]" :: [_addr_]"r"(virt_addr));
// Register PAR_EL1 stores the translation result.
asm volatile("MRS %0, PAR_EL1": "=r"(phys_addr));
However, the value of register PAR_EL1, i.e. stored in phys_addr, is 0x80b (or 0b100000001011), which is not a correct result.
Then I found a manual about register PAR_EL1.
According to the manual, bit[0] is 1 means a fault occurs on the execution of an Address translation instruction.
bits[6:1] is Fault Status Code (FST). My FST is 0b000101. The manual tell me that the error information is: Translation fault, level 1.
I don't know the fault reason.
My cpu is Cortex-A53, and operating system is Linux-4.14.59.

How to write CPU's DMA address to FPGA (PCIe Endpoint)?

I'm trying to add DMA to my PCIe Linux driver using streaming DMA mappings. The FPGA (endpoint) has BAR4 configured for DMA and in my setup function I do (in order):
pci_set_master()
pci_enable_msi()
pci_set_dma_mask()
pci_set_consistent_dma_mask()
__get_free_pages()
dma_addr = pci_map_single(..., PCI_DMA_FROMDEVICE)
At this point I do not know how to tell the FPGA what my DMA address, dma_addr, is that was returned from pci_map_single(). Do I write dma_addr to BAR4 using pci_write_config_dword()? There has to be some way to tell the FPGA where it needs to write when using DMA or am I completely missing something here?
To read/write data in BARx of your PCIe, you have to map the BARx with function :
void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen);
You can do as following for exemple:
/* declare the bar4 buffer */
static volatile u32 __iomem * bar4;
/* map the bar4 */
bar4 = pcim_iomap(&pdev->dev, 4, BAR4_SIZE);
bar4[DMA_VECTOR_REGISTER_ADDRESS] = dma_addr;
See more documentation about BARx/MMIO in kernel Documentation.
The address of DMA_VECTOR_REGISTER_ADDRESS depend of your FPGA architecture. As I can see in comments, you are using a CycloneV GT. You should look at the CRA (Configuration Registers Access) registers.
On CycloneV GX PCIe Hard ip for Avalon-MM (page 83), the register address for DMA vector begin at 0x1000 (Avalon-MM-to-PCI Express Address Translation Table).

Linux device driver - memory mapped I/O example discussion

I have gone through the following topic and I still have some questions.
ioread32 followed by iowrite32 not giving same value
In the link, where can I get my base which is defined as 0xfed00000
in the post ?
what should I put for the second parameter in
void request_mem_region(unsigned long start, unsigned long len,char *name);
what should I put for the second parameter in
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
By having the Makefile and generating the kernel module, I should use the insmod and then dmesg to check if the code works as I expect, is this correct ?
In the case, should I add iounmap(virtual_base); before return 0; in the source ?
Thanks
In the link, where can I get my base which is defined as 0xfed00000 in the post ?
It's the base (physical) address of the peripheral's registers.
If the peripheral is a discrete chip on the board, then consult the board documentation.
If the peripheral is embedded in a SoC, then consult the memory map in the SoC datasheet.
what should I put for the second parameter in
void request_mem_region(unsigned long start, unsigned long len,char *name);
what should I put for the second parameter in
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
These two routines should be called with the same first and second parameters.
The length/size is the number of bytes the peripheral's register set occupies.
Sometimes the entire memory region to the next peripheral is specified.
By having the Makefile and generating the kernel module, I should use the insmod and then dmesg to check if the code works as I expect, is this correct ?
A judicious sprinkling of printk() statements is the tried & true method of testing a Linux kernel driver/module.
Unix has kdb.
In the case, should I add iounmap(virtual_base); before return 0; in the source ?
Do not copy that poorly written example of init code.
If ioremap() is performed in a driver's probe() (or other initialization) routine, then the iounmap() should be in the probe's error exit sequence and in the driver's remove() (or the complementary to init) routine.
There are numerous examples to study in the Linux kernel source. Use an online Linux cross reference such as http://lxr.free-electrons.com/source/
Note that almost all Linux drivers use iounmap() two or more times.

Accessing memory pointers in hardware registers

I'm working on enhancing the stock ahci driver provided in Linux in order to perform some needed tasks. I'm at the point of attempting to issue commands to the AHCI HBA for the hard drive to process. However, whenever I do so, my system locks up and reboots. Trying to explain the process of issuing a command to an AHCI drive is far to much for this question. If needed, reference this link for the full discussion (the process is rather defined all over because there are several pieces, however, ch 4 has the data structures necessary).
Essentially, one writes the appropriate structures into memory regions defined by either the BIOS or the OS. The first memory region I should write to is the Command List Base Address contained in the register PxCLB (and PxCLBU if 64-bit addressing applies). My system is 64 bits and so I'm trying to getting both 32-bit registers. My code is essentially this:
void __iomem * pbase = ahci_port_base(ap);
u32 __iomem *temp = (u32*)(pbase + PORT_LST_ADDR);
struct ahci_cmd_hdr *cmd_hdr = NULL;
cmd_hdr = (struct ahci_cmd_hdr*)(u64)
((u64)(*(temp + PORT_LST_ADDR_HI)) << 32 | *temp);
pr_info("%s:%d cmd_list is %p\n", __func__, __LINE__, cmd_hdr);
// problems with this next line, makes the system reboot
//pr_info("%s:%d cl[0]:0x%08x\n", __func__, __LINE__, cmd_hdr->opts);
The function ahci_port_base() is found in the ahci driver (at least it is for CentOS 6.x). Basically, it returns the proper address for that port in the AHCI memory region. PORT_LST_ADDR and PORT_LST_ADDR_HI are both macros defined in that driver. The address that I get after getting both the high and low addresses is usually something like 0x0000000037900000. Is this memory address in a space that I cannot simply dereference it?
I'm hitting my head against the wall at this point because this link shows that accessing it in this manner is essentially how it's done.
The address that I get after getting both the high and low addresses
is usually something like 0x0000000037900000. Is this memory address
in a space that I cannot simply dereference it?
Yes, you are correct - that's a bus address, and you can't just dereference it because paging is enabled. (You shouldn't be just dereferencing the iomapped addresses either - you should be using readl() / writel() for those, but the breakage here is more subtle).
It looks like the right way to access the ahci_cmd_hdr in that driver is:
struct ahci_port_priv *pp = ap->private_data;
cmd_hdr = pp->cmd_slot;

writing to an ioport resulting in segfaults

I'm writing for an atmel at91sam9260 arm 9 cored single board computer [glomation gesbc9260]
Using request_mem_region(0xFFFFFC00,0x100,"name"); //port range runs from fc00 to fcff
that works fine and shows up in /proc/iomem
then i try to write to the last bit of the port at fc20 with
writel(0x1, 0xFFFFFC20);
and i segfault...specifically "unable to handle kernel paging request at virtual address fffffc20."
I'm of the mind that i'm not allocating the right memory space...
any helpful insight would be great...
You need to ioremap the mem region you requested. ioremap maps a virtual address to a physical one.
writel works with virtual addresses, not with physical ones.
/* request mem_region */
...
base = ioremap(0xFFFFFC00, 0x100);
if(base == NULL)
release_mem_region(...);
/* now you can use base */
writel(0x1, base + 20)
...
What you probably need is to write your driver as a platform_driver, and declare a platform device in your board_file
An example of a relatively simple platform_driver can be found here
In fact, navigating through the kernel sources using lxr is probably the best way to learn how to
stuff like this.

Resources