Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 7 Jan 2013 21:40:11 -0200
From:      "Dr. Rolf Jansen" <rj@cyclaero.com>
To:        freebsd-drivers@freebsd.org
Subject:   Re: How to map device addresses into user space
Message-ID:  <8BDABFD4-D371-49D4-B21C-EBCD7C86B47C@cyclaero.com>
In-Reply-To: <201301071421.10559.jhb@freebsd.org>
References:  <DFC983B8-91B2-4ED8-89B0-FC4BD4AB2576@cyclaero.com> <201301071011.49234.jhb@freebsd.org> <F62E8A3F-7D16-49F9-9375-5FFE2CBAAF09@cyclaero.com> <201301071421.10559.jhb@freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
Am 07.01.2013 um 17:21 schrieb John Baldwin:

> On Monday, January 07, 2013 12:25:33 PM Dr. Rolf Jansen wrote:
>> Am 07.01.2013 um 13:11 schrieb John Baldwin:
>>> On Monday, January 07, 2013 08:52:01 AM Dr. Rolf Jansen wrote:
>>>> Am 03.01.2013 um 14:45 schrieb Dr. Rolf Jansen:
>>>>> ...
>>>>>=20
>>>>> So, how can I map device addresses into user space on FreeBSD?
>>>>=20
>>>> Many Thanks to everybody, who responded.
>>>>=20
>>>> I got it working. I wrote a PCI device driver, which in addition to
>>>> open/close/read/write responds also to ioctl and mmap requests from =
user
>>>> space. In its pci_attach routine, it assembles a custom =
address_space
>>>> struct containing the addresses of the two BAR's of my NI PCI-DAQ =
board
>>>> and two huge allocated memory regions for DMA...
>>>=20
>>> Note that if your BARs are not contiguous you can allow mapping of =
things
>>> in the middle.  This is a potentital security hole if your driver =
can be
>>> opened by non-root.
>>=20
>> I can understand this. Actually, on the present model of the DAQ =
board, the
>> BARs are contiguous, and in the present incarnation, my driver can =
only be
>> addressed by root. Anyway, I want to do it correctly, that means, I =
would
>> like to avoid right now potential holes bubbling up in the future, =
when
>> using my driver with other DAQ boards, and in different usage =
scenarios.
>=20
> Note that the BARs being contiguous is a property of your BIOS or OS, =
not your=20
> board. :)  The firmware/OS are free to assign whatever ranges to your =
BARs=20
> that they wish.
>=20
>>> A more robust approach is to define a virtual address space for
>>> your device. This is also a lot easier to program against in =
userland.
>>> For example, you could define offset 0 as mapping to BAR 0 and the =
next
>>> chunk
>>=20
>>> of virtual address space (the offset) map to BAR 1, etc.:
>> I did this, but id didn't work out. The 2 BARs are contiguous, and =
the 2
>> DMA regions are also, but there is huge gap between BAR and DMA. And
>> depending on which mapping is done first, either the DMAs or the BARs
>> receive invalid paddr. So, I guess, defining a virtual address space =
is a
>> little bit more involved than simply getting the offsets straight as =
you
>> lined out.
>>=20
>> I am still learning things, so please bear with me, if it is a dumb
>> question. How do I define a virtual address space for the device?
>=20
> Ah, I'll explain a bit.  When you mmap a section of a file, the OS is =
treating=20
> the file as if it were its own section of virtual memory, where files =
contents=20
> were addressed by the offset.  That is, virtual address 0 of the file=20=

