[PATCH 4/4] arm: add support for libmm for arm with gumstix bsp

Gedare Bloom gedare at rtems.org
Fri Oct 12 13:40:35 UTC 2012


I did not get a chance to review this code before, so here is my
initial take. Comments in-line.

On Thu, Oct 11, 2012 at 3:10 PM, Gedare Bloom <gedare at rtems.org> wrote:
> From: Hesham AL-Matary <heshamelmatary at gmail.com>
>
> ---
>  c/src/lib/libcpu/arm/Makefile.am                 |   10 +-
>  c/src/lib/libcpu/arm/preinstall.am               |    8 +
>  c/src/lib/libcpu/arm/shared/arm920/mmu_support.c |  365 ++++++++++++++++++++++
>  c/src/lib/libcpu/arm/shared/arm920/mmu_support.h |   95 ++++++
>  4 files changed, 476 insertions(+), 2 deletions(-)
>  create mode 100644 c/src/lib/libcpu/arm/shared/arm920/mmu_support.c
>  create mode 100644 c/src/lib/libcpu/arm/shared/arm920/mmu_support.h
>
> diff --git a/c/src/lib/libcpu/arm/Makefile.am b/c/src/lib/libcpu/arm/Makefile.am
> index e488c45..b6f30ec 100644
> --- a/c/src/lib/libcpu/arm/Makefile.am
> +++ b/c/src/lib/libcpu/arm/Makefile.am
> @@ -15,12 +15,18 @@ include_libcpu_HEADERS =
>  ## shared/include
>  if shared
>
> -include_libcpu_HEADERS += shared/include/mmu.h
> +include_libcpu_HEADERS += shared/include/mmu.h \
> +                                                                                                       ../shared/include/memorymanagement.h \
> +                                                                                                       shared/arm920/mmu_support.h
Fix tabs replace with spaces, here and more below.

>  include_libcpu_HEADERS += shared/include/arm-cp15.h
>
>  ## shared/arm920
>  noinst_PROGRAMS += shared/arm920.rel
> -shared_arm920_rel_SOURCES = shared/arm920/mmu.c
> +shared_arm920_rel_SOURCES = shared/arm920/mmu.c \
> +                                                                                                               shared/arm920/mmu_support.h \
> +                                                                                                               shared/arm920/mmu_support.c \
> +                                                                                                               ../shared/include/memorymanagement.h \
> +                                                                                                               ../shared/src/memorymanagement_manager.c
>  shared_arm920_rel_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/shared/src
>  shared_arm920_rel_LDFLAGS = $(RTEMS_RELLDFLAGS)
>  endif
> diff --git a/c/src/lib/libcpu/arm/preinstall.am b/c/src/lib/libcpu/arm/preinstall.am
> index 751a085..ecc4509 100644
> --- a/c/src/lib/libcpu/arm/preinstall.am
> +++ b/c/src/lib/libcpu/arm/preinstall.am
> @@ -33,6 +33,14 @@ $(PROJECT_INCLUDE)/libcpu/mmu.h: shared/include/mmu.h $(PROJECT_INCLUDE)/libcpu/
>         $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/libcpu/mmu.h
>  PREINSTALL_FILES += $(PROJECT_INCLUDE)/libcpu/mmu.h
>
> +$(PROJECT_INCLUDE)/libcpu/memorymanagement.h: ../shared/include/memorymanagement.h $(PROJECT_INCLUDE)/libcpu/$(dirstamp)
> +       $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/libcpu/memorymanagement.h
> +PREINSTALL_FILES += $(PROJECT_INCLUDE)/libcpu/memorymanagement.h
> +
> +$(PROJECT_INCLUDE)/libcpu/mmu_support.h: shared/arm920/mmu_support.h $(PROJECT_INCLUDE)/libcpu/$(dirstamp)
> +       $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/libcpu/mmu_support.h
> +PREINSTALL_FILES += $(PROJECT_INCLUDE)/libcpu/mmu_support.h
> +
>  $(PROJECT_INCLUDE)/libcpu/arm-cp15.h: shared/include/arm-cp15.h $(PROJECT_INCLUDE)/libcpu/$(dirstamp)
>         $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/libcpu/arm-cp15.h
>  PREINSTALL_FILES += $(PROJECT_INCLUDE)/libcpu/arm-cp15.h
> diff --git a/c/src/lib/libcpu/arm/shared/arm920/mmu_support.c b/c/src/lib/libcpu/arm/shared/arm920/mmu_support.c
> new file mode 100644
> index 0000000..00e9049
> --- /dev/null
> +++ b/c/src/lib/libcpu/arm/shared/arm920/mmu_support.c
> @@ -0,0 +1,365 @@
> +/* COPYRIGHT (c) 2012. Hesham AL-Matary
> + * The license and distribution terms for this file may be
> + * found in the file LICENSE in this distribution or at
> + * http://www.rtems.com/license/LICENSE.
> + */
> +
> +#include <libcpu/arm-cp15.h>
> +#include <rtems/rtems/status.h>
> +#include "mmu_support.h"
> +#include <libcpu/memorymanagement.h>
> +extern uint32_t _ttbl_base;
> +
> +/*
> + * @brief Deleting MPE and freeing its Control block as well as uninstalling
> + * mpe from HW and uninstall the high-level pointer to cpu_mpe
> + */
> +static inline rtems_status_code _CPU_Memory_management_Delete_MPE(rtems_memory_management_entry *mpe)
Why is this "static inline"? Also, it appears to be unused.

