Main ID (MIDR) などの取得
x86 の CPUID 命令みたいなことを ARM で行いたいのですが、Raspbian のユーザー空間では CPU 情報を得る命令(MRC)が実行できなかったので、カーネル モジュールを作る。
MRC 命令を実行するデバイス ドライバ
ARM の MRC 命令のオペランドのうち、Rt を除いた
coproc, #opcode1, CRn, CRm, #opcode2
を、アドレス空間
+-----+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|...20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|
+-----+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|0..01| coproc |opcode1 | CRn | CRm |opcode2 | |
+-----+-----------+--------+-----------+-----------+--------+-----+
に見立てた、キャラクタ デバイスのファイル /dev/arm_mrc で読めるようにします。
データは4バイトだから下位2ビットがデータ内のバイト位置になります。
アドレスは lseek 関数で指定します。先頭からの範囲は 0x100000 〜 0x1FFFFF です。
データは read 関数で取得します。
注意点
-
カーネル モジュール内で実行した MRC 命令の結果である
カーネル/ユーザーで共通となる情報とは限りません。 -
カーネル モジュール内での未定義命令実行
領域によっては未定義命令の実行になって処理が中断します。
Linux カーネル モジュール
ファイルは3つ。 "make insmod" で "/dev/arm_mrc" が現れます。
環境は Raspbian buster, Kernel Version 4.19 で行っています。
Raspbian 用ソースコード
arm_mrc.c
#include <uapi/linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/cacheflush.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("ikiuo");
MODULE_DESCRIPTION("Invoke MRC instruction");
MODULE_VERSION("0.1.0");
#if defined(DEBUG)
#define debug(x) printk x
#else
#define debug(x)
#endif
typedef struct class class_type;
typedef struct device device_type;
typedef struct cdev chrdev_type;
typedef struct inode inode_type;
typedef struct file file_type;
typedef struct file_operations file_handler;
typedef unsigned int (*mrc_func)(void);
static void *mrc_proc = NULL;
#define INS_MRC 0xee100010
#define INS_MOV_PC_LR 0xe1a0f00e /* MOV PC,LR */
#define INS_BX_LR 0xe12fff1e /* BX LR */
/* addr = [1:mode][4:cop][3:op1][4:CRn][4:CRm][3:op2][2:00] */
static const loff_t offset_max = (1 << (1+4+3+4+4+3+2));
static loff_t
device_llseek (file_type *file, loff_t offset, int mode)
{
debug((KERN_INFO "lseek(%p, %Ld, %d): call\n", file, offset, mode));
switch (mode)
{
default: return -EINVAL;
case SEEK_SET: offset += file->f_pos; break;
case SEEK_CUR: break;
case SEEK_END: offset += offset_max; break;
}
if (offset < 0) return -EINVAL;
if (offset > offset_max) return -EINVAL;
file->f_pos = offset;
debug((KERN_INFO "lseek(%p, %Ld, %d): end\n", file, offset, mode));
return offset;
}
static ssize_t
device_read (file_type *file, char *buffer, size_t size, loff_t *offset)
{
loff_t addr, rem, pos = *offset;
ssize_t total = 0;
size_t res_size;
loff_t res_pos;
unsigned int *ins = (unsigned int *) mrc_proc;
unsigned int cop, CRn, op1, CRm, op2;
loff_t mode;
unsigned int status;
if (pos < 0) return -EIO;
if (pos >= offset_max) return -EIO;
rem = offset_max - pos;
if (size > rem) size = rem;
while (size > 0)
{
addr = pos >> 2;
op2 = (addr >> 0) & 7;
CRm = (addr >> 3) & 15;
CRn = (addr >> 7) & 15;
op1 = (addr >> 11) & 7;
cop = (addr >> 14) & 15;
mode = (addr >> 18);
debug((KERN_INFO "read(%p, %p, %d, %Ld)\n", file, buffer, size, *offset));
status = 0;
if (mode != 1)
return -EIO;
ins[0] = INS_MRC | (op1 << 21) | (CRn << 16) | (cop << 8) | (op2 << 5) | CRm;
ins[1] = INS_MOV_PC_LR;
flush_kernel_vmap_range (ins, 16);
flush_icache_range ((unsigned long)ins,16);
status = ((mrc_func)ins) ();
res_pos = pos & 3;
res_size = 4 - res_pos;
if (res_size > size)
res_size = size;
__copy_to_user (buffer, ((char*)&status) + res_pos, res_size);
buffer += res_size;
size -= res_size;
pos += res_size;
total += res_size;
}
*offset = pos;
return total;
}
static int
device_open (inode_type *inode, file_type *file)
{
return (try_module_get (THIS_MODULE) ? 0 : -EBUSY);
}
static int
device_release (inode_type *inode, file_type *file)
{
module_put (THIS_MODULE);
return 0;
}
static file_handler handler = {
.llseek = device_llseek,
.read = device_read,
.open = device_open,
.release = device_release,
};
static chrdev_type cdev_mrc;
#define DEVICE_NAME "arm_mrc"
static const int arm_ins_buffer_type = 1;
static const char *device_name = DEVICE_NAME;
static int major_number = 0;
static int minor_base = 0;
static int minor_count = 1;
static class_type *device_class = NULL;
static device_type *sys_device = NULL;
static void *
arm_alloc_ins_buffer (void)
{
if (arm_ins_buffer_type)
return __vmalloc (256, GFP_KERNEL, PAGE_KERNEL_EXEC);
else
return kmalloc (256, GFP_KERNEL);
}
static void
arm_free_ins_buffer (void *p)
{
if (p == NULL)
return;
if (arm_ins_buffer_type)
vfree (p);
else
kfree (p);
}
static void
arm_mrc_exit_chrdev (void)
{
dev_t dev = MKDEV (major_number, minor_base);
unregister_chrdev_region (dev, minor_count);
arm_free_ins_buffer (mrc_proc);
}
static void
arm_mrc_exit_cdev_chrdev (void)
{
cdev_del (&cdev_mrc);
arm_mrc_exit_chrdev ();
}
static void
arm_mrc_exit_all (void)
{
device_destroy (device_class, MKDEV (major_number, minor_base));
class_destroy (device_class);
arm_mrc_exit_cdev_chrdev ();
}
static int
__init arm_mrc_init (void)
{
dev_t dev;
int stat;
mrc_proc = arm_alloc_ins_buffer ();
if (mrc_proc == NULL) return -ENOMEM;
stat = alloc_chrdev_region (&dev, minor_base, minor_count, device_name);
if (stat < 0)
{
printk (KERN_ERR DEVICE_NAME ": alloc_chrdev_region failed: %d\n", stat);
return stat;
}
major_number = MAJOR (dev);
cdev_init (&cdev_mrc, &handler);
cdev_mrc.owner = THIS_MODULE;
stat = cdev_add (&cdev_mrc, dev, minor_count);
if (stat < 0)
{
printk (KERN_ERR DEVICE_NAME ": cdev_add failed: %d\n", stat);
arm_mrc_exit_chrdev ();
return stat;
}
device_class = class_create (THIS_MODULE, device_name);
if (IS_ERR(device_class))
{
printk (KERN_ERR DEVICE_NAME ": class_create failed\n");
arm_mrc_exit_cdev_chrdev ();
return -1;
}
sys_device = device_create(device_class, NULL, dev, NULL, DEVICE_NAME);
if (IS_ERR(sys_device))
{
arm_mrc_exit_all ();
return -1;
}
return 0;
}
static void
__exit arm_mrc_exit (void)
{
arm_mrc_exit_all ();
}
module_init (arm_mrc_init);
module_exit (arm_mrc_exit);
Makefile
# Makefile
#MOD_DIR = /lib/modules/4.9.0-6-rpi
#MOD_DIR = /lib/modules/4.19.93+
#MOD_DIR = /lib/modules/4.19.93-v7+
MOD_DIR = /lib/modules/4.19.93-v7l+
DEVNAME = arm_mrc
KMOD = $(DEVNAME).ko
obj-m += $(DEVNAME).o
all: build
build: $(KMOD)
$(KMOD): $(DEVNAME).c
make -C $(MOD_DIR)/build M=$(PWD) modules
clean:
make -C $(MOD_DIR)/build M=$(PWD) clean
insmod: build
sudo cp 99-arm_mrc.rules /etc/udev/rules.d/
sudo insmod $(KMOD)
rmmod:
sudo rmmod $(KMOD)
99-arm_mrc.rules
KERNEL=="arm_mrc", GROUP="root", MODE="0666"
テスト プログラム
test.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int
main (int argc, char **argv)
{
int debug = 0;
int coproc = 0;
int opcode1 = 0;
int CRn = 0;
int CRm = 0;
int opcode2 = 0;
int fd;
off_t addr, spos;
ssize_t rdsz;
unsigned int value;
int exitcode = 0;
if (argc < 2)
{
printf ("Usage: %s coproc [opcode1 [CRn [CRm [opcode2]]]]\n", argv[0]);
return 1;
}
if (argc > 1) coproc = strtol (argv[1], NULL, 10);
if (argc > 2) opcode1 = strtol (argv[2], NULL, 10);
if (argc > 3) CRn = strtol (argv[3], NULL, 10);
if (argc > 4) CRm = strtol (argv[4], NULL, 10);
if (argc > 5) opcode2 = strtol (argv[5], NULL, 10);
if ((coproc > 15) || (opcode1 > 7) || (CRn > 15)
|| (CRm > 15) || (opcode2 > 7))
{
printf ("invalid argument.\n");
printf ("coproc=%d, opcode1=%d, CRn=%d, CRm=%d, opcode2=%d\n",
coproc, opcode1, CRn, CRm, opcode2);
return 1;
}
addr = ((1 << 20) | (coproc << 16) | (opcode1 << 13)
| (CRn << 9) | (CRm << 5) | (opcode2 << 2));
fd = open ("/dev/arm_mrc", O_RDONLY);
if (fd < 0)
{
printf ("Can't open '/dev/arm_mrc': error=%d (%d)\n", fd, errno);
return 2;
}
spos = lseek (fd, addr, SEEK_SET);
if (addr != spos)
{
printf ("seek error: error=%d (%d)\n", spos, errno);
exitcode = 2;
}
else
{
rdsz = read (fd, &value, sizeof (value));
if (rdsz != sizeof (value))
{
printf ("read error: error=%d (%d)\n", spos, errno);
exitcode = 2;
}
else
{
printf ("MRC p%d,#%d,Rt,c%d,c%d,#%d; Rt=%#010x\n",
coproc, opcode1, CRn, CRm, opcode2, value);
}
}
close (fd);
return 0;
}
RaspberryPi 4 でのテスト プログラムの実行結果
MIDR_EL1
$ ./test 15 0 0 0 0
MRC p15,0,Rt,c0,c0,0; Rt=0x410fd083
ID_PFR0_EL1
./test 15 0 0 1 0
MRC p15,0,Rt,c0,c1,0; Rt=0x00000131
ID_PFR1_EL1
$ ./test 15 0 0 1 1
MRC p15,0,Rt,c0,c1,1; Rt=0x00011011