> corresponds to the data at file offset 0, and virtual address N =
corresponds to=20
> the data at file offset N.
>=20
> Now, think of your /dev/foo device as a file.  When you mmap() your =
/dev/foo,=20
> the OS has assumes it has a similar virtual memory that is addressed =
by the=20
> offset into the file.  However, for a character device, the OS needs =
the=20
> driver to describe this virtual address space.  That is what you are =
doing=20
> with d_mmap() and the 'offset' parameter.  You are telling it what =
physical=20
> page backs each "virtual page".  It is important to note that when =
using=20
> d_mmap(), these mappings are immutable.  Once you provide a mapping =
for a=20
> given virtual page, the OS will cache it forever (or until reboot).
>=20
> In other words, you can write your d_mmap() to give the appearance =
that your=20
> /dev/foo "file" contains BAR 0 followed by BAR 1, etc.
>=20
>>> static int
>>> mydaq_mmap(...)
>>> {
>>>=20
>>> 	struct mydaq_softc *sc;  // you should be storing things like =
the
>>> 	pointers
>>> =09
>>>                            // to your BARs in a softc
>>> =09
>>> 	sc =3D dev->si_drv1;
>>> 	if (offset <=3D rman_get_size(sc->bar0res))
>>> =09
>>> 		return (rman_get_start(sc->bar0res) + offset);
>>> =09
>>> 	offset -=3D rman_get_size(sc->bar0res);
>>> 	if (offset <=3D rman_get_size(sc->bar1res))
>>> =09
>>> 		return (rman_get_start(sc->bar1res) + offset);
>>> =09
>>> 	offset -=3D rman_get_size(sc->bar1res);
>>> 	if (offset <=3D dmaAIBufSize)
>>> =09
>>> 		return (vtophys(sc->dma[0]) + offset);
>>> =09
>>> 	offset -=3D dmaAIBufSize;
>>> 	if (offset <=3D dmaAOBufSize)
>>> =09
>>> 		return (vtophys(sc->dma[1]) + offset);
>>> =09
>>> 	/* Invalid offset */
>>> 	return (-1);
>>>=20
>>> }
>>=20
>> I guess, you hacked this quickly together, and I picked the concept, =
so
>> this is fine. In order to make this working, the addresses should be
>> assigned to the *paddr parameter, and a status code should be =
returned. In
>> order to check things, of course I applied these minor corrections, =
but it
>> doesn't work for either the BAR block or the DMA block, depending on =
which
>> is mapped first.
>=20
> Yes, it needs the changes you made.  Can you post your updated =
d_mmap()
> routine along with how you are mapping it in userland?  With what I =
posted
> above you would do something like this in userland:
>=20
> const char *bar0, *bar1;
>=20
> int fd =3D open("/dev/foo");
>=20
> bar0 =3D mmap(NULL, <size of BAR 0>, PROT_READ, MAP_SHARED, fd, 0);
> bar1 =3D mmap(NULL, <size of BAR 1>, PROT_READ, MAP_SHARED, fd,
>    <size of BAR 0>);
>=20
> etc.


Yes, now I understood it. I realized, that mydaq_mmap() is not called =
only once
for each mapping, but once for each page in each mapping. And finally I =
got it
working. Besides the corrections already mentioned, the comparison =
operators
needed to be changed from "<=3D" to "<". The working d_mmap() is now:


static int mydaq_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t =
*paddr,
                      int nprot, vm_memattr_t *memattr)
{
   mydaq_pci_softc *sc =3D dev->si_drv1;

   if (offset < rman_get_size(sc->bar0res))
   {
      *paddr =3D rman_get_start(sc->bar0res) + offset;
      return 0;
   }

   offset -=3D rman_get_size(sc->bar0res);
   if (offset < rman_get_size(sc->bar1res))
   {
      *paddr =3D rman_get_start(sc->bar1res) + offset;
      return 0;
   }

   offset -=3D rman_get_size(sc->bar1res);
   if (offset < dmaAIBufferSize)
   {
      *paddr =3D vtophys(sc->dma[0]) + offset;
      return 0;
   }

   offset -=3D dmaAIBufferSize;
   if (offset < dmaAOBufferSize)
   {
      *paddr =3D vtophys(sc->dma[1]) + offset;
      return 0;
   }

   return -1;
}


Thank you very much again, for the big help.

Best regards

Rolf=



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?8BDABFD4-D371-49D4-B21C-EBCD7C86B47C>