7.5   Using the MMU Programmatically

This section describes the facilities provided for manipulating the MMU programmatically using low-level routines in vmLib. You can make data private to a task or code segment, make portions of memory noncacheable, or write-protect portions of memory. The fundamental structure used to implement virtual memory is the virtual memory context (VMC).

For a summary of the VxVMI routines, see the reference entry for vmLib.

7.5.1   Virtual Memory Contexts

A virtual memory context (VM_CONTEXT, defined in vmLib) is made up of a translation table and other information used for mapping a virtual address to a physical address. Multiple virtual memory contexts can be created and swapped in and out as desired.

Global Virtual Memory

Some system objects, such as text segments and semaphores, must be accessible to all tasks in the system regardless of which virtual memory context is made current. These objects are made accessible by means of global virtual memory. Global virtual memory is created by mapping all the physical memory in the system (the mapping is defined in sysPhysMemDesc) to the identical address in the virtual memory space. In the default system configuration, this initially gives a one-to-one relationship between physical memory and global virtual memory; for example, virtual address 0x5000 maps to physical address 0x5000. On some architectures, it is possible to use sysPhysMemDesc to set up virtual memory so that the mapping of virtual-to-physical addresses is not one-to-one; see 7.3 Virtual Memory Configuration for additional information.

Global virtual memory is accessible from all virtual memory contexts. Modifications made to the global mapping in one virtual memory context appear in all virtual memory contexts. Before virtual memory contexts are created, add all global memory with vmGlobalMap( ). Global memory that is added after virtual memory contexts are created may not be available to existing contexts.

Initialization

Global virtual memory is initialized by vmGlobalMapInit( ) in usrMmuInit( ), which is called from usrRoot( ). The routine usrMmuInit( ) is in installDir/target/src/config/usrMmuInit.c, and creates global virtual memory using sysPhysMemDesc. It then creates a default virtual memory context and makes the default context current. Optionally, it also enables the MMU.

Page States

Each virtual memory page (typically 8KB) has a state associated with it. A page can be valid/invalid, writable/nonwritable, or cacheable/noncacheable. See Table 7-2 for the associated constants.

Table 7-2:  State Flags


Constant
Description

VM_STATE_VALID  
Valid translation 
VM_STATE_VALID_NOT  
Invalid translation 
VM_STATE_WRITABLE  
Writable memory 
VM_STATE_WRITABLE_NOT  
Read-only memory 
VM_STATE_CACHEABLE  
Cacheable memory 
VM_STATE_CACHEABLE_NOT  
Noncacheable memory 

Validity
A valid state indicates the virtual-to-physical translation is true. When the translation tables are initialized, global virtual memory is marked as valid. All other virtual memory is initialized as invalid.

Writability
Pages can be made read-only by setting the state to nonwritable. This is used by VxWorks to write-protect all text segments.

Cacheability
The caching of memory pages can be prevented by setting the state flags to noncacheable. This is useful for memory that is shared between processors (including DMA devices).

Change the state of a page with the routine vmStateSet( ). In addition to specifying the state flags, a state mask must describe which flags are being changed; see Table 7-3. Additional architecture-dependent states are specified in vmLib.h.

Table 7-3:  State Masks


Constant
Description

VM_STATE_MASK_VALID  
Modify valid flag 
VM_STATE_MASK_WRITABLE  
Modify write flag 
VM_STATE_MASK_CACHEABLE  
Modify cache flag 

7.5.2   Private Virtual Memory

Private virtual memory can be created by creating a new virtual memory context. This is useful for protecting data by making it inaccessible to other tasks or by limiting access to specific routines. Virtual memory contexts are not automatically created for tasks, but can be created and swapped in and out in an application-specific manner.

At system initialization, a default context is created. All tasks use this default context. To create private virtual memory, a task must create a new virtual memory context using vmContextCreate( ), and make it current. All virtual memory contexts share the global mappings that are created at system initialization; see Figure 7-1. Only the valid virtual memory in the current virtual memory context (including global virtual memory) is accessible. Virtual memory defined in other virtual memory contexts is not accessible. To make another memory context current, use vmCurrentSet( ).

To create a new virtual-to-physical mapping, use vmMap( ); both the physical and virtual address must be determined in advance. The physical memory (which must be page aligned) can be obtained using valloc( ). The easiest way to determine the virtual address is to use vmGlobalInfoGet( ) to find a virtual page that is not a global mapping. With this scheme, if multiple mappings are required, a task must keep track of its own private virtual memory pages to guarantee it does not map the same non-global address twice.