> +{
> +  arm_bsp_mm_mpe *arm_mpe;
> +  arm_mpe = mpe->cpu_mpe;
> +  if ( arm_mpe == NULL )
> +    return RTEMS_NO_MEMORY;
> +
> +  free(arm_mpe);
> +
> +  return RTEMS_SUCCESSFUL;
> +}
> +
> +/* Changing Page table attributes to new attributes */
> +static inline rtems_status_code arm_Region_Change_Attr(
> +    arm_bsp_mm_mpe *mpe,
> +    uint32_t AP,
> +    uint32_t CB
> +) {
> +  mmu_lvl1_t     *lvl1_pt;
> +  int             sectionsNumber; /* 1MB sections */
> +  uint32_t        vAddress;
> +  int             PTEIndex;
> +  uint32_t        paddr;
> +  int b,c,i;
> +
> +  sectionsNumber = mpe->pagesNumber;
> +
> +  lvl1_pt = (mmu_lvl1_t *) &_ttbl_base;
> +  PTEIndex = ((mpe->vAddress & 0xfff00000) >> 20);
> +  paddr = (mpe->vAddress & 0xfff00000);
> +
> +  /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned off */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_DES |
> +      MMU_CTRL_I_CACHE_DES |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_DES
> +  );
> +
> +  c = 0;
> +  b = 0;
> +
> +  switch (CB) {
> +    case ARM_MMU_cb:
> +      c = 0;
> +      b = 0;
> +      break;
> +    case ARM_MMU_cB:
> +      c = 0;
> +      b = 1;
> +      break;
> +    case ARM_MMU_WT:
> +      c = 1;
> +      b = 0;
> +      break;
> +    case ARM_MMU_WB:
> +      c = 1;
> +      b = 1;
> +      break;
> +  }
> +
> +  /* Return AP/CB for this region to defaults */
> +  for ( i = 0; i < sectionsNumber; i++) {
> +    uint32_t paddr_base = paddr;
> +    paddr_base = (i<<20) + paddr;
> +    lvl1_pt[PTEIndex++] = MMU_SET_LVL1_SECT(
> +        paddr_base,
> +        AP,
> +        0,
> +        c,
> +        b
> +    );
> +  }
> +
> +  PTEIndex = ((mpe->vAddress & 0xfff00000) >> 20);
> +  uint32_t *PTE_debug = ((uint32_t *) ((mpe->ptAddress) + (mpe->pagesNumber * 4)));
> +  printk(" ~~~ Debug : Entered arm_Region_Change_Attr function succesfully and \n\
> +      changed the first PTE for region starting at base address %x with assigned \n\
> +     pagetable address at address %x is %x ~~~ \n",mpe->vAddress, mpe->ptAddress + (mpe->pagesNumber *4),\
> +     lvl1_pt[PTEIndex]);
> +
Guard this print with #ifdef DEBUG. I think I did this somewhere else already.

> +  mpe->ap = AP; /* Default when installing entry */
> +  mpe->cb = CB; /* Default */
> +
> +    /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned on */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_EN |
> +      MMU_CTRL_I_CACHE_EN |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_EN
> +  );
> +  return RTEMS_SUCCESSFUL;
> +}
> +
> +/* Verify that size must is multiple of page size */
> +inline rtems_status_code _CPU_Memory_management_Verify_size(uint32_t size)
> +{
> +  if ( (size % MMU_SECT_SIZE) != 0)
> +    return RTEMS_INVALID_SIZE;
> +
> +  return RTEMS_SUCCESSFUL;
> +}
> +
> +/* Verify that size is a multiple of page size */
I think this comment is misplaced.

