什么是更新MMU翻译表的正确方法?

3

我在我的s3c2440板上启用了MMU(3G-4G内存::故障属性),当我没有读/写3G-4G内存时,一切都很好。为了测试页面故障向量,我向3G地址写入了0xFF,正如我所预期的那样,我从FSR得到了正确的值,所以我在_do_page_fault()中执行了这个步骤:

.....                 // set new page to translation table 
.....
invlidate_icache ();  // clear icache 
clr_dcache ();        // wb is used ,clear dcache 
invalidate_ttb ();    // invalidate translation table 

然后 ISR_dataabort 返回,我读取 3G 地址以获取之前写入的 0xFF。

不幸的是,我再次遇到了数据异常。(我确定我设置的翻译表值是正确的)

那么更新 MMU 翻译表的正确方法是什么呢?非常感谢任何帮助!

这是我用来测试的主要代码(只是为了一些测试,因为我对 ARM 架构有点陌生,所以这段代码可能有点丑陋)。

/* MMU TTB 0 BASE ATTR */
#define TTB0_FAULT          (0|(1<<4))          /* TTB FAULT */
#define TTB0_COARSE         (1|(1<<4))          /* COARSE PAGE BASE ADDR */
#define TTB0_SEG            (2|(1<<4))          /* SEG BASE ADDR */
#define TTB0_FINE           (3|(1<<4))          /* FINE PAGE BASE ADDR */

/* MMU TTB 1 BASE ATTR */ 
#define TTB1_FAULT          (0)
#define TTB1_LPG            (1)                 /* Large page */
#define TTB1_SPG            (2)                 /* small page */
#define TTB1_TPG            (3)                 /* tiny page */

/* domain access priority level */
#define FAULT_PL            (0x0)               /* domain fault */
#define USR_PL              (0x1)               /* usr mode */
#define RSV_PL              (0x2)               /* reserved */
#define SYS_PL              (0x3)               /* sys mode */

#define DOMAIN_FAULT        (0x0<<5)            /* fault 0*/
#define DOMAIN_SYS          (0x1<<5)            /* sys 1*/
#define DOMAIN_USR          (0x2<<5)            /* usr 2*/

/* C,B bit */
#define CB                  (3<<2)              /* cache_on, write_back */
#define CNB                 (2<<2)              /* cache_on, write_through */ 
#define NCB                 (1<<2)              /* cache_off,WR_BUF on */
#define NCNB                (0<<2)              /* cache_off,WR_BUF off */

/* ap 2 bits */
#define AP_FAULT            (0<<10)             /* access deny */
#define AP_SU_ONLY          (1<<10)             /* rw su only */
#define AP_USR_RO           (2<<10)             /* sup=RW, user=RO */
#define AP_RW               (3<<10)             /* su=RW, user=RW */

/* page dir 1 ap0 */
#define AP0_SU_ONLY         (1<<4)             /* rw su only */
#define AP0_USR_RO          (2<<4)             /* sup=RW, user=RO */
#define AP0_RW              (3<<4)             /* su=RW, user=RW */

/* page dir 1 ap1 */
#define AP1_SU_ONLY         (1<<6)             /* rw su only */
#define AP1_USR_RO          (2<<6)             /* sup=RW, user=RO */
#define AP1_RW              (3<<6)             /* su=RW, user=RW */


/* page dir 1 ap2 */
#define AP2_SU_ONLY         (1<<8)             /* rw su only */
#define AP2_USR_RO          (2<<8)             /* sup=RW, user=RO */
#define AP2_RW              (3<<8)             /* su=RW, user=RW */

/* page dir 1 ap3 */
#define AP3_SU_ONLY         (1<<10)             /* rw su only */
#define AP3_USR_RO          (2<<10)             /* sup=RW, user=RO */
#define AP3_RW              (3<<10)             /* su=RW, user=RW */


#define RAM_START           (0x30000000)
#define KERNEL_ENTRY        (0x30300000)        /* BANK 6 (3M) */
#define KERNEL_STACK        (0x3001A000)        /* BANK 6 (16K + 64K + 16K + 8K) (8k kernel stack) */
#define IRQ_STACK           (0x3001B000)        /* 4K IRQ STACK */
#define KERNEL_IMG_SIZE     (0x20000)            

#define IRQ_STACK           (0x3001B000)

