This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

OMAP L-137 mmap DSP Memory in Linux

Other Parts Discussed in Thread: OMAP-L138

Hello,
I'm want to map the DSP memory regions to Linux userspace, using the OMAP L-137 EVM. The main reason for this is to enable high speed communication between the ARM and DSP applications by means of shared memory and interrupts. I'm not able to use DSP Link since this introduces a variable delay in the DSP application.

I'm implementing my driver in the form of a Kernel module. This module exports a number of character devices which represent the DSP memory regions. I've attached mmap file operations to these devices so the memory can be mapped in userspace by using mmap(). When mmap is performed on the device it performs an remap_pfn_range on the physical address of the memory region.

For some reason this strategy is not working out. The memory is mapped without error but when I read mmap'ed memory in userspace it contains all zeroes. When I attach Code Composer Studio to the DSP and read the same regiser (for example L1DRAM @ 0x11F0 0000) it shows random data.
- Does anybody know why this is happening?

I've also noticed that when I perform an ioremap action on the same physical address  (for example L1DRAM @ 0x11F0 0000) I'm able to print the data. This data corresponds to the data read from Code Composer Studio. So it seems I'm able to access the DSP memory using ioremap, but I'm not sure how I can provide this map to userspace.
- Is there a way to convert and supply the mapped memory using ioremap to userspace (via the mmap call for example)? Which functions can be used for that operation?

Another thing I noticed is that when I connect Code Composer to debug both the ARM and DSP after Linux is booted, I'm only able to read the L1DRAM from the DSP debug connection. When I enter the address in the ARM memory view it displays all zeroes.
- Can anybody tell me what is responsible for this behaviour?

I've also added parts of my source code to make it all a little bit more understandable. I've only added the parts that apply to L1DRAM but I'm having the same problem with;
- L2ROM, L2RAM and L1PRAM
I'm able to map the SDRAM and Shared Ram using /dev/mem but I want to add those devices to my own driver as well.

-- dspcomk.c : kernel module --
#define L1DRAM_ADDR       0x11F00000 
#define L1DRAM_SIZE       0x8000      //32K

static int
dspcom_mmap(struct file * filp, struct vm_area_struct * vma)
{
  int retval;

  unsigned long physical  = 0;
  unsigned long psize     = 0;
  unsigned long vsize     = vma->vm_end - vma->vm_start;

  switch( iminor( filp->f_dentry->d_inode ) )
  {
    case DSPCOM_MINOR_L1DRAM:
      physical  = L1DRAM_ADDR;
      psize     = L1DRAM_SIZE;
      break;
    default:
      return -ENOMEM;
  }

  printk( " DSPCom: physical addr:  0x%lX\n", physical );
  printk( " DSPCom: vsize:          0x%lX\n", vsize );
  printk( " DSPCom: psize:          0x%lX\n", psize );
  printk( " DSPCom: vm start addr:  0x%lX\n", vma->vm_start );
  printk( " DSPCom: vm end addr:    0x%lX\n", vma->vm_end );
  printk( " DSPCom: vm offset:      0x%lX\n", vma->vm_pgoff << PAGE_SHIFT );

  if( vsize > psize )
  {
    printk( "DSPCom: vsize[%ld] > psize[%ld]\n", vsize, psize );
    return -ENOMEM;
  }

  retval = remap_pfn_range( vma, vma->vm_start,physical , vsize, vma->vm_page_prot );
  if( 0 != retval )
  {
    printk( "DSPCom: unable to remap_pfn_range: %d\n", retval );
  }
  return 0;
}

struct file_operations dspcom_sharedmem_fops =
{
  .owner = THIS_MODULE,
  .mmap  = dspcom_mmap,
};

static int __init
dspcom_init_module ( void )
{
  dev_l1dram   = cdev_alloc();
  ...
  dev_l1dram->ops = &dspcom_sharedmem_fops;
  dev_l1dram->dev = MKDEV( DSPCOM_MAJOR, DSPCOM_MINOR_L1DRAM );
  retval = cdev_add( dev_l1dram, MKDEV( DSPCOM_MAJOR, DSPCOM_MINOR_L1DRAM ), 1 );
  ...
}

static void __exit
dspcom_cleanup_module ( void )
{
  cdev_del( dev_l1dram );
}

-- Test App --
fd = open( "/dev/l1dram", O_RDWR );
if( -1 == fd) error("bad file");
buf = mmap( 0, 256*1024, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0 );
if( -1 == buf ) error("bad map" );
printf( "[0]:0x%X\n[1]:0x%X\n[2]:0x%X\n[3]:0x%X\n",
      static_cast<unsigned int*>(buf)[0], static_cast<unsigned int*>(buf)[1],
      static_cast<unsigned int*>(buf)[2], static_cast<unsigned int*>(buf)[3] );
