incubator-nuttx/mm/kmap/kmm_map.c

531 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)
{
/* Initialize the architecture specific part */
DEBUGVERIFY(up_addrenv_kmap_init());
/* Then, the local vmap */
g_kmm_map_vpages = gran_initialize((FAR void *)CONFIG_ARCH_KMAP_VBASE,
CONFIG_ARCH_KMAP_NPAGES << MM_PGSHIFT,
MM_PGSHIFT, MM_PGSHIFT);
DEBUGASSERT(g_kmm_map_vpages != NULL);
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