/* 16K aignment */
#define TTB_BASE            (0x30000000)        
#define PAGE_DIR0           (TTB_BASE)
#define TTB_FULL_SIZE       (0x4000)        
#define PAGE_DIR1           (TTB_BASE+TTB_FULL_SIZE)                 

#define PAGE_DIR0_SIZE      (0x4000)            /* 16k */


    void _do_page_fault (void)
    {
        //
        ...........
        // 
        // read the FSR && get the vaddr && type here 
    volatile unsigned *page_dir = (volatile unsigned*)(TTB_BASE);  
    unsigned index = vaddr >> 20,i = 0, j = 0;
    unsigned page = 0;

        if (!(page_dir[index] & ~(0x3FF) && (type == 0x0B))) {          /* page_dir empty */
            i = index & ~0x03;                                          
            if ( (page_dir[i+0] & ~(0x3FF)) || (page_dir [i+1] & ~(0x3FF))         
              || (page_dir[i+2] & ~(0x3FF)) || (page_dir [i+3] & ~(0x3FF)) )
            {
                panic ( "page dir is bad !\n" );                        /* 4 continuous page_dir must be 0 */
            }

            if (!(page = find_free_page ())) 
                panic ( "no more free page !\n" );                      /* alloc a page page dir*/

            page_dir[i+0] = (page + 0x000) | DOMAIN_USR | TTB0_COARSE ; /* small page 1st 1KB */
            page_dir[i+1] = (page + 0x400) | DOMAIN_USR | TTB0_COARSE ; /* small page 2nd 1KB */
            page_dir[i+2] = (page + 0x800) | DOMAIN_USR | TTB0_COARSE ; /* small page 3rd 1KB */
            page_dir[i+3] = (page + 0xC00) | DOMAIN_USR | TTB0_COARSE ; /* small page 4th 1KB */

            if (!(page = find_free_page ())) 
                panic ( "no more free page !\n" );                      /* alloc a page page table*/

            volatile unsigned *page_tbl = (volatile unsigned*) (page_dir[index] & ~(0x3FF));

            *page_tbl = page|AP0_RW|AP1_RW|AP2_RW|AP3_RW| NCNB|TTB1_SPG;/* small page is used */


            invalidate_icache ();

            for (i = 0; i < 64; i++)
            {
                for (j = 0;j < 8;j ++)
                    clr_invalidate_dcache ( (i<<26)|(j<<5) );
            }


            invalidate_tlb ();
        }
        ........
        //

    }

    /* here is the macros */

    #define invalidate_tlb()    \
    {\
         __asm__  __volatile__ (\
            "mov    r0,#0\n"\
            "mcr    p15,0,r0,c8,c7,0\n"\
            :::"r0" \
        );\
    }

    #define clr_invalidate_dcache(index)    \
    {\
        __asm__  __volatile__ (\
            "mcr    p15,0,%[i],c7,c14,2\n"\
            :: [i]"r"(index)\
        );\
    }


    #define invalidate_icache() \
    {\
        __asm__  __volatile__ (\
            "mov    r0,#0\n"\
            "mcr    p15,0,r0,c7,c5,0\n"\
            ::: "r0"\
        );\
    }

    #define invalidate_dcache() \
    {\
        __asm__  __volatile__ (\
            "mov    r0,#0\n"\
            "mcr    p15,0,r0,c7,c6,0\n"\
            ::: "r0"\
        );\
    }



    #define invalidate_idcache()    \
    {\
        __asm__  __volatile__ (\
            "mov    r0,#0\n"\
            "mcr    p15,0,r0,c7,c7,0\n"\
            :::"r0"\
        );\
    }\

1
请提供实际使用的代码。一小段拼写错误的伪代码,没有任何关于你正在使用哪个操作系统的信息,无法提供任何关于你问题所在的线索。此外,你确信你的翻译表项是正确的并不意味着它是正确的,请也包含这个信息。 - unixsmurf
我使用的代码已经发布在上面了,我不知道为什么当调用 invalidate_tbl() 时我的 s3c2440 会停止工作,你能帮我吗?谢谢! - true_casey
如果您不想显示定义,可以在中止之前显示L1L2表项的十六进制转储。请务必提供相对于TTB_BASEL2基址的偏移量。如果这样做,您可能会回答自己的问题。 - artless noise
所有的 #define 都已经发布了,谢谢你的帮助 ^_^ - true_casey
1个回答

4