> +inline rtems_status_code _CPU_Memory_management_Initialize(void)
Should this be inline?

> +{
> +  mmu_lvl1_t *lvl1_base;
> +  int i;
> +  uint32_t paddr;
> +
> +  /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /* set manage mode access for all domains */
> +  arm_cp15_set_domain_access_control(0xffffffff);
> +
> +  lvl1_base = (mmu_lvl1_t *) &_ttbl_base;
> +
> +  /* set up the trans table */
> +  //
> +  //mmu_set_map_inval(lvl1_base);
> +  arm_cp15_set_translation_table_base(lvl1_base);
> +
> +  /* fill level 1 pagetable with no protection slots, Cache through attributes
> +   * and 1:1 address mapping */
> +  paddr = 0x00000000;
> +  for (i = 0; i < (0x4000 / 4); i++) {
> +    paddr = i; /* i = 1MB page size */
> +    lvl1_base[i] = MMU_SET_LVL1_SECT(
> +        paddr << 20,
> +        ARM_MMU_AP_NOPR,
> +        0,
> +        1,
> +        0
> +    );
> +  }
> +
> +  /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned on */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_EN |
> +      MMU_CTRL_I_CACHE_EN |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_EN
> +  );
> +  return RTEMS_SUCCESSFUL;
> +}
> +
> +/* @brief Installing @mpe allocates new arm_bsp_mm_mpe for it
> + * and set its value for then allocate a new lvl2 page table
> + * and activate it */
> +rtems_status_code _CPU_Memory_management_Install_MPE(
> +  rtems_memory_management_entry *mpe
> +) {
> +  arm_bsp_mm_mpe *arm_mpe;
> +  mmu_lvl1_t     *lvl1_pt;
> +  int             sectionsNumber; /* 1MB sections */
> +  size_t          size; /* per Byte */
> +  uint32_t        vAddress;
> +  int             PTEIndex;
> +  uint32_t        paddr;
> +  uint32_t       *PTE_debug
> +  int i;
> +  uint32_t        paddr_base;
> +
> +  lvl1_pt = (mmu_lvl1_t *) &_ttbl_base;
> +  PTEIndex = ((((uint32_t)mpe->region.base) & 0xfff00000) >> 20);
> +  paddr = ((((uint32_t)mpe->region.base)) & 0xfff00000);
> +  size = mpe->region.size;
> +
> +  arm_mpe = (arm_bsp_mm_mpe *) malloc(sizeof(arm_bsp_mm_mpe));
> +
> +  if ( arm_mpe == NULL )
> +    return RTEMS_NO_MEMORY;
> +
> +  sectionsNumber = (size / MMU_SECT_SIZE);
> +
> +  /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned off */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_DES |
> +      MMU_CTRL_I_CACHE_DES |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_DES
> +  );
> +
> +  /* Set AP for this region to NO ACCESS */
> +
> +  for ( i = 0; i < sectionsNumber; i++) {
> +    paddr_base = (i<<20) + paddr;
> +
> +    lvl1_pt[PTEIndex++] = MMU_SET_LVL1_SECT(
> +        paddr_base,
> +        ARM_MMU_AP_NO_ACCESS,
> +        0,
> +        1,
> +        0
> +    );
> +  }
> +
> +  arm_mpe->vAddress = mpe->region.base;
> +  /* for level 1 page table ptAddress is the same as ptlvl1Address */
> +  arm_mpe->ptAddress = lvl1_pt;
> +  arm_mpe->ptlvl1Address = lvl1_pt;
> +  arm_mpe->pagesNumber = sectionsNumber;
> +  arm_mpe->type = LVL1_PT; /* Default value now */
> +  arm_mpe->ap   = ARM_MMU_AP_NO_ACCESS; /* Default when installing entry */
> +  arm_mpe->cb   = ARM_MMU_WT; /* Default */
> +  /* TODO: Domain may be defined as read only, write.. and any page may
> +   * be attached to it */
What is this TODO saying we should do?

