- 思路
- 内核创建一个字符设备文件/dev/devmemhqb。
- 用户态程序接受一个cmd(指示读取1/2/4/8 byte)和物理phy_addr,open字符设备/dev/devmemhqb,通过ioctl将cmd和phy_addr传给内核态ko。
- 内核态ko的unlocked_ioctl接口中,使用phys_to_virt将phy_addr转换为virt_addr,按照cmd指示读取对应数量字节的内存数据。
- 目录结构
// https://blog.csdn.net/m0_59416558/article/details/127453236
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/page_64.h>
#include "devmem.h"
//设计结构体类型来表示整个驱动对象(描述设备驱动信息)
struct led_drv
{
unsigned int major;//主设备号
dev_t devno;//设备号
struct class * cls;//设备文件信息结构体
struct device* dev;//设备文件信息
};
//定义全局设备对象(存储信息)
struct led_drv led;
//驱动与应用程序函数关联
int led_open(struct inode * inode, struct file * file)
{
printk("hqb led_open\n");
return 0;
}
int led_close(struct inode * inode, struct file * file)
{
printk("hqb led_close\n");
return 0;
}
ssize_t led_write(struct file * file, const char __user * buf, size_t size, loff_t * ops)
{
int num = 0;
void *p = (void *)copy_from_user(&num, buf, size);
printk("addr is = %p num:%#x size:%ld.\n", p, num, size);
return 0;
}
static long dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned long value = 0xAAAAAAAA55555555;
void *vaddr = NULL;
unsigned long paddr = 0;
/* 从用户空间读取新的工作模式 */
if (copy_from_user(&paddr, (const void __user *)arg, sizeof(paddr))) {
printk("copy_from_user failed.\n");
return -1;
}
printk("cmd:%d user paddr:%#lx arg:%#lx.\n", cmd, paddr, arg);
vaddr = phys_to_virt(paddr);
switch (cmd)
{
case READ_BYTE_FROM_PADDR:
value = *(char *)vaddr;
break;
case READ_WORD_FROM_PADDR:
value = *(short int *)vaddr;
break;
case READ_DWORD_FROM_PADDR:
value = *(int *)vaddr;
break;
case READ_DOUBLE_DWORD_FROM_PADDR:
value = *(unsigned long *)vaddr;
break;
default:
printk("cmd:%d not supported.\n", cmd);
break;
}
printk("cmd:%d user vaddr:%p paddr:%#lx value:%#lx.\n", cmd, vaddr, paddr, value);
if (copy_to_user((void __user *)arg, &value, sizeof(value))) {
printk("copy_to_user failed.\n");
return -1;
}
return 0;
}
const struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.unlocked_ioctl = dev_ioctl,
};
static int __init devmemhqb_init(void)
{
int ret = -1;
//申请设备号
led.major = 100; //250; wsl中设备号250已经被其它设备占用
led.devno = led.major << 20 | 0;
ret = register_chrdev(led.major, "devmemhqb drv", &fops);
if (ret < 0)
{
printk("register devno error\n");
goto err_1;
}
//创建设备文件
led.cls = class_create(THIS_MODULE, "devmemhqb cls");
if (IS_ERR(led.cls))
{
printk("class create error\n");
goto err_2;
}
led.dev = device_create(led.cls, NULL, led.devno, NULL, "devmemhqb");
if (IS_ERR(led.dev))
{
printk("device create erroe\n");
goto err_3;
}
return 0;
err_3:
class_destroy(led.cls);
err_2:
unregister_chrdev(led.major, "devmemhqb drv");
err_1:
return -1;
}
static void __exit devmemhqb_exit(void)
{
// 卸载驱动内容
// 1、释放设备文件
device_destroy(led.cls, led.devno);
// 2、释放设备文件结构体
class_destroy(led.cls);
// 3、释放设备号
unregister_chrdev(led.major,"devmemhqb drv");
}
module_init(devmemhqb_init);
module_exit(devmemhqb_exit);
MODULE_LICENSE("GPL");
#ifndef _DEVMEM_H_
#define _DEVMEM_H_
typedef enum {
READ_BYTE_FROM_PADDR = 1,
READ_WORD_FROM_PADDR = 2,
READ_DWORD_FROM_PADDR = 3,
READ_DOUBLE_DWORD_FROM_PADDR = 4,
} user_cmd_type;
#endif
#KERNLE_PATH=/home/ubuntu/code/kernel/linux-3.14
obj-m += devmem.o
all:
make -j $(nproc) -C /root/hqb/projects/test/linux-image-bsk M=$(PWD) modules
clean:
rm *.cmd *.o *.ko
install:
#cp *.ko $(APP) /nfsdir/rootfs
@modprobe -r $(TARGETNAME)
@install $(MODULE) /lib/modules/$(shell uname -r)/kernel/drivers/hid
@depmod
@modprobe $(TARGETNAME)
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include "virt_to_phy.h"
#include "devmem.h"
// 使用mmap从内核申请一段连续的空间
// int main() {
// // 请求映射的字节数
// size_t mapping_size = 1 * 1024 * 1024; // 1 MiB
// // 创建匿名映射,映射类型为私有、可写、可执行、连续(默认属性即可保证连续性)
// void *mapped_memory = mmap(NULL, mapping_size,
// PROT_READ | PROT_WRITE | PROT_EXEC,
// MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// if (mapped_memory == MAP_FAILED) {
// perror("mmap");
// return EXIT_FAILURE;
// }
// // ... 使用映射的内存 ...
// printf("Successfully mapped %zu bytes of continuous memory at address %p.\n",
// mapping_size, mapped_memory);
// // 当不再需要映射时,释放内存
// int munmap_result = munmap(mapped_memory, mapping_size);
// if (munmap_result == -1) {
// perror("munmap");
// return EXIT_FAILURE;
// }
// printf("Memory unmapped successfully.\n");
// return EXIT_SUCCESS;
// }
// malloc的内存转换出物理地址到内核态读取数据,再返回用户态
// int main() {
// // 请求映射的字节数
// size_t mapping_size = 1 * 1024 * 1024; // 1 MiB
// uintptr_t vaddr, paddr = 0;
// pid_t pid= getpid();
// vaddr = (uintptr_t)malloc(mapping_size);
// if (vaddr == 0) {
// printf("malloc failed.");
// return EXIT_FAILURE;
// }
// if( mlock((void *)vaddr, mapping_size) == -1) {
// perror("mlock");
// }
// // if (virt_to_phys_user(&paddr, pid, (uintptr_t)mapped_memory)) {
// if (virt_to_phys_user(&paddr, pid, vaddr)) {
// fprintf(stderr, "error: virt_to_phys_user\n");
// return EXIT_FAILURE;
// };
// printf("Successfully malloc %zu bytes of continuous memory at address %p paddr %p.\n",
// mapping_size, (void *)vaddr, (void *)paddr);
// int fd = open("/dev/devmemhqb",O_WRONLY);
// if (fd < 0) {
// perror("open file failed");
// return -1;
// }
// *(uint64_t *)vaddr = 0x55555555AAAAAAAA;
// uint64_t value = (uint64_t)paddr;
// int ret = ioctl(fd, READ_DOUBLE_DWORD_FROM_PADDR, &value);
// if (ret != 0) {
// printf("ioctl failed ret:%d.\n", ret);
// return EXIT_FAILURE;
// }
// printf("ioctl ret:%d value: %#lx \n", ret, value);
// return EXIT_SUCCESS;
// }
int main(int argc, char **argv) {
uintptr_t paddr = 0;
uint64_t cmd = 0;
if (argc < 3) {
printf("Usage: ./devmem cmd(1:one byte/2:two byte/3:four byte/4:eight byte) paddr\n");
return EXIT_FAILURE;
}
cmd = strtoull(argv[1], NULL, 0);
paddr = strtoull(argv[2], NULL, 0);
printf("Read data at address paddr %p, cmd:%ld(1:one byte/2:two byte/3:four byte/4:eight byte).\n",
(void *)paddr, cmd);
if (cmd < READ_BYTE_FROM_PADDR || cmd > READ_DOUBLE_DWORD_FROM_PADDR) {
printf("cmd:%ld value is err.\n", cmd);
return EXIT_FAILURE;
}
int fd = open("/dev/devmemhqb",O_WRONLY);
if (fd < 0) {
perror("open file failed");
return -1;
}
uint64_t value = (uint64_t)paddr;
int ret = ioctl(fd, cmd, &value);
if (ret != 0) {
printf("ioctl failed ret:%d.\n", ret);
return EXIT_FAILURE;
}
printf("ioctl ret:%d value: %#lx \n", ret, value);
return EXIT_SUCCESS;
}
#https://blog.csdn.net/men_wen/article/details/75272585
# https://www.jianshu.com/p/3cfe11e54293
.PHONY:clean
CC = gcc
CFLAGS = -Wall -g
CFLAGS += -I../virt_to_phy
CFLAGS += -I./devmem_drv
CPPFLAGS += -I../virt_to_phy
LDFLAGS += -L../virt_to_phy
OBJ = mmap_mm.o
mmap_mm:$(OBJ)
$(CC) $(LDFLAGS) $^ -o $@ -lvirt_to_phy
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o mmap_mm
// gcc -g -c virt_to_phy.c
// ar rcs libvirt_to_phy.a virt_to_phy.o
#include <fcntl.h> /* open */
#include <stdint.h> /* uint64_t */
#include <stdio.h> /* printf */
#include <stdlib.h> /* size_t */
#include <unistd.h> /* pread, sysconf */
#include<sys/mman.h>
typedef struct {
uint64_t pfn : 55;
unsigned int soft_dirty : 1;
unsigned int file_page : 1;
unsigned int swapped : 1;
unsigned int present : 1;
} PagemapEntry;
/* Parse the pagemap entry for the given virtual address.
*
* @param[out] entry the parsed entry
* @param[in] pagemap_fd file descriptor to an open /proc/pid/pagemap file
* @param[in] vaddr virtual address to get entry for
* @return 0 for success, 1 for failure
*/
int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr)
{
size_t nread;
ssize_t ret;
uint64_t data;
uintptr_t vpn;
vpn = vaddr / sysconf(_SC_PAGE_SIZE);
nread = 0;
while (nread < sizeof(data)) {
ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data) - nread,
vpn * sizeof(data) + nread);
nread += ret;
if (ret <= 0) {
return 1;
}
}
entry->pfn = data & (((uint64_t)1 << 55) - 1);
entry->soft_dirty = (data >> 55) & 1;
entry->file_page = (data >> 61) & 1;
entry->swapped = (data >> 62) & 1;
entry->present = (data >> 63) & 1;
return 0;
}
/* Convert the given virtual address to physical using /proc/PID/pagemap.
*
* @param[out] paddr physical address
* @param[in] pid process to convert for
* @param[in] vaddr virtual address to get entry for
* @return 0 for success, 1 for failure
*/
int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr)
{
char pagemap_file[BUFSIZ];
int pagemap_fd;
snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);
pagemap_fd = open(pagemap_file, O_RDONLY);
if (pagemap_fd < 0) {
return 1;
}
PagemapEntry entry;
if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) {
return 1;
}
close(pagemap_fd);
*paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE));
return 0;
}
#ifndef _VIRT_TO_PHY_
#define _VIRT_TO_PHY_
#include <stdint.h>
int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr);
#endif