注意:我假设TTB_BASE是主要的ARM L1页表。如果它是某些阴影,则需要根据unixsmurf显示更多代码。这是我最好的猜测...

您的page_dir既充当主要的L1条目,也充当L2细小页面表。 TTB_BASE应仅包含节、超级部分或指向子页表的指针。您需要为L2页表分配更多物理内存。

我猜想您的page_dir[i+0]page_dir[i+1]等正在覆盖其他L1部分条目和您的invalidate_tlb()使CPU能够具体化。 您应该使用L2page_tbl指针来设置/索引小/细小页面。也许您的内核代码在3G-4G空间中,并且正在覆盖一些关键的L1映射;任何奇怪的事情都可能发生。当前对page_tbl的使用不清楚。

您不能双重使用主要的L1表,因为它们对硬件有意义。我猜这只是一个错误?

主要的L1页表只有三种类型的条目,

  1. 节 - 1MB内存映射直接从虚拟到物理,没有第二个页面表。
  2. 超级部分 - 与节相同的4MB内存映射,但最小化TLB压力。
  3. 第2个页面表。 条目可以是1k、4k和64k。 细小页面表在表大小上为4k,在现代ARM设计中不存在。 每个条目在细小页面表中占据1k的地址。

主页表必须位于物理地址上,该地址是16k对齐,这也是它的大小。16k /(4字节/条目)* 1MB给出L1表的4GB地址范围。因此,主L1页表中的每个条目始终指向1MB条目。较粗的页面表是新版ARM上唯一的选项,并且它们引用1K L2表。

1K_L2_size * 4K_entry / (4bytes_per_entry) gives 1MB address space.
1K_L2_size * 64K_entry / (16bytes_per_entry) gives 1MB address space.

在超级节的主要L1中有四个1MB条目。在L2表中,每个64k大页有四个条目。如果您正在使用超级节,则没有L2条目。
我认为您可能混淆了超级节和大页?对于某些格式不正确的页表,只会在TLB使失效时显现,因此MMU映射将通过步骤从表中重新获取。
最后,您应该刷新D缓存并清除写缓冲区,以确保与内存一致。您可能还希望有一个内存屏障。
static inline void dcache_clean(void)
{
    const int zero = 0;
    asm volatile ("" ::: "memory"); /* barrier */
    /* clean entire D cache -> push to external memory. */
    asm volatile ("1: mrc p15, 0, r15, c7, c10, 3\n"
                    " bne 1b\n" ::: "cc");
    /* drain the write buffer */
    asm volatile ("mcr 15, 0, %0, c7, c10, 4"::"r" (zero));
}

此外,还有协处理器命令用于失效单个TLB条目,因为您只更改了数据故障中的vaddr/paddr映射的一部分。

另请参阅:ARM MMU教程虚拟内存结构和您的ARM架构参考手册


是的,TTB_BASE是一级页表,占用16KB物理地址。情况是我想使用段映射0x0000 - 0x30000000 && 0x40000000 - 0xA0000000,并使用粗略页映射(成本为1KB),0x30000000 - 0x34000000(SDRAM = 64M)。由于L2(粗略页表成本为1KB),我让4个连续的4个L2页表使用相同的页面。这就是为什么我使用“i = index&〜0x03”的原因。 - true_casey
1
一个 L1 页表占用 4 字节,它指向 1MB 的物理内存无论类型如何。当我使用粗略页时,L1 页表指向 1KB 的物理内存,但 find_free_page()返回 4KB 的页面,所以我让 L1 的 4 个连续条目指向同一个页面,如果 vaddr 是 3M,则 3M(4K 页面)的 L1 条目与 0M、1M、2M 相同。"i = index & ~3" 获取 4M 对齐条目。 - true_casey
不,那不是必需的,但添加这些信息会很有帮助。我在我的答案中添加了一些额外的代码可能会有所帮助。 - artless noise
非常感谢。我现在正在阅读ARM_ARM,已经找到了正确的更新MMU表的方法:
  1. 如果是写回缓存,则清除数据缓存
  2. 使数据缓存无效
  3. 使指令缓存无效
  4. 清空写缓冲区。
- true_casey
1
现在,我不需要关闭MMU,只需放入以下代码: `clr_all_dcache ();` `//dcache_clean ();` `invalidate_dcache ();` `__asm__ __volatile__ ("mcr p15,0,%0,c8,c6,1"::"r"(vaddr));` - true_casey
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接