> +  arm_mpe->domain = 0;
> +
> +  /* install a pointer to high-level API to bsp_mm_mpe */
> +  mpe->cpu_mpe = arm_mpe;
> +
> +  /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned on */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_EN |
> +      MMU_CTRL_I_CACHE_EN |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_EN
> +  );
> +
> +  PTEIndex = (arm_mpe->vAddress & 0xfff00000) >> 20;
> +#ifdef DEBUG
> +  PTE_debug = (uint32_t *)(arm_mpe->ptAddress + arm_mpe->pagesNumber * 4);
> +  printk("Installed PTE at base %x with pagetable address %x\n",
> +      arm_mpe->vAddress, arm_mpe->ptAddress + (arm_mpe->pagesNumber *4),
> +      lvl1_pt[PTEIndex + arm_mpe->pagesNumber - 1]);
> +#endif
> +  return RTEMS_SUCCESSFUL;
> +}
> +
> +/* @brief Unistalling @mpe from level1 page table and return
> + * access/cache attributes to its defaults. Note that bsp MPE
> + * will still exist even after uninstalling mpe
> + */
> +rtems_status_code _CPU_Memory_management_UnInstall_MPE(
> +  rtems_memory_management_entry *mpe
> +) {
> +  arm_bsp_mm_mpe *arm_mpe;
> +  mmu_lvl1_t     *lvl1_pt;
> +  int             sectionsNumber; /* 1MB sections */
> +  size_t          size; /* per Byte */
> +  uint32_t        vAddress;
> +  int             PTEIndex;
> +  uint32_t        paddr;
> +
> +  arm_mpe = (arm_bsp_mm_mpe *) mpe->cpu_mpe;
> +
> +  if ( arm_mpe == NULL )
> +    return RTEMS_UNSATISFIED;
> +
> +  sectionsNumber = arm_mpe->pagesNumber;
> +
> +  lvl1_pt = (mmu_lvl1_t *) &_ttbl_base;
> +  PTEIndex = ((((uint32_t)mpe->region.base) & 0xfff00000) >> 20);
> +  paddr = (((uint32_t)mpe->region.base) & 0xfff00000);
> +
> +  /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned off */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_DES |
> +      MMU_CTRL_I_CACHE_DES |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_DES
> +  );
> +
> +  /* Return ap/CB for this region to defaults */
> +  int i;
> +  for ( i = 0; i < sectionsNumber; i++) {
> +    uint32_t paddr_base = paddr;
> +    paddr_base = (i<<20) + paddr;
> +
> +    lvl1_pt[PTEIndex++] = MMU_SET_LVL1_SECT(
> +        paddr_base,
> +        ARM_MMU_AP_NO_ACCESS,
> +        0,
> +        1,
> +        0
> +    );
> +  }
It seems like this code is mostly copy-pasted, can it be consolidated
into some helper functions?

