因为 ART-Pi Smart 使用了 MMU 来管理内存,这在一定程度为调试外设驱动增加了复杂度,为了方便在命令行下调试物理内存,我写了一个 dbg_mm 驱动,导出 了两个命令:
rdbg_mm 物理地址 数据长度 # 备注地址要 4 字节对齐,长度以 4 字节为单位,地址和数据都要 0x/0X 开头
wdbg_mm 物理地址 数据0 数据1 .... # 备注地址要 4 字节对齐,数据 16 进制表示,地址和数据都要 0x/0X 开头
查看 ART-Pi Smart 的内存地址映射是这样的:
根据上述映射关系,实现的 dbg_mm 驱动的源码是这样的:
/******************************************************************************
* File: drv_dbg_mm.c
*
* Author:
iysheng@163.com
* Created: 04/26/22
* Descrip
tion: 调试物理内存驱动
*****************************************************************************/
#include
#include
#include
#include "ioremap.h"
#include
#include
#define LOG_LVL LOG_LVL_DBG
#define LOG_TAG "dbg.mm"
#include
typedef enum {
DBG_MM_SET_WIDTH_BITS,
DBG_MM_GET_WIDTH_BITS,
DBG_MM_SET_ADDR_STEP,
DBG_MM_GET_ADDR_STEP,
}dbg_mm_cmd_E;
static struct rt_device gs_dbg_mm_dev;
/* 默认支持字节访问,但是 IMX6ULL 对数据访问总线宽度有严格的限制
* 在访问外设寄存器时,如果 width 宽度不匹配导致崩溃
* */
static int __gs_width_in_bits = 8;
/* 地址 step 默认和位宽保持一致 */
static int __gs_addr_step = 1;
#define PV_LEN 0x20000000
static void dbg_mm_hex_smart(rt_uint32_t paddr, rt_uint32_t addr, rt_size_t len)
{
rt_size_t i = 0, j = 0;
for (; i < len; i += 4)
{
rt_kprintf("0x%08x:", paddr + i * __gs_addr_step);
for (j = 0; j < 4; j++)
{
if (j + i < len)
{
switch (__gs_width_in_bits / 8)
{
case 1:
rt_kprintf("0x%02x ", *((rt_uint8_t *)addr));
break;
case 2:
rt_kprintf("0x%04x ", *((rt_uint16_t *)addr));
break;
case 4:
rt_kprintf("0x%08x ", *((rt_uint32_t *)addr));
break;
}
addr += __gs_addr_step;
}
else
{
break;
}
}
rt_kprintf("
");
}
}
static void dbg_mm_copy_smart(rt_uint32_t dst, rt_uint32_t src, rt_size_t len)
{
rt_size_t i = 0;
for (; i < len; i++)
{
switch (__gs_width_in_bits / 8)
{
case 1:
*((rt_uint8_t *)dst) = *((rt_uint8_t *)src);
break;
case 2:
*((rt_uint16_t *)dst) = *((rt_uint16_t *)src);
break;
case 4:
*((rt_uint32_t *)dst) = *((rt_uint32_t *)src);
break;
default:
break;
}
dst += __gs_addr_step;
src += __gs_addr_step;
}
}
static rt_err_t dbg_mm_init(rt_device_t dev)
{
return RT_EOK;
}
static rt_err_t dbg_mm_open(rt_device_t dev, rt_uint16_t oflag)
{
return RT_EOK;
}
static rt_err_t dbg_mm_close(rt_device_t dev)
{
return RT_EOK;
}
static rt_err_t dbg_mm_control(rt_device_t dev,
int cmd,
void *args)
{
int ret = RT_EOK;
switch (cmd)
{
case DBG_MM_SET_WIDTH_BITS:
__gs_width_in_bits = *((int *)args);
break;
case DBG_MM_GET_WIDTH_BITS:
*((int *)args) = __gs_width_in_bits;
break;
case DBG_MM_SET_ADDR_STEP:
__gs_addr_step = *((int *)args);
break;
case DBG_MM_GET_ADDR_STEP:
*((int *)args) = __gs_addr_step;
break;
default:
break;
}
return ret;
}
static rt_size_t dbg_mm_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
rt_uint32_t * pvaddr = RT_NULL;
int io_remaped = 0;
if (pos >= KERNEL_VADDR_START + PV_OFFSET && pos < KERNEL_VADDR_START + PV_OFFSET + PV_LEN)
{
pvaddr = (rt_uint32_t *)(pos - PV_OFFSET);
}
else if (pos < KERNEL_VADDR_START + PV_OFFSET)
{
pvaddr = (rt_uint32_t *)rt_ioremap((void *)pos, __gs_addr_step * size);
if (!pvaddr)
{
LOG_E("Failed io remap@%#x.", pos);
return 0;
}
io_remaped = 1;
}
//LOG_I("Rpa@%#x v@%#x.", pos, pvaddr);
dbg_mm_copy_smart((rt_uint32_t)buffer, (rt_uint32_t)pvaddr, size);
dbg_mm_hex_smart(pos, (rt_uint32_t)pvaddr, size);
if (io_remaped)
{
rt_iounmap((void *)pvaddr);
}
return size;
}
extern void rt_hw_cpu_dcache_clean(void *addr, int size);
static rt_size_t dbg_mm_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
rt_uint32_t pvaddr = 0;
int io_remaped = 0;
if (pos > KERNEL_VADDR_START + PV_OFFSET && pos < KERNEL_VADDR_START + PV_OFFSET + PV_LEN)
{
pvaddr = (rt_uint32_t)(pos - PV_OFFSET);
}
else if (pos < KERNEL_VADDR_START + PV_OFFSET)
{
pvaddr = (rt_uint32_t)rt_ioremap((void *)pos, size * __gs_addr_step);
if (!pvaddr)
{
LOG_E("Failed io remap@%#x.", pos);
return 0;
}
io_remaped = 1;
}
//LOG_I("pa@%#x v@%#x len=%u.", pvaddr, buffer, size);
dbg_mm_copy_smart((rt_uint32_t)pvaddr, (rt_uint32_t)buffer, size);
dbg_mm_hex_smart(pos, (rt_uint32_t)pvaddr, size);
if (io_remaped)
{
rt_iounmap((void *)pvaddr);
}
else
{
rt_hw_cpu_dcache_clean((void *)pvaddr, size * __gs_addr_step);
}
return size;
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops dbg_mm_ops =
{
dbg_mm_init,
dbg_mm_open,
dbg_mm_close,
dbg_mm_read,
dbg_mm_write,
dbg_mm_control
};
#endif
int rt_hw_dbg_mm_init(void)
{
/* set device type */
gs_dbg_mm_dev.type = RT_Device_Class_Miscellaneous;
/* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
gs_dbg_mm_dev.ops = &dbg_mm_ops ;
#else
gs_dbg_mm_dev.init = dbg_mm_init;
gs_dbg_mm_dev.open = dbg_mm_open;
gs_dbg_mm_dev.close = dbg_mm_close;
gs_dbg_mm_dev.read = dbg_mm_read;
gs_dbg_mm_dev.write = dbg_mm_write;
gs_dbg_mm_dev.control = dbg_mm_control;
#endif
/* register to device manager */
rt_device_register(&gs_dbg_mm_dev, "dbg_mm", RT_DEVICE_FLAG_RDWR);
return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_dbg_mm_init);
/*
* dbg_mm <-w> <-b width> <-s step> addr
* */
long dbg_mm(int argc, char * argv[])
{
int i = 0;
rt_uint32_t paddr = 0x0;
rt_size_t len = 1;
int widths_in_bits = 0, addr_in_step = -1, read_or_write = 0 /* 默认读数据 */;
rt_uint32_t buffer[128];
int opt;
optind = 0;
while ((opt = getopt(argc, argv, "wb:s:")) != -1)
{
switch (opt)
{
case 'b':widths_in_bits = atoi(optarg);break;
case 's':addr_in_step = atoi(optarg);break;
/* 修改为写数据 */
case 'w':read_or_write = 1;break;
default:break;
}
}
if (widths_in_bits)
{
if (!gs_dbg_mm_dev.ops->control(&gs_dbg_mm_dev, DBG_MM_SET_WIDTH_BITS, &widths_in_bits))
{
LOG_I("widths_int_bits:%d.", widths_in_bits);
}
}
else
{
if (!gs_dbg_mm_dev.ops->control(&gs_dbg_mm_dev, DBG_MM_GET_WIDTH_BITS, &widths_in_bits))
{
LOG_I("widths_int_bits:%d.", widths_in_bits);
}
}
if (addr_in_step != -1)
{
if (!gs_dbg_mm_dev.ops->control(&gs_dbg_mm_dev, DBG_MM_SET_ADDR_STEP, &addr_in_step))
{
LOG_I("addr_in_step:%d.", addr_in_step);
}
}
else
{
if (!gs_dbg_mm_dev.ops->control(&gs_dbg_mm_dev, DBG_MM_GET_ADDR_STEP, &addr_in_step))
{
LOG_I("addr_in_step:%d.", addr_in_step);
}
}
if (optind >= argc)
{
LOG_E("invalid format");
return -2;
}
if (rt_strstr(argv[optind], "0x") || rt_strstr(argv[optind], "0X"))
{
sscanf(argv[optind++], "0x%x", &paddr);
if (paddr % (widths_in_bits / 8))
{
LOG_E("addr should align with:%d", widths_in_bits / 8);
return -3;
}
}
else
{
LOG_E("invalid address format, use 0x/0X prefix please.");
return -4;
}
if (!read_or_write)
{
if (optind < argc)
{
len = atoi(argv[optind]);
}
#ifdef RT_USING_DEVICE_OPS
gs_dbg_mm_dev.ops->read(&gs_dbg_mm_dev, paddr, buffer, len);
#else
gs_dbg_mm_dev.read(&gs_dbg_mm_dev, paddr, buffer, len);
#endif
}
else
{
for (i = 0; i < argc - optind; i++)
{
if (rt_strstr(argv[i + optind], "0x") || rt_strstr(argv[i + optind], "0X"))
{
switch (addr_in_step)
{
case 1:
sscanf(argv[i + optind], "0x%hhx", (rt_uint8_t *)((rt_uint32_t)buffer + i * addr_in_step));
break;
case 2:
sscanf(argv[i + optind], "0x%hx", (rt_uint16_t *)((rt_uint32_t)buffer + i * addr_in_step));
break;
case 4:
sscanf(argv[i + optind], "0x%x", (rt_uint32_t *)((rt_uint32_t)buffer + i * addr_in_step));
break;
default:
break;
}
}
else
{
LOG_E("invalid data format, use 0x/0X prefix please.");
return -3;
}
}
#ifdef RT_USING_DEVICE_OPS
gs_dbg_mm_dev.ops->write(&gs_dbg_mm_dev, paddr, buffer, i);
#else
gs_dbg_mm_dev.write(&gs_dbg_mm_dev, paddr, buffer, i);
#endif
}
return 0;
}
MSH_CMD_EXPORT(dbg_mm, read dbg mm);
实现的效果是这样的:
为什么实现的这么复杂呢,因为我调试的时候发现,IMX6ULL 对外设地址的对齐访问要求的比较严格。以0x20e01e0 这个地址为例,查看手册:
这个地址是 32 bit的,如果我以1字节对齐访问就是出现这样的错误:
[0mbacktrace:
[33m[98553] W/BACKTRACE: unwind: Unsupported personality routine 81019b40 in the index at c00ed168
[0m
data abort:Execption:
r00:0xc0144234 r01:0xf01951e0 r02:0xf01951e0 r03:0xc0144234
r04:0xc0002e5c r05:0xdeadbeef r06:0xdeadbeef r07:0xdeadbeef
r08:0xdeadbeef r09:0xdeadbeef r10:0xdeadbeef
fp :0xc01441f4 ip :0x00000000
sp :0xc01441d8 lr :0xc0002f3c pc :0xc0002ca0
cpsr:0x60000013
dfsr:0x00001008
ttbr0:0x8012c018
dfar:0xf01951e0
0xf01951e0 -> 0x020e01e0
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 20 running 0x000001dc 0x00001000 38% 0x00000007 -09
sysinfo_ 25 suspend 0x0000011c 0x00004000 02% 0x0000012c 000
sys_work 23 suspend 0x00000088 0x00001000 03% 0x0000000a 000
usbd 8 suspend 0x000000ec 0x00001000 05% 0x00000014 -09
link_d1 30 suspend 0x000000c4 0x00001000 11% 0x00000002 000
tcpip 10 suspend 0x00000110 0x00002000 08% 0x0000000f -09
etx 12 suspend 0x000000d4 0x00002000 03% 0x00000010 -09
erx 12 suspend 0x000000dc 0x00002000 08% 0x00000008 -09
mmcsd_de 22 suspend 0x000000dc 0x00001000 15% 0x00000002 -09
tidle0 31 ready 0x00000068 0x00002000 02% 0x0000000b 000
timer 4 suspend 0x00000084 0x00001000 03% 0x00000009 000
main 10 suspend 0x000000cc 0x00001000 34% 0x00000007 000
shutdown...
(0) assertion failed at function:rt_hw_cpu_shutdown, line number:87
再举一个例子0x21a0000地址为例:
如果我以32bit对齐访问,还是会出问题:
backtrace:
[109559] W/BACKTRACE: unwind: Unsupported personality routine 81019b40 in the index at c00ed168
data abort:Execption:
r00:0xc0144234 r01:0xf0195000 r02:0xf0195000 r03:0xc0144234
r04:0xc0002e5c r05:0xdeadbeef r06:0xdeadbeef r07:0xdeadbeef
r08:0xdeadbeef r09:0xdeadbeef r10:0xdeadbeef
fp :0xc01441f4 ip :0x00000000
sp :0xc01441d8 lr :0xc0002f3c pc :0xc0002cc8
cpsr:0x60000013
dfsr:0x00001008
ttbr0:0x8012c018
dfar:0xf0195000
0xf0195000 -> 0x021a0000
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 20 running 0x000001dc 0x00001000 38% 0x00000002 -09
sysinfo_ 25 suspend 0x0000011c 0x00004000 02% 0x0000012c 000
sys_work 23 suspend 0x00000088 0x00001000 03% 0x0000000a 000
usbd 8 suspend 0x000000ec 0x00001000 05% 0x00000014 -09
link_d1 30 suspend 0x000000c4 0x00001000 11% 0x00000002 000
tcpip 10 suspend 0x00000110 0x00002000 08% 0x00000011 -09
etx 12 suspend 0x000000d4 0x00002000 03% 0x00000010 -09
erx 12 suspend 0x000000dc 0x00002000 08% 0x0000000b -09
mmcsd_de 22 suspend 0x000000dc 0x00001000 15% 0x00000002 -09
tidle0 31 ready 0x00000068 0x00002000 02% 0x00000003 000
timer 4 suspend 0x00000084 0x00001000 03% 0x00000009 000
main 10 suspend 0x000000cc 0x00001000 34% 0x00000007 000
为了保证可以改善上述问题,我开发了这个驱动用来调试物理内存。根据上述例子,查看的步骤是这样的:
msh />dbg_mm -b 32 -s 4 0x20e01e0
[86777] I/dbg.mm: widths_int_bits:32.
[86782] I/dbg.mm: addr_in_step:4.
0x020e01e0:0x00000010
msh />dbg_mm -b 16 -s 4 0x21a0000 5
[37268] I/dbg.mm: widths_int_bits:16.
[37272] I/dbg.mm: addr_in_step:4.
0x021a0000:0x0000 0x001f 0x0080 0x0081
0x021a0010:0x0078
这个工具还支持修改指定的物理地址,我以内存为例记录下方法:
msh />dbg_mm -b 16 -s 2 0x90000000 16
[30352] I/dbg.mm: widths_int_bits:16.
[30356] I/dbg.mm: addr_in_step:2.
0x90000000:0x1111 0x1111 0x2222 0x0000
0x90000008:0x3333 0x3333 0x0067 0x0000
0x90000010:0x0012 0x0000 0x1900 0x0000
0x90000018:0x9dbc 0xc00b 0x00e1 0x0500
msh />dbg_mm -b 16 -s 2 -w 0x90000000 0xaaaa 0xbbbb 0xcccc 0xdddd 0xeeee 0xffff
[69352] I/dbg.mm: widths_int_bits:16.
[69356] I/dbg.mm: addr_in_step:2.
0x90000000:0xaaaa 0xbbbb 0xcccc 0xdddd
0x90000008:0xeeee 0xffff
使用上述命令的时候还是要小心不要越界,否则还是会崩溃的。
原作者:iysheng