When physical pages are mapped into new sections of the virtual space, the physical page is accessible from two different virtual addresses (a condition known as aliasing): the newly mapped virtual address and the virtual address equal to the physical address in the global virtual memory. This can cause problems for some architectures, because the cache may hold two different values for the same underlying memory location. To avoid this, invalidate the virtual page (using vmStateSet( )) in the global virtual memory. This also ensures that the data is accessible only when the virtual memory context containing the new mapping is current.

Figure 7-2 depicts two private virtual memory contexts. The new context (pvmc2) maps virtual address 0x6000000 to physical address 0x10000. To prevent access to this address from outside of this virtual context (pvmc1), the corresponding physical address (0x10000) must be set to invalid. If access to the memory is made using address 0x10000, a bus error occurs because that address is now invalid.

Example 7-1:  Private Virtual Memory Contexts

In the following code example, private virtual memory contexts are used for allocating memory from a task's private memory partition. The setup routine, contextSetup( ), creates a private virtual memory context that is made current during a context switch. The virtual memory context is stored in the field spare1 in the task's TCB. Switch hooks are used to save the old context and install the task's private context. Note that the use of switch hooks increases the context switch time. A user-defined memory partition is created using the private virtual memory context. The partition ID is stored in spare2 in the tasks TCB. Any task wanting a private virtual memory context must call contextSetup( ). A sample task to test the code is included.


/* contextExample.h - header file for vm contexts used by switch hooks */
#define NUM_PAGES (3)

/* context.c - use context switch hooks to make task private context current */ 
#include "vxWorks.h" #include "vmLib.h" #include "semLib.h" #include "taskLib.h" #include "taskHookLib.h" #include "memLib.h" #include "contextExample.h"
void privContextSwitch (WIND_TCB *pOldTask, WIND_TCB *pNewTask);
/************************************************************************ * * initContextSetup - install context switch hook * */
STATUS initContextSetup ( ) { /* Install switch hook */ if (taskSwitchHookAdd ((FUNCPTR) privContextSwitch) == ERROR) return (ERROR);
return (OK); }
/************************************************************************ * * contextSetup - initialize context and create separate memory partition * * Call only once for each task that wants a private context. * * This could be made into a create-hook routine if every task on the * system needs a private context. To use as a create hook, the code for * installing the new virtual memory context should be replaced by simply * saving the new context in spare1 of the task's TCB. */
STATUS contextSetup (void) { VM_CONTEXT_ID pNewContext; int pageSize; int pageBlkSize; char * pPhysAddr; char * pVirtAddr; UINT8 * globalPgBlkArray; int newMemSize; int index; WIND_TCB * pTcb;
/* create context */
pNewContext = vmContextCreate();
/* get page and page block size */
pageSize = vmPageSizeGet (); pageBlkSize = vmPageBlockSizeGet (); newMemSize = pageSize * NUM_PAGES;
/* allocate physical memory that is page aligned */
if ((pPhysAddr = (char *) valloc (newMemSize)) == NULL) return (ERROR);
/* Select virtual address to map. For this example, since only one page * block is used per task, simply use the first address that is not a * global mapping. vmGlobalInfoGet( ) returns a boolean array where each * element corresponds to a block of virtual memory. */
globalPgBlkArray = vmGlobalInfoGet(); for (index = 0; globalPgBlkArray[index] == TRUE; index++) ; pVirtAddr = (char *) (index * pageBlkSize);
/* map physical memory to new context */
if (vmMap (pNewContext, pVirtAddr, pPhysAddr, newMemSize) == ERROR) { free (pPhysAddr); return (ERROR); }
/* * Set state in global virtual memory to be invalid - any access to * this memory must be done through new context. */
if (vmStateSet(pNewContext, pPhysAddr, newMemSize, VM_STATE_MASK_VALID, VM_STATE_VALID_NOT) == ERROR) return (ERROR);
/* get tasks TCB */
pTcb = taskTcb (taskIdSelf());
/* change virtual memory contexts */
/* * Stash the current vm context in the spare TCB field -- the switch * hook will install this when this task gets swapped out. */
pTcb->spare1 = (int) vmCurrentGet();
/* install new tasks context */
vmCurrentSet (pNewContext);
/* create new memory partition and store id in task's TCB */
if ((pTcb->spare2 = (int) memPartCreate (pVirtAddr,newMemSize)) == NULL) return (ERROR);
return (OK); }
/******************************************************************* * * privContextSwitch - routine to be executed on a context switch * * If old task had private context, save it. If new task has private * context, install it. */
void privContextSwitch ( WIND_TCB *pOldTcb, WIND_TCB *pNewTcb )
{ VM_CONTEXT_ID pContext = NULL; /* If previous task had private context, save it--reset previous context. */
if (pOldTcb->spare1) { pContext = (VM_CONTEXT_ID) pOldTcb->spare1; pOldTcb->spare1 = (int) vmCurrentGet ();
/* restore old context */
vmCurrentSet (pContext); }
/* * If next task has private context, map new context and save previous * context in task's TCB. */
if (pNewTcb->spare1) { pContext = (VM_CONTEXT_ID) pNewTcb->spare1; pNewTcb->spare1 = (int) vmCurrentGet();
/* install new tasks context */
vmCurrentSet (pContext); } }