> +
> +  arm_mpe->ap   = ARM_MMU_AP_NO_ACCESS; /* Default */
> +  arm_mpe->cb   = ARM_MMU_WT; /* Default */
> +
> +    /* flush the cache and TLB */
> +  arm_cp15_cache_invalidate();
> +  arm_cp15_tlb_invalidate();
> +
> +  /*  I & D caches turned on */
> +  arm_cp15_set_control(
> +      MMU_CTRL_DEFAULT |
> +      MMU_CTRL_D_CACHE_EN |
> +      MMU_CTRL_I_CACHE_EN |
> +      MMU_CTRL_ALIGN_FAULT_EN |
> +      MMU_CTRL_LITTLE_ENDIAN |
> +      MMU_CTRL_MMU_EN
> +  );
> +  return RTEMS_SUCCESSFUL;
> +}
> +
> +rtems_status_code _CPU_Memory_management_Set_read_only(
> +    rtems_memory_management_entry *mpe
> +) {
> +  arm_bsp_mm_mpe *arm_mpe = (arm_bsp_mm_mpe *)mpe->cpu_mpe;
> +  return arm_Region_Change_Attr(arm_mpe, ARM_MMU_AP_USER_READ_ONLY, ARM_MMU_WT);
> +}
> +
> +rtems_status_code _CPU_Memory_management_Set_write(
> +    rtems_memory_management_entry *mpe
> +) {
> +  arm_bsp_mm_mpe *arm_mpe = (arm_bsp_mm_mpe *)mpe->cpu_mpe;
> +  return arm_Region_Change_Attr(arm_mpe, ARM_MMU_AP_NOPR, ARM_MMU_WT);
> +}
> diff --git a/c/src/lib/libcpu/arm/shared/arm920/mmu_support.h b/c/src/lib/libcpu/arm/shared/arm920/mmu_support.h
> new file mode 100644
> index 0000000..98744e9
> --- /dev/null
> +++ b/c/src/lib/libcpu/arm/shared/arm920/mmu_support.h
> @@ -0,0 +1,95 @@
> +/*
> +*  COPYRIGHT (c) 2012
> +*  On-Line Applications Research Corporation (OAR).
> +*
> +*  The license and distribution terms for this file may be
> +*  found in the file LICENSE in this distribution or at
> +*  http://www.rtems.com/license/LICENSE.
> +*/
> +
> +#ifndef __MMU_SUPPORT_H_
> +#define __MMU_SUPPORT_H_
> +
> +#include <stdint.h>
> +
> +#define MMU_CTRL_MMU_EN             (1 << 0)
> +#define MMU_CTRL_MMU_DES            (0 << 0)
> +#define MMU_CTRL_ALIGN_FAULT_EN     (1 << 1)
> +#define MMU_CTRL_D_CACHE_EN         (1 << 2)
> +#define MMU_CTRL_D_CACHE_DES        (0 << 2)
> +#define MMU_CTRL_DEFAULT            (0xf << 3)
> +#define MMU_CTRL_LITTLE_ENDIAN      (0 << 7)
> +#define MMU_CTRL_BIG_ENDIAN         (1 << 7)
> +#define MMU_CTRL_SYS_PROT           (1 << 8)
> +#define MMU_CTRL_ROM_PROT           (1 << 9)
> +#define MMU_CTRL_I_CACHE_EN         (1 << 12)
> +#define MMU_CTRL_I_CACHE_DES        (0 << 12)
> +#define MMU_CTRL_LOW_VECT           (0 << 13)
> +#define MMU_CTRL_HIGH_VECT          (1 << 13)
> +
> +#define ARM_MMU_AP_NOPR             0x03
> +#define ARM_MMU_AP_USER_READ_ONLY   0x02
> +#define ARM_MMU_AP_USER_NO_ACCESS   0x01
> +#define ARM_MMU_AP_NO_ACCESS        0x00
> +
> +#define ARM_MMU_cb 0x0 //cache and buffer disabled
> +#define ARM_MMU_cB 0x1 // cache disable and buffer enabled
> +#define ARM_MMU_WT 0x2 // Write through
> +#define ARM_MMU_WB 0x3 // Write Back
> +
> +/* FAULT ENTRY */
> +#define MMU_SET_FAULT (0x0)
> +
> +/* FIXME: ARM supports various page sizes */
> +#define ARM_MMU_PAGE_SIZE  0x1000
> +#define MMU_SECT_SIZE      0x100000
> +
> +/* Set lvl1 PTE to point to the base of a page table mapping
> + * 4K pages
> + */
> +#define MMU_SET_LVL1_DIR_4K_PAGES(lvl2_base, dom) \
> +           ((((lvl2_base) & 0xfffffc00)) |     \
> +           (((dom)&0xf)<<5)                  |     \
> +           0x11)
> +
> +#define MMU_SET_LVL2_PTE_4KB(paddr,ap,c,b) \
> +                           (((paddr)&0xfffff000)   | \
> +                            ((ap)<<10)      | \
> +                            ((ap)<<8)       | \
> +                            ((ap)<<6)       | \
> +                            ((ap)<<4)       | \
> +                            ((c)<<3)        | \
> +                            ((b)<<2)        | \
> +                            0x2)
> +
> +#define MMU_SET_LVL1_SECT(addr, ap, dom, ce, be) \
> +          ((((addr) & 0xfff00000)) |     \
> +           ((ap) << 10)            |     \
> +           ((dom) << 5)            |     \
> +           ((ce) << 3)             |     \
> +           ((be) << 2)             |     \
> +           0x12)
> +
> +/* TODO: Add other page sizes */
> +typedef enum { LVL1_PT, LVL2_PT } pt_type;
> +
> +typedef uint32_t mmu_lvl1_t;
> +typedef uint32_t mmu_lvl2_t;
> +
> +/* This Control Structure applies for most ARM architectures */
> +typedef struct{
> +uint32_t vAddress;
> +/* physical address for page table lvl2 or lvl1 controlling a region */
> +uint32_t ptAddress;
> +/* physical address for  level 1 page table */
> +uint32_t ptlvl1Address;
> +/* Number of used PTEs from this page table */
> +uint16_t pagesNumber;
> +/* lvl2 , lvl1 */
> +pt_type  type;
> +uint8_t  ap;
> +uint8_t  cb;
> +uint8_t domain;
> +} arm_bsp_mm_mpe;
This probably should be arm920_mm_mpe or something, or the definition
should be provided in a shared arm-bsp header file.

> +
> +#endif
> --
> 1.7.1
>



More information about the devel mailing list