Michael suggested on IRC we write a TLB miss handler that somewhat
resembles real life, to illustrate what the kernel needs. Here's a
first try.

/*
 * For the general structure of Linux page tables, see
 * http://lwn.net/Articles/106177/
 * In the case of LM32, we only need three levels, hence to PMD.
 *
 * Also note that I simplified the types. So there's a lot of uint32_t,
 * which in the kernel would be pmd_t, pte_t, etc. Also, in the kernel,
 * the many operations are encapsulated in macros or inline functions.
 */


#define DTLBMA          (*(volatile uint32_t *) 0x....) /* miss VA */
#define TLBCTRL         (*(volatile uint32_t *) 0x....) /* TLB command */
#define TLBVADDR        (*(volatile uint32_t *) 0x....) /* VA operand */
#define TLBPADDR        (*(volatile uint32_t *) 0x....) /* PA operand */

#define TLB_CMD_UPDATE  (2 << 1)


void handle_tlb_miss(void)                      /* 0 */
{
        uint32_t addr = DTLBMA;                 /* virtual address of miss */
        uint32_t **pgd = current_task->pgd;     /* 1st level page table */
        uint32_t *pmd = pgd[addr >> 22];        /* 2nd level page table */

        if (unlikely(!pmd))                     /* 1 */
                goto no_page_table_fault;

        uint32_t pte = pmd[(addr >> 12) & 1023];

        if (unlikely(!pte_present(pte)))        /* 2 */
                goto no_page_fault;

        TLBVADDR = addr;                        /* 3, 4 */
        TLBPADDR = pte;
        TLBCTRL = TLB_CMD_UPDATE;               /* 5 */

        return; /* from exception */

no_page_table_fault:
        /* page fault (table miss) exception */

no_page_fault:                                  /* 6 */
        /* page fault (page miss) exception */

        /* 7 */
}


Notes:

0) or handle_itlb_miss / handle_dtlb_miss, depending on whether we
   want separate handlers.

1) assumes NULL indicates a page table is absent (i.e., the virtual
   address isn't mapped.)

2) the page table entry may contain non-zero bits also if the page
   is absent. Therefore, we use pte_present, which does whatever
   test is necessary. (E.g., on x86, it would be something similar
   to

   static inline int pte_present(pte_t pte)
   {
        return pte & PAGE_PRESENT_BIT;
   }

3) I lost track of how the TLB is currently selected. So add TLB
   selection bits as needed :-)

   Ideally, the TLB selection would be implicit, e.g., by encoding
   it in the lower 10 bits of DTLBMA/TLBVADDR, or by remembering
   what kind of TLB miss exception we're handling at the moment.

4) ideally, DTLBMA == TLBVADDR so that we don't have to copy the
   address.

5) if a TLB entry update is the only operation that uses TLBPADDR,
   then we could make a write to TLBPADDR trigger an implicit
   TLB update, and eliminate the CSR write to TLBCTRL.

6) no_page_table_fault and no_page_fault may use the same code
   path, i.e., a general "map this page" operation. Note that
   handling a page fault can be a very complex operation, with
   "disk" I/O and other processes getting scheduled (possibly
   incurring their own misses and faults).

7) the page fault code path may or may not update the TLB. If it
   doesn't, then we'd return from the TLB miss exception and
   just get the same exception again, but this time with the
   page present.

This architecture assumes that the hardware will perform a
permission check when retrying the faulting operation. Thus, a
write to a read-only page (e.g., an executable page), could
cause this sequence:

        DTLB miss exception (1)
        -> page table is not present
                -> create page table, return
        DTLB miss exception (2)
        -> page is not present
                -> create/read page,
                   return without updating the TLB (see 7)
        DTLB miss exception (3)
        -> page is not mapped in TLB
                -> map page
        DTLB permission exception
        -> figure out what to do, e.g., segfault

If we're after a fork() and the write is to a data page that
has been cloned but not accessed yet, the sequence would end
like this:

        ...
        DTLB miss exception (3)
        -> page is not mapped in TLB
                -> map page
        /* until now it's the same */
        DTLB permission exception
        -> oh, it's a copy-on-write (COW) page
                -> new_page = allocate_page();
                -> memcpy(new_page, old_page, PAGE_SIZE);
                -> invalidate TLB entry (*)
        DTLB miss exception (4)
        -> page is not mapped in TLB
                -> map page
        write succeeds now

(*) if, instead of invalidating the TLB entry, we update it,
    then we'd avoid the 4th DTLB miss.

- Werner
_______________________________________________
http://lists.milkymist.org/listinfo.cgi/devel-milkymist.org
IRC: #milkymist@Freenode

Reply via email to