/* taskExample.h - header file for testing VM contexts used by switch hook */ 
/* This code is used by the sample task. */
#define MAX (10000000)
typedef struct myStuff { int stuff; int myStuff; } MY_DATA;

/* testTask.c - task code to test switch hooks */
#include "vxWorks.h" #include "memLib.h" #include "taskLib.h" #include "stdio.h" #include "vmLib.h" #include "taskExample.h"
IMPORT char *string = "test\n";
MY_DATA *pMem;
/************************************************************************ * * testTask - allocate private memory and use it * * Loop forever, modifying memory and printing out a global string. Use this * in conjunction with testing from the shell. Since pMem points to private * memory, the shell should generate a bus error when it tries to read it. * For example: *     -> sp testTask *     -> d pMem */
STATUS testTask (void) { int val; WIND_TCB *myTcb;
/* install private context */
if (contextSetup () == ERROR) return (ERROR);
/* get TCB */
myTcb = taskTcb (taskIdSelf ());
/* allocate private memory */
if ((pMem = (MY_DATA *) memPartAlloc((PART_ID) myTcb->spare2, sizeof (MY_DATA))) == NULL) return (ERROR);
/* * Forever, modify data in private memory and display string in * global memory. */
FOREVER { for (val = 0; val <= MAX; val++) { /* modify structure */
pMem->stuff = val; pMem->myStuff = val / 2;
/* make sure can access global virtual memory */
printf (string);
taskDelay (sysClkRateGet() * 10); } } return (OK); }
/************************************************************************ * * testVmContextGet - return a task's virtual memory context stored in TCB * * Used with vmContextShow()1
to display a task's virtual memory context.
* For example, from the shell, type:
*   -> tid = sp (testTask)
*   -> vmContextShow (testVmContextGet (tid))
*/
VM_CONTEXT_ID testVmContextGet 
    ( 
    UINT tid 
    ) 
    { 
    return ((VM_CONTEXT_ID) ((taskTcb (tid))->spare1)); 
    }

7.5.3   Noncacheable Memory

Architectures that do not support bus snooping must disable the memory caching that is used for interprocessor communication (or by DMA devices). If multiple processors are reading from and writing to a memory location, you must guarantee that when the CPU accesses the data, it is using the most recent value. If caching is used in one or more CPUs in the system, there can be a local copy of the data in one of the CPUs' data caches. In the example in Figure 7-3, a system with multiple CPUs share data, and one CPU on the system (CPU 0) caches the shared data. A task on CPU 0 reads the data [1] and then modifies the value [2]; however, the new value may still be in the cache and not flushed to memory when a task on another CPU (CPU 1) accesses it [3]. Thus the value of the data used by the task on CPU 1 is the old value and does not reflect the modifications done by the task on CPU 0; that value is still in CPU 0's data cache [2].

To disable caching on a page basis, use vmStateSet( ); for example:

vmStateSet (pContext, pSData, len, VM_STATE_MASK_CACHEABLE, VM_STATE_CACHEABLE_NOT) 

To allocate noncacheable memory, see the reference entry for cacheDmaMalloc( ).

7.5.4   Nonwritable Memory

Memory can be marked as nonwritable. Sections of memory can be write-protected using vmStateSet( ) to prevent inadvertent access.

One use of this is to restrict modification of a data object to a particular routine. If a data object is global but read-only, tasks can read the object but not modify it. Any task that must modify this object must call the associated routine. Inside the routine, the data is made writable for the duration of the routine, and on exit, the memory is set to VM_STATE_WRITABLE_NOT.

Example 7-2:  Nonwritable Memory

In this code example, to modify the data structure pointed to by pData, a task must call dataModify( ). This routine makes the memory writable, modifies the data, and sets the memory back to nonwritable. If a task tries to read the memory, it is successful; however, if it tries to modify the data outside of dataModify( ), a bus error occurs.


/* privateCode.h - header file to make data writable from routine only */
#define MAX 1024
typedef struct myData { char stuff[MAX]; int moreStuff; } MY_DATA;

