https://github.com/ikwzm/uiomem
uiomem is a Linux device driver for accessing a memory area outside the Linux Kernel management from user space.
https://github.com/ikwzm/uiomem
arm arm64 cpu-cache fpga fpga-soc-linux linux-drivers
Last synced: 25 days ago
JSON representation
uiomem is a Linux device driver for accessing a memory area outside the Linux Kernel management from user space.
- Host: GitHub
- URL: https://github.com/ikwzm/uiomem
- Owner: ikwzm
- License: bsd-2-clause
- Created: 2020-06-30T02:23:53.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2025-12-01T03:05:16.000Z (2 months ago)
- Last Synced: 2025-12-22T20:06:13.215Z (about 2 months ago)
- Topics: arm, arm64, cpu-cache, fpga, fpga-soc-linux, linux-drivers
- Homepage:
- Size: 147 KB
- Stars: 13
- Watchers: 1
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: Readme.md
- License: LICENSE
Awesome Lists containing this project
README
uiomem(User space mappable I/O Memory)
==================================================================================
**This project is under development.**
See the develop branch for this project for details.
https://github.com/ikwzm/uiomem/tree/develop
Currently 1.0.0-alpha.7 is tentatively released.
https://github.com/ikwzm/uiomem/tree/v1.0.0-alpha.7.
# Overview
## Introduction of uiomem
uiomem is a Linux device driver for accessing a memory area outside the Linux
Kernel management from user space.
uiomem has following features.
* uiomem can enable CPU cache, so it can access memory at high speed.
* uiomem can manually invalidiate and flush the CPU cache.
* uiomem can be freely attached and detached from Linux Kernel.
It is possible to access memory from the user space by opneing the device
file(e.g. /dev/uiomem0) and mapping to the user memory space, or using
the read()/write() functions.
The start address and size of the allocated memory area can be specified when
the device driver is loaded (e.g. when loaded via the `insmod` command).
Some platforms allow to specify them in the device tree.
## Supported platforms
* OS : Linux Kernel Version 4.19, 5.4, 6.1, 6.6, 6.12 (the author tested on 5.4 and 6.1 and 6.12).
* CPU: ARMv7 Cortex-A9 (Xilinx ZYNQ / Altera CycloneV SoC)
* CPU: ARM64 Cortex-A53 (Xilinx ZYNQ UltraScale+ MPSoC)
# Usage
## Compile
The following `Makefile` is included in the repository.
```Makefile:Makefile
# SPDX-License-Identifier: GPL-2.0 OR MIT
# Copyright (C) 2015-2023 Ichiro Kawazome
#
# For in kernel tree variables
#
obj-$(CONFIG_UIOMEM) += uiomem.o
#
# For out of kernel tree variables
#
CONFIG_MODULES ?= CONFIG_UIOMEM=m
HOST_ARCH ?= $(shell uname -m | sed -e s/arm.*/arm/ -e s/aarch64.*/arm64/)
ARCH ?= $(shell uname -m | sed -e s/arm.*/arm/ -e s/aarch64.*/arm64/)
ifeq ($(ARCH), arm)
ifneq ($(HOST_ARCH), arm)
CROSS_COMPILE ?= arm-linux-gnueabihf-
endif
endif
ifeq ($(ARCH), arm64)
ifneq ($(HOST_ARCH), arm64)
CROSS_COMPILE ?= aarch64-linux-gnu-
endif
endif
ifdef KERNEL_SRC
KERNEL_SRC_DIR := $(KERNEL_SRC)
else
KERNEL_SRC_DIR ?= /lib/modules/$(shell uname -r)/build
endif
#
# For out of kernel tree rules
#
all:
$(MAKE) -C $(KERNEL_SRC_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) $(CONFIG_MODULES) modules
modules_install:
$(MAKE) -C $(KERNEL_SRC_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) $(CONFIG_MODULES) modules_install
clean:
$(MAKE) -C $(KERNEL_SRC_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
```
## Install
Load the uiomem kernel driver using `insmod`.
The start address and size of the allocated memory area should be provided as an argument as follows.
The device driver is created, and allocates memory area with the specified address and size.
The memory area that can be specified must be aligned with the page size.
The maximum number of memory area that can be allocated using `insmod` is 8 (uiomem0/1/2/3/4/5/6/7).
```console
shell$ sudo insmod uiomem.ko uiomem0_addr=0x0400000000 uiomem0_size=0x00040000
[ 562.657246] uiomem uiomem0: driver version = 1.0.0-alpha.7
[ 562.657264] uiomem uiomem0: major number = 238
[ 562.657270] uiomem uiomem0: minor number = 0
[ 562.657275] uiomem uiomem0: range address = 0x0000000400000000
[ 562.657282] uiomem uiomem0: range size = 262144
[ 562.657287] uiomem uiomem.0: driver installed.
shell$ ls -la /dev/uiomem0
crw------- 1 root root 238, 0 Oct 21 17:36 /dev/uiomem0
```
The module can be uninstalled by the `rmmod` command.
```console
shell$ sudo rmmod uiomem
[ 657.672544] uiomem uiomem.0: driver removed.
```
## Configuration via the device tree file
In addition to the allocation via the `insmod` command and its arguments,
memory area can be allocated by specifying the reg property in the device tree file.
When a device tree file contains an entry like the following, uiomem will allocate
memory area and create device drivers when loaded by `insmod`.
```devicetree:devicetree.dts
#address-cells = <2>;
#size-cells = <2>;
uiomem_plmem {
compatible = "ikwzm,uiomem";
device-name = "uiomem0";
minor-number = <0>;
reg = <0x04 0x00000000 0x0 0x00040000>;
};
```
```console
shell$ sudo insmod uiomem.ko
[ 773.889476] uiomem uiomem0: driver version = 1.0.0-alpha.7
[ 773.889496] uiomem uiomem0: major number = 237
[ 773.889501] uiomem uiomem0: minor number = 0
[ 773.889506] uiomem uiomem0: range address = 0x0000000400000000
[ 773.889512] uiomem uiomem0: range size = 262144
[ 773.889518] uiomem 400000000.uiomem_plbram: driver installed.
shell$ ls -la /dev/uiomem0
crw------- 1 root root 237, 0 Oct 21 17:40 /dev/uiomem0
```
The following properties can be set in the device tree.
* `compatible`
* `reg`
* `memory-region`
* `shareable`
* `minor-number`
* `device-name`
* `sync-offset`
* `sync-size`
* `sync-direction`
### `compatible`
The `compatible` property is used to set the corresponding device driver when loading
uiomem. The `compatible` property is mandatory. Be sure to specify `compatible`
property as "ikwzm,uiomem".
### `reg`
The `reg` property specifies the physical address and size.
The `reg` property is used when uiomem allocates a buffer outside the management of Linux Kernel.
The memory area that can be specified must be aligned with the page size.
Either the `reg` property or the `memory-region` property is required.
```devicetree:devicetree.dts
#address-cells = <2>;
#size-cells = <2>;
uiomem@0xFFFC0000 {
compatible = "ikwzm,uiomem";
reg = <0x0 0xFFFC0000 0x0 0x00040000>;
};
```
### `memory-region`
The `memory-region` property specifies the memory region allocated for reserved memory.
The memory region specified by the `memory-region` property must always have the `no-map` property specified.
Either the `reg` property or the `memory-region` property is required.
```devicetree:devicetree.dts
#address-cells = <2>;
#size-cells = <2>;
reserved_memory {
ranges;
image_buf0: image_buf@0 {
no-map;
reg = <0x0 0x70000000 0x0 0x10000000>;
label = "image_buf0";
};
};
uiomem@image_buf0 {
compatible = "ikwzm,uiomem";
memory-region = <&image_buf0>;
};
```
### `shareable`
The `shareable` property is specified when multiple uiomem shares the memory space specified by `reg` property.
```devicetree:devicetree.dts
#address-cells = <2>;
#size-cells = <2>;
uiomem0 {
compatible = "ikwzm,uiomem";
reg = <0x0 0xFFFC0000 0x0 0x00040000>;
shareable;
};
uiomem1 {
compatible = "ikwzm,uiomem";
reg = <0x0 0xFFFC0000 0x0 0x00040000>;
shareable;
};
```
### `minor-number`
The `minor-number` property is used to set the minor number.
The valid minor number range is 0 to 255. A minor number provided as `insmod`
argument will has higher precedence, and when definition in the device tree has
colliding number, creation of the device defined in the device tree will fail.
The `minor-number` property is optional. When the `minor-number` property is not
specified, uiomem automatically assigns an appropriate one.
```devicetree:devicetree.dts
uiomem0 {
compatible = "ikwzm,uiomem";
minor-number = <0>;
reg = <0x0 0xFFFC0000 0x0 0x00040000>;
};
```
### `device-name`
The `device-name` property is used to set the name of device.
The `device-name` property is optional. The device name is determined as follow:
1. If `device-name` property is specified, the value of `device-name` property is used.
2. If `device-name` property is not present, and if `minor-number` property is
specified, `sprintf("uiomem%d", minor-number)` is used.
### `sync-offset`
The `sync-offset` property is used to set the start of the buffer range when manually
controlling the cache of uiomem.
The `sync-offset` property is optional.
When the `sync-offset` property is not specified, `sync-offset` is set to <0>.
### `sync-size`
The `sync-size` property is used to set the size of the buffer range when manually
controlling the cache of uiomem.
The `sync-size` property is optional.
When the `sync-size` property is not specified, `sync-size` is set to ths size specified by the `reg` property.
### `sync-direction`
The `sync-direction` property is used to set the direction of DMA when manually
controlling the cache of uiomem
* `sync-direction`=<0>: Read and Write
* `sync-direction`=<1>: Write Only
* `sync-direction`=<2>: Read Only
The `sync-direction` property is optional.
When the `sync-direction` property is not specified, `sync-direction` is set to <0>.
```devicetree:devicetree.dts
uiomem0 {
compatible = "ikwzm,uiomem";
reg = <0x0 0xFFFC0000 0x0 0x00040000>;
sync-offset = <0x00010000>;
sync-size = <0x000F0000>;
sync-direction = <2>;
};
```
## Device file
When uiomem is loaded into the kernel, the following device files are created.
`` is a placeholder for the device name described in the previous section.
* `/dev/`
* `/sys/class/uiomem//phys_addr`
* `/sys/class/uiomem//size`
* `/sys/class/uiomem//sync_offset`
* `/sys/class/uiomem//sync_size`
* `/sys/class/uiomem//sync_direction`
* `/sys/class/uiomem//sync_owner`
* `/sys/class/uiomem//sync_for_cpu`
* `/sys/class/uiomem//sync_for_device`
### `/dev/`
`/dev/` is used when `mmap()`-ed to the user space or accessed via `read()`/`write()`.
```C:uiomem_test.c
if ((fd = open("/dev/uiomem0", O_RDWR)) != -1) {
buf = mmap(NULL, buf_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
/* Do some read/write access to buf */
close(fd);
}
```
The device file can be directly read/written by specifying the device as the target of `dd` in the shell.
```console
shell$ dd if=/dev/urandom of=/dev/uiomem0 bs=4096 count=64
64+0 records in
64+0 records out
262144 bytes (262 kB, 256 KiB) copied, 0.00341051 s, 76.9 MB/s
```
```console
shell$ dd if=/dev/uiomem0 of=random.bin bs=4096
64+0 records in
64+0 records out
262144 bytes (262 kB, 256 KiB) copied, 0.00192588 s, 136 MB/s
```
### `phys_addr`
The physical address of a memory area can be retrieved by reading `/sys/class/uiomem//phys_addr`.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long phys_addr;
if ((fd = open("/sys/class/uiomem/uiomem0/phys_addr", O_RDONLY)) != -1) {
read(fd, attr, 1024);
sscanf(attr, "%x", &phys_addr);
close(fd);
}
```
### `size`
The size of a memory area can be retrieved by reading `/sys/class/uiomem//size`.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned int buf_size;
if ((fd = open("/sys/class/uiomem/uiomem0/size", O_RDONLY)) != -1) {
read(fd, attr, 1024);
sscanf(attr, "%d", &buf_size);
close(fd);
}
```
### `sync_offset`
The device file `/sys/class/uiomem//sync_offset` is used to specify
the start address of a memory block of which cache is manually managed.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_offset = 0x00000000;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_offset", O_WRONLY)) != -1) {
sprintf(attr, "%d", sync_offset); /* or sprintf(attr, "0x%x", sync_offset); */
write(fd, attr, strlen(attr));
close(fd);
}
```
### `sync_size`
The device file `/sys/class/uiomem//sync_size` is used to specify
the size of a memory block of which cache is manually managed.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_size = 1024;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_size", O_WRONLY)) != -1) {
sprintf(attr, "%d", sync_size); /* or sprintf(attr, "0x%x", sync_size); */
write(fd, attr, strlen(attr));
close(fd);
}
```
### `sync_direction`
The device file `/sys/class/uiomem//sync_direction` is used to set the
direction(Read/Write) of memory area of which cache is manually managed.
- 0: sets Read and Write
- 1: sets Write Only
- 2: sets Read Only
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_direction = 1;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_direction", O_WRONLY)) != -1) {
sprintf(attr, "%d", sync_direction);
write(fd, attr, strlen(attr));
close(fd);
}
```
### `sync_owner`
The device file `/sys/class/uiomem//sync_owner` reports the owner of
the memory block in the manual cache management mode.
If this value is 1, the buffer is owned by the device.
If this value is 0, the buffer is owned by the cpu.
```C:uiomem_test.c
unsigned char attr[1024];
int sync_owner;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_owner", O_RDONLY)) != -1) {
read(fd, attr, 1024);
sscanf(attr, "%x", &sync_owner);
close(fd);
}
```
### `sync_for_cpu`
In the manual cache management mode, CPU can be the owner of the buffer by writing
non-zero to the device file `/sys/class/uiomem//sync_for_cpu`.
This device file is write only.
If '1' is written to device file, if `sync_direction` is 2(=Read Only) or 0(=Read and Write),
the write to the device file invalidates a cache specified by `sync_offset` and `sync_size`.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_for_cpu = 1;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_for_cpu", O_WRONLY)) != -1) {
sprintf(attr, "%d", sync_for_cpu);
write(fd, attr, strlen(attr));
close(fd);
}
```
The value written to this device file can include sync_offset, sync_size, and sync_direction.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_offset = 0;
unsigned long sync_size = 0x10000;
unsigned int sync_direction = 1;
unsigned long sync_for_cpu = 1;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_for_cpu", O_WRONLY)) != -1) {
sprintf(attr, "0x%08X%08X", (sync_offset & 0xFFFFFFFF), (sync_size & 0xFFFFFFF0) | (sync_direction << 2) | sync_for_cpu);
write(fd, attr, strlen(attr));
close(fd);
}
```
The sync_offset/sync_size/sync_direction specified by ```sync_for_cpu``` is temporary and does not affect the ```sync_offset``` or ```sync_size``` or ```sync_direction``` device files.
### `sync_for_device`
In the manual cache management mode, DEVICE can be the owner of the buffer by
writing non-zero to the device file `/sys/class/uiomem//sync_for_device`.
This device file is write only.
If '1' is written to device file, if `sync_direction` is 1(=Write Only) or 0(=Read and Write),
the write to the device file flushes a cache specified by `sync_offset` and `sync_size` (i.e. the
cached data, if any, will be updated with data on DDR memory).
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_for_device = 1;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_for_device", O_WRONLY)) != -1) {
sprintf(attr, "%d", sync_for_device);
write(fd, attr, strlen(attr));
close(fd);
}
```
The value written to this device file can include sync_offset, sync_size, and sync_direction.
```C:uiomem_test.c
unsigned char attr[1024];
unsigned long sync_offset = 0;
unsigned long sync_size = 0x10000;
unsigned int sync_direction = 1;
unsigned long sync_for_device = 1;
if ((fd = open("/sys/class/uiomem/uiomem0/sync_for_device", O_WRONLY)) != -1) {
sprintf(attr, "0x%08X%08X", (sync_offset & 0xFFFFFFFF), (sync_size & 0xFFFFFFF0) | (sync_direction << 2) | sync_for_device);
write(fd, attr, strlen(attr));
close(fd);
}
```
The sync_offset/sync_size/sync_direction specified by ```sync_for_device``` is temporary and does not affect the ```sync_offset``` or ```sync_size``` or ```sync_direction``` device files.
# Example using uiomem
* https://github.com/ikwzm/PLBRAM-Ultra96
* https://github.com/ikwzm/PLBRAM-ZYBO-Z7