7c893ddfc2
Mapping a physical page to a kernel virtual page is very simple and does not need the kernel vma list, just get the kernel addressable virtual address for the page.
525 lines
14 KiB
C
525 lines
14 KiB
C
/****************************************************************************
|
|
* mm/kmap/kmm_map.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
|
|
#include <nuttx/addrenv.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/compiler.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/mm/gran.h>
|
|
#include <nuttx/mm/map.h>
|
|
#include <nuttx/pgalloc.h>
|
|
#include <nuttx/sched.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#if defined(CONFIG_BUILD_KERNEL)
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static GRAN_HANDLE g_kmm_map_vpages;
|
|
static struct mm_map_s g_kmm_map;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: get_user_pages
|
|
*
|
|
* Description:
|
|
* Get the physical pages mapped to a user virtual memory region.
|
|
*
|
|
* Input Parameters:
|
|
* pages - Pointer to buffer where the page addresses are recorded.
|
|
* npages - Amount of pages.
|
|
* vaddr - Start address of the user virtual memory region.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is returned
|
|
* on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int get_user_pages(FAR void **pages, size_t npages, uintptr_t vaddr)
|
|
{
|
|
FAR struct tcb_s *tcb = nxsched_self();
|
|
uintptr_t page;
|
|
int i;
|
|
|
|
/* Find the pages associated with the user virtual address space */
|
|
|
|
for (i = 0; i < npages; i++, vaddr += MM_PGSIZE)
|
|
{
|
|
page = up_addrenv_find_page(&tcb->addrenv_curr->addrenv, vaddr);
|
|
if (!page)
|
|
{
|
|
/* Something went wrong, get out */
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
pages[i] = (FAR void *)page;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: map_pages
|
|
*
|
|
* Description:
|
|
* Map pages into kernel virtual memory.
|
|
*
|
|
* Input Parameters:
|
|
* pages - Pointer to buffer that contains the physical page addresses.
|
|
* npages - Amount of pages.
|
|
* prot - Access right flags.
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the mapped virtual memory on success; NULL on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR void *map_pages(FAR void **pages, size_t npages, int prot)
|
|
{
|
|
struct mm_map_entry_s entry;
|
|
FAR void *vaddr;
|
|
size_t size;
|
|
int ret;
|
|
|
|
/* The region size is full pages */
|
|
|
|
size = npages << MM_PGSHIFT;
|
|
|
|
/* Find a virtual memory area that fits */
|
|
|
|
vaddr = gran_alloc(g_kmm_map_vpages, size);
|
|
if (!vaddr)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Map the pages into the kernel page directory */
|
|
|
|
ret = up_addrenv_kmap_pages(pages, npages, (uintptr_t)vaddr, prot);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_vaddr;
|
|
}
|
|
|
|
entry.vaddr = vaddr;
|
|
entry.length = size;
|
|
entry.offset = 0;
|
|
entry.munmap = NULL;
|
|
|
|
ret = mm_map_add(&g_kmm_map, &entry);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_pgmap;
|
|
}
|
|
|
|
return vaddr;
|
|
|
|
errout_with_pgmap:
|
|
up_addrenv_kunmap_pages((uintptr_t)vaddr, npages);
|
|
errout_with_vaddr:
|
|
gran_free(&g_kmm_map_vpages, vaddr, size);
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: map_single_user_page
|
|
*
|
|
* Description:
|
|
* Map a single user page into kernel memory.
|
|
*
|
|
* Input Parameters:
|
|
* pages - Pointer to buffer that contains the physical page addresses.
|
|
* npages - Amount of pages.
|
|
* prot - Access right flags.
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the mapped virtual memory on success; NULL on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR void *map_single_user_page(uintptr_t vaddr)
|
|
{
|
|
FAR struct tcb_s *tcb = nxsched_self();
|
|
uintptr_t page;
|
|
|
|
/* Find the page associated with this virtual address */
|
|
|
|
page = up_addrenv_find_page(&tcb->addrenv_curr->addrenv, vaddr);
|
|
if (!page)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
vaddr = up_addrenv_page_vaddr(page);
|
|
return (FAR void *)vaddr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: map_single_page
|
|
*
|
|
* Description:
|
|
* Map (find) a single page from the kernel addressable virtual memory
|
|
* pool.
|
|
*
|
|
* Input Parameters:
|
|
* page - The physical page.
|
|
*
|
|
* Returned Value:
|
|
* The kernel virtual address for the page, or NULL if page is not kernel
|
|
* addressable.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR void *map_single_page(uintptr_t page)
|
|
{
|
|
return (FAR void *)up_addrenv_page_vaddr(page);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: is_kmap_vaddr
|
|
*
|
|
* Description:
|
|
* Return true if the virtual address, vaddr, lies in the kmap address
|
|
* space.
|
|
*
|
|
* Input Parameters:
|
|
* vaddr - The kernel virtual address where the mapping begins.
|
|
*
|
|
* Returned Value:
|
|
* True if vaddr is in the kmap address space; false otherwise.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool is_kmap_vaddr(uintptr_t vaddr)
|
|
{
|
|
return (vaddr >= CONFIG_ARCH_KMAP_VBASE && vaddr < ARCH_KMAP_VEND);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_map_lock
|
|
*
|
|
* Description:
|
|
* Get exclusive access to the kernel mm_map
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int kmm_map_lock(void)
|
|
{
|
|
return nxrmutex_lock(&g_kmm_map.mm_map_mutex);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_map_unlock
|
|
*
|
|
* Description:
|
|
* Relinquish exclusive access to the kernel mm_map
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void kmm_map_unlock(void)
|
|
{
|
|
DEBUGVERIFY(nxrmutex_unlock(&g_kmm_map.mm_map_mutex));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_map_initialize
|
|
*
|
|
* Description:
|
|
* Initialize the kernel dynamic mapping module.
|
|
*
|
|
* Input Parameters:
|
|
* None.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void kmm_map_initialize(void)
|
|
{
|
|
g_kmm_map_vpages = gran_initialize((FAR void *)CONFIG_ARCH_KMAP_VBASE,
|
|
CONFIG_ARCH_KMAP_NPAGES << MM_PGSHIFT,
|
|
MM_PGSHIFT, MM_PGSHIFT);
|
|
DEBUGVERIFY(g_kmm_map_vpages);
|
|
mm_map_initialize(&g_kmm_map, true);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_map_pages
|
|
*
|
|
* Description:
|
|
* Map pages into kernel virtual memory.
|
|
*
|
|
* Input Parameters:
|
|
* pages - Pointer to buffer that contains the physical page addresses.
|
|
* npages - Amount of pages.
|
|
* prot - Access right flags.
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the mapped virtual memory on success; NULL on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR void *kmm_map(FAR void **pages, size_t npages, int prot)
|
|
{
|
|
uintptr_t vaddr;
|
|
|
|
if (!pages || !npages || npages > CONFIG_ARCH_KMAP_NPAGES)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* A single page can be addressed directly, if it is a kernel page */
|
|
|
|
if (npages == 1)
|
|
{
|
|
return map_single_page((uintptr_t)pages[0]);
|
|
}
|
|
|
|
/* Attempt to map the pages */
|
|
|
|
vaddr = (uintptr_t)map_pages(pages, npages, prot);
|
|
if (!vaddr)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return (FAR void *)vaddr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_unmap
|
|
*
|
|
* Description:
|
|
* Unmap a previously allocated kernel virtual memory area.
|
|
*
|
|
* Input Parameters:
|
|
* kaddr - The kernel virtual address where the mapping begins.
|
|
*
|
|
* Returned Value:
|
|
* None.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void kmm_unmap(FAR void *kaddr)
|
|
{
|
|
FAR struct mm_map_entry_s *entry;
|
|
unsigned int npages;
|
|
int ret;
|
|
|
|
/* Speed optimization: check that addr is within kmap area */
|
|
|
|
if (!is_kmap_vaddr((uintptr_t)kaddr))
|
|
{
|
|
/* Nope: get out */
|
|
|
|
return;
|
|
}
|
|
|
|
/* Lock the mapping list when we fiddle around with it */
|
|
|
|
ret = kmm_map_lock();
|
|
if (ret == OK)
|
|
{
|
|
/* Find the entry, it is OK if none found */
|
|
|
|
entry = mm_map_find(&g_kmm_map, kaddr, 1);
|
|
if (entry)
|
|
{
|
|
npages = MM_NPAGES(entry->length);
|
|
|
|
/* Remove the mappings from the page directory */
|
|
|
|
up_addrenv_kunmap_pages((uintptr_t)entry->vaddr, npages);
|
|
|
|
/* Release the virtual memory area for use */
|
|
|
|
gran_free(&g_kmm_map_vpages, entry->vaddr, entry->length);
|
|
|
|
/* Remove the mapping from the kernel mapping list */
|
|
|
|
mm_map_remove(&g_kmm_map, entry);
|
|
}
|
|
|
|
kmm_map_unlock();
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_map_user
|
|
*
|
|
* Description:
|
|
* Map a region of user memory (physical pages) for kernel use through
|
|
* a continuous virtual memory area.
|
|
*
|
|
* Input Parameters:
|
|
* uaddr - The user virtual address where mapping begins.
|
|
* size - Size of the region.
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the virtual memory area, or NULL if out of memory.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR void *kmm_map_user(FAR void *uaddr, size_t size)
|
|
{
|
|
FAR void **pages;
|
|
uintptr_t vaddr;
|
|
uintptr_t offset;
|
|
size_t npages;
|
|
int ret;
|
|
|
|
/* Find the kernel addressable virtual address, or map the user memory */
|
|
|
|
vaddr = (uintptr_t)uaddr;
|
|
|
|
/* If the memory is not user memory, get out */
|
|
|
|
if (!up_addrenv_user_vaddr(vaddr))
|
|
{
|
|
/* Not user memory, get out */
|
|
|
|
return uaddr;
|
|
}
|
|
|
|
/* How many pages (including partial) does the space encompass ? */
|
|
|
|
offset = vaddr & MM_PGMASK;
|
|
vaddr = MM_PGALIGNDOWN(vaddr);
|
|
npages = MM_NPAGES(offset + size);
|
|
|
|
/* Does the area fit in 1 page, including page boundary crossings ? */
|
|
|
|
if (npages == 1)
|
|
{
|
|
/* Yes, can simply return the kernel addressable virtual address */
|
|
|
|
vaddr = (uintptr_t)map_single_user_page(vaddr);
|
|
return (FAR void *)(vaddr + offset);
|
|
}
|
|
|
|
/* No, the area must be mapped into kernel virtual address space */
|
|
|
|
pages = (FAR void **)kmm_zalloc(npages * sizeof(FAR void *));
|
|
if (!pages)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Fetch the physical pages for the user virtual address range */
|
|
|
|
ret = get_user_pages(pages, npages, vaddr);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_pages;
|
|
}
|
|
|
|
/* Map the physical pages to kernel memory */
|
|
|
|
vaddr = (uintptr_t)map_pages(pages, npages, PROT_READ | PROT_WRITE);
|
|
if (!vaddr)
|
|
{
|
|
goto errout_with_pages;
|
|
}
|
|
|
|
/* Ok, we have a virtual memory area, add the offset back */
|
|
|
|
kmm_free(pages);
|
|
return (FAR void *)(vaddr + offset);
|
|
|
|
errout_with_pages:
|
|
kmm_free(pages);
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: kmm_map_user_page
|
|
*
|
|
* Description:
|
|
* Map a single physical page into kernel virtual memory. Typically just
|
|
* returns the kernel addressable page pool virtual address.
|
|
*
|
|
* Input Parameters:
|
|
* uaddr - The virtual address of the user page.
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the new address environment, or NULL if out of memory.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR void *kmm_map_user_page(FAR void *uaddr)
|
|
{
|
|
uintptr_t vaddr;
|
|
uintptr_t offset;
|
|
|
|
/* Find the kernel addressable virtual address, or map the user memory */
|
|
|
|
vaddr = (uintptr_t)uaddr;
|
|
|
|
/* If the memory is not user memory, get out */
|
|
|
|
if (!up_addrenv_user_vaddr(vaddr))
|
|
{
|
|
/* Not user memory, get out */
|
|
|
|
return uaddr;
|
|
}
|
|
|
|
/* Record the offset and add it back later */
|
|
|
|
offset = vaddr & MM_PGMASK;
|
|
vaddr = MM_PGALIGNDOWN(vaddr);
|
|
|
|
vaddr = (uintptr_t)map_single_user_page(vaddr);
|
|
if (!vaddr)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return (FAR void *)(vaddr + offset);
|
|
}
|
|
|
|
#endif
|