Re: DMA to user space

From: Richard B. Johnson (root@chaos.analogic.com)
Date: Thu Mar 16 2000 - 12:12:11 EST


On Thu, 16 Mar 2000, John Wilson wrote:

> On Thu, Mar 16, 2000 at 08:22:38AM -0500, Richard B. Johnson wrote:
> > No. Not a "user-buffer". It may be possible to memory-map something that's
> > DMA..able to user space, but even that's not guaranteed. To have something
> > DMA..able in the ix86 machines, it needs to be:
> >
> > (1) Physical, below 16 Megabytes
> > (2) DATA alignment must be such that a 64k block is not crossed
> > during a DMA transfer. The ix86 machines use a page register
> > to access 64k pages.
> > (3) You need access to the DMA controller which the kernel isn't
> > going to give you, without a fight iopl(3), etc., from user-
> > space.
>
> No no, these requirements are for ISA DMA, not DMA in general. I'm using
> a PCI device, so there are no address restrictions (on x86 anyway, I dunno
> how PCI works on CPUs with >32 addr bits), and it has its own DMA paging
> registers so it doesn't require a contiguous buffer either. The buffer
> just has to be locked in place, which sounds like mlock() would be my friend.
>
> Also, my driver is a real driver, it doesn't run in user space. I just want
> it to be able to set the device up so that the DMA buffer (*only*) is in
> user space, and let 'er rip.
>

If your device is doing it's own DMA, basically you are telling it to
write its stuff to some physical address that you give it. If this
is correct, then your driver needs to lock user pages in place if
you are not going to use kernel memory.

The easiest way I can see is to reference a physical page from
user space. Since this page doesn't go away (it's physical), your device
wouldn't have any problem writing to it.

For instance, this program maps physical address c0000 from user-space
and reads from it. If your boards were writing to this area, you would
see what they had written.

You can write too. In other words, this is exactly what you would
do under DOS DEBUG if you were referencing C000:0000. That said, if
you expect to be able to tell your board to write to a DOS program's
buffer at 2000:1234, (remember this is physical memory), you
need to do exactly the same thing from 32-bit world except that
you don't use the segment/offset. So you would mmap 201234 or any
other physical address you could find.

You do not allocate memory from user-space then find its physical
address because, even if locked, the kernel is free to change its
physical location (can happen if you add new pages). Also note
that the physical/logical translation is per page. What looks
like a continuous xMb buffer in user-space could certainly be
a bunch of 0x1000 - byte pages scattered all around physical memory,
even if locked into place.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>

#define DEBUG

#if !defined(PAGE_SIZE)
#define PAGE_SIZE 0x1000
#endif
#if !defined(PAGE_MASK)
#define PAGE_MASK ~(PAGE_SIZE - 1)
#endif
#if !defined(MAP_FAILED)
#define MAP_FAILED (void *) -1
#endif
#define UL unsigned long

#define ERRORS \
    { fprintf(stderr, errmsg, __LINE__,\
      __FILE__, errno, strerror(errno));\
      exit(EXIT_FAILURE); }

#ifdef DEBUG
#define DEB(f) (f)
#else
#define DEB(f)
#endif

static const char errmsg[]="Error at line %d, file %s (%d) [%s]\n";
static const char mapfile[]="/dev/mem";
#define PROT (PROT_READ|PROT_WRITE)
#define FLAGS (MAP_FIXED|MAP_SHARED)
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*
 * This writes a 'debug-like' dump out the screen. It always writes
 * 16 lines of 16 characters. It returns the last address displayed.
 */
static off_t dump(off_t addr);
int main(void);

static off_t dump(off_t addr)
{
    size_t i, j;
    int fd;
    caddr_t mem;
    off_t next;
    unsigned char c, *buf;

    next = addr;
    addr &= PAGE_MASK;
    if((fd = open(mapfile, O_RDWR)) < 0)
        ERRORS;

    if((mem = mmap((caddr_t)addr, PAGE_SIZE, PROT, FLAGS, fd, addr)) == MAP_FAILED)
    {
        (void)close(fd);
        fprintf(stderr, "\tCan't map address %08lX\n", (UL) addr);
        return addr;
    }
    buf = (unsigned char *) next;
    DEB(fprintf(stderr, "Mapped address = 0x%lx\n", addr));
    DEB(fprintf(stderr, "Buffer address = %p\n", buf));

    for(i=0; i< 0x10; i++)
    {
        fprintf(stdout, "\n%08lX ", (UL) next);
        next += 0x10;
        for(j=0; j< 0x10; j++)
            fprintf(stdout, "%02X ", (unsigned int) *buf++);
        buf -= 0x10;
        for(j=0; j<0x10; j++)
        {
            c = *buf++;
            if((c < (unsigned char) ' ') || (c > (unsigned char) 'z'))
                c = (unsigned char) '.';
            fprintf(stdout, "%c", c);
        }
    }
    fprintf(stdout, "\n");
    (void)fflush(stdout);
    (void)close(fd);
    if(munmap(mem, PAGE_SIZE)< 0)
        ERRORS;
    return next;
}

int main()
{
   
    off_t addr = 0xc0000;
    for(;;)
        addr = dump(addr);
    return 0;
}

Cheers,
Dick Johnson

Penguin : Linux version 2.3.41 on an i686 machine (800.63 BogoMips).

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Thu Mar 23 2000 - 21:00:20 EST