retval = close( fd );
if( -1 == retval ) error( "bad close");

-- ioremap in init --
volatile unsigned int* L2RAM; // declared globaly
L2RAM       = ioremap( L2RAM_ADDR, 256*1024 );
printk( " DSPCom: L2RAM addr:       0x%X, data[0]: 0x%X\n", L2RAM, (unsigned int)(L2RAM)[0] );

  • With a modification to the driver I'm able to map the "Shared Memory" between the ARM and the DSP at 0x8000 0000. I'm able to write to and read from the memory in userspace as well. I'm still not able to map for example the L2RAM with this modification. Can anyone tell me what I'm doing wrong?

    I've added the altered code below, the bold part contains the change;

    #define SHAREDRAM_ADDR    0x80000000
    #define SHAREDRAM_SIZE    0x20000     // 128K

    struct cdev *dev_sharedram;

    // mmap memory
    static int
    dspcom_mmap(struct file * filp, struct vm_area_struct * vma)
    {
      int retval;

      unsigned long physical  = 0;
      unsigned long psize     = 0;
      size_t        vsize     = vma->vm_end - vma->vm_start;

      switch( iminor( filp->f_dentry->d_inode ) )
      {
        case DSPCOM_MINOR_SHAREDRAM:
          physical  = SHAREDRAM_ADDR;
          psize     = SHAREDRAM_SIZE;
          break;
        default:
          return -ENOMEM;
      }

      if( vsize > psize )
      {
        printk( "DSPCom: vsize[%ld] > psize[%ld]\n", vsize, psize );
        return -ENOMEM;
      }

      retval = remap_pfn_range( vma, vma->vm_start,physical >> PAGE_SHIFT, vsize, vma->vm_page_prot );
      if( 0 != retval )
      {
        printk( "DSPCom: unable to remap_pfn_range: %d\n", retval );
      }
      return 0;
    }

    struct file_operations dspcom_sharedmem_fops =
    {
      .owner = THIS_MODULE,
      .mmap  = dspcom_mmap,
    };

    static int __init
    dspcom_init_module ( void )
    {
      int retval = 0;

      dev_sharedram = cdev_alloc();

      // SHAREDRAM Memory
      dev_sharedram->ops = &dspcom_sharedmem_fops;
      dev_sharedram->dev = MKDEV( DSPCOM_MAJOR, DSPCOM_MINOR_SHAREDRAM );
      retval = cdev_add( dev_sharedram, MKDEV( DSPCOM_MAJOR, DSPCOM_MINOR_SHAREDRAM ), 1 );
      if( 0 != retval )
      {
        printk( "DSPCom: cdev_add sharedram error: %d\n", retval );
        retval = -EAGAIN;
        goto sharedram;
      }
      return 0;
    }

  • I've managed to fix the problem by using mapping the L2RAM, for people who struggle with it in the future! Please find my code below:

    static int
    dspcom_mmap(struct file * filp, struct vm_area_struct * vma)
    {
      int retval;

      unsigned long physical  = 0;
      unsigned long psize     = 0;
      unsigned int  mode_io   = 0;
      size_t        vsize     = vma->vm_end - vma->vm_start;

      printk( "+ DSPCom mmap\n" );
      printk( " DSPCom: mmap  minor: %d\n", iminor( filp->f_dentry->d_inode ) );

      switch( iminor( filp->f_dentry->d_inode ) )
      {
        case DSPCOM_MINOR_L2ROM:
          physical  = L2ROM_ADDR;
          psize     = L2ROM_SIZE;
          mode_io   = 1;
          break;
        case DSPCOM_MINOR_L2RAM:
          physical  = L2RAM_ADDR;
          psize     = L2RAM_SIZE;
          mode_io   = 1;
          break;
        case DSPCOM_MINOR_L1PRAM:
          physical  = L1PRAM_ADDR;
          psize     = L1PRAM_SIZE;
          mode_io   = 1;
          break;
        case DSPCOM_MINOR_L1DRAM:
          physical  = L1DRAM_ADDR;
          psize     = L1DRAM_SIZE;
          mode_io   = 1;
          break;
        case DSPCOM_MINOR_SHAREDRAM:
          physical  = SHAREDRAM_ADDR;
          psize     = SHAREDRAM_SIZE;
          break;
        case DSPCOM_MINOR_SDRAM:
          physical  = SDRAM_ADDR;
          psize     = SDRAM_SIZE;
          break;
        default:
          return -ENOMEM;
      }

      if( vsize > psize )
      {
        printk( "DSPCom: vsize[%d] > psize[%ld]\n", vsize, psize );
        return -ENOMEM;
      }

      // Make sure the memory is not cached
      vma->vm_page_prot = pgprot_noncached( vma->vm_page_prot );
      vma->vm_page_prot = pgprot_writecombine( vma->vm_page_prot );

      if( 0 == mode_io )
      {
        printk( " DSPCom: mapping non-io\n" );
        retval = remap_pfn_range( vma, vma->vm_start,physical >> PAGE_SHIFT, vsize, vma->vm_page_prot );
      }
      else
      {
        printk( " DSPCom: mapping io\n" );
        vma->vm_flags |= VM_IO;

        retval = io_remap_pfn_range( vma, vma->vm_start,physical >> PAGE_SHIFT, vsize, vma->vm_page_prot );
      }
      if( 0 != retval )
      {
        printk( "DSPCom: unable to remap_pfn_range: %d\n", retval );
      }

      printk( "- DSPCom mmap\n" );
      return 0;
    }

  • Thanks for the code posting.  I'm writing my own driver as well to replace DSPLINK and only needed access to 0x80000000 for what I'm doing so my code was fine.  Where I am seeing a problem and don't know if you have yet is in the interrupt handler for the ARM/DSP communication registers.  For each time the DSP sets the CHIPSIG INT0/1 bits the handler on the Linux side gets called 3 times.  The first is the real call where the bits are cleared, CHIPSIG_CLR, the other two are from the low level handler.  /proc/interrupts only shows a single interrupt.  If I cheat and write the interrupt number to AINTC SICR and HIPIR2 I can get it down to 2 calls, the real one and a phantom one.  Can't really do this though as opens potential for race condition when the lower level Linux code acknowledges system interrupt again.

    Only way around it thus far is to simply check the CHIPSIG register in the interrupt handler and if bit not set return IRQ_HANDLED, ignoring interrupt.  Note SA_INTERRUPT is set on request_irq call.  Works but wasted CPU cycles.  In the extra calls to the interrupt handler CHIPSIG INT0/1 bits are not set, were cleared by the handler during the first real interrupt and remain cleared until I use JTAG and poke the DSP register again.  Interrupts are set for edge trigger as well.  Anyways I'll look into it more when get some time but curious if you've seen anything.

    Kev

     

  • I ran into exact the same problem! I had no clue where to look for the source of the problem. I first thought it was because I didn't set the interrupt type so it would trigger on rising edge, level and falling edge. Setting the interrupt type to rising did not solve the problem.

    Have you already thought about how you want to program code into your DSP and start/stop it? I was thinking of using the DSP Link for this because it looks like a pretty advanced sequence. Down part of the DSP LInk driver is the overhead and its complexity.

    Luke

  • Well at least I know now it wasn't just me ;-).  I figured it was set to level interrupts and I was getting recursion but verified no recursion & edge set, it is the Linux handler that is re-invoking us after each return.  The forced clearing of SICR/HIPIR2 reducing it by one is kind of strange, especially given /proc/interrupts only shows a single interrupt occured.  When I get a chance I'll look into doing a special kernel build with some debug information but my guess is there is a race condition in the kernel interrupt handler...  Both system interrupt 28 and 29 act identical.  Eventually I'll be moving to the OMAP-L138 and will worry about it then if still occurs.  Support for the L137 is not the best and figure TI focus will be more L138 with open source.  Besides I really need the extended address space on the L138 so I can get rid of using latches for upper memory access on EMIFA.  L138 should be available end of August/beginning of September, although not sure state of Linux at that point, although port should be easy from L137.

    With regards to start/stop my world is fairly easy as I only have to start, DSP is more of the master and Linux is just providing communications and file access.  I modified the ARM ubl spi code to not reset the DSP when uboot is loaded thus DSP stays running and I can use JTAG or spin waiting for some event to occur (like Linux to boot).  Figure when I get to the real code I'll just have Linux load the file image to some known location for the DSP and then tell the initial DSP ubl code where to branch too once ready to run.  Much simpler.  Need both messaging and DSP/Linux to share common memory variable references so DSP not interrupted all the time and don't have to copy data.

    Kev

     

  • Yeah I also removed the part from the ARM UBL that put the DSP in reset. I use Code Composer to load the DSP application during development. I have to be able to update the DSP image from within Linux, I also need to suspend the DSP when the device is in standby to save power.

    The coff parsing involved with programming the DSP application was what looked difficult to me. Maybe it's possible to write a binary directly into the L1PRAM. I assume that start, stop and reset is just writing a sequence to some registers. I still have to get more information on that.