/* privateCode.c - uses VM contexts to make data private to a code segment */ 
#include "vxWorks.h" #include "vmLib.h" #include "semLib.h" #include "privateCode.h"
MY_DATA * pData; SEM_ID dataSemId; int pageSize;
/*********************************************************************** * * initData - allocate memory and make it nonwritable * * This routine initializes data and should be called only once. * */
STATUS initData (void) { pageSize = vmPageSizeGet();
/* create semaphore to protect data */
dataSemId = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY);
/* allocate memory = to a page */
pData = (MY_DATA *) valloc (pageSize);
/* initialize data and make it read-only */
bzero (pData, pageSize); if (vmStateSet (NULL, pData, pageSize, VM_STATE_MASK_WRITABLE, VM_STATE_WRITABLE_NOT) == ERROR) { semGive (dataSemId); return (ERROR); }
/* release semaphore */
semGive (dataSemId); return (OK); }
/******************************************************************** * * dataModify - modify data * * To modify data, tasks must call this routine, passing a pointer to * the new data. * To test from the shell use: *     -> initData *     -> sp dataModify *     -> d pData *     -> bfill (pdata, 1024, 'X') */
STATUS dataModify ( MY_DATA * pNewData ) {
/* take semaphore for exclusive access to data */
semTake (dataSemId, WAIT_FOREVER);
/* make memory writable */
if (vmStateSet (NULL, pData, pageSize, VM_STATE_MASK_WRITABLE, VM_STATE_WRITABLE) == ERROR) { semGive (dataSemId); return (ERROR); }
/* update data*/
bcopy (pNewData, pData, sizeof(MY_DATA));
/* make memory not writable */
if (vmStateSet (NULL, pData, pageSize, VM_STATE_MASK_WRITABLE, VM_STATE_WRITABLE_NOT) == ERROR) { semGive (dataSemId); return (ERROR); }
semGive (dataSemId);
return (OK); }

7.5.5   Troubleshooting

If INCLUDE_MMU_FULL_SHOW is included in the project facility VxWorks view, you can use vmContextShow( ) to display a virtual memory context on the standard output device. In the following example, the current virtual memory context is displayed. Virtual addresses between 0x0 and 0x59fff are write protected; 0xff800000 through 0xffbfffff are noncacheable; and 0x2000000 through 0x2005fff are private. All valid entries are listed and marked with a V+. Invalid entries are not listed.

-> vmContextShow 0 
value = 0 = 0x0

The output is sent to the standard output device, and looks like the following:

VIRTUAL ADDR   BLOCK LENGTH   PHYSICAL ADDR   STATE 
0x0            0x5a000        0x0             W- C+ V+ (global) 
0x5a000        0x1f3c000      0x5a000         W+ C+ V+ (global) 
0x1f9c000      0x2000         0x1f9c000       W+ C+ V+ (global) 
0x1f9e000      0x2000         0x1f9e000       W- C+ V+ (global) 
0x1fa0000      0x2000         0x1fa0000       W+ C+ V+ (global) 
0x1fa2000      0x2000         0x1fa2000       W- C+ V+ (global) 
0x1fa4000      0x6000         0x1fa4000       W+ C+ V+ (global) 
0x1faa000      0x2000         0x1faa000       W- C+ V+ (global) 
0x1fac000      0xa000         0x1fac000       W+ C+ V+ (global) 
0x1fb6000      0x2000         0x1fb6000       W- C+ V+ (global) 
0x1fb8000      0x36000        0x1fb8000       W+ C+ V+ (global) 
0x1fee000      0x2000         0x1fee000       W- C+ V+ (global) 
0x1ff0000      0x2000         0x1ff0000       W+ C+ V+ (global) 
0x1ff2000      0x2000         0x1ff2000       W- C+ V+ (global) 
0x1ff4000      0x2000         0x1ff4000       W+ C+ V+ (global) 
0x1ff6000      0x2000         0x1ff6000       W- C+ V+ (global) 
0x1ff8000      0x2000         0x1ff8000       W+ C+ V+ (global) 
0x1ffa000      0x2000         0x1ffa000       W- C+ V+ (global) 
0x1ffc000      0x4000         0x1ffc000       W+ C+ V+ (global) 
0x2000000      0x6000         0x1f96000       W+ C+ V+  
0xff800000     0x400000       0xff800000      W- C- V+ (global) 
0xffe00000     0x20000        0xffe00000      W+ C+ V+ (global) 
0xfff00000     0xf0000        0xfff00000      W+ C- V+ (global) 

7.5.6   Precautions

Memory that is marked as global cannot be remapped using vmMap( ). To add to global virtual memory, use vmGlobalMap( ). For further information on adding global virtual memory, see 7.5.2 Private Virtual Memory.

Performances of MMUs vary across architectures; in fact, some architectures may cause the system to become non-deterministic. For additional information, see the architecture-specific documentation for your hardware.


1:  This routine is not built in to the Tornado shell. To use it from the Tornado shell, you must define INCLUDE_MMU_FULL_SHOW in your VxWorks configuration; see the Tornado User's Guide: Projects. When invoked this routine's output is sent to the standard output device.