From: di...@netcom.com (William R. Dirks)
Subject: vmalloc() and DMA
Date: 1998/07/18
Message-ID: <dirksEw9pCH.Grn@netcom.com>#1/1
X-Deja-AN: 372536286
Sender: di...@netcom8.netcom.com
Organization: Netcom
Newsgroups: comp.os.linux.development.system

Hello. I am new to Linux, and I have a kernel-mode programming question.
I am using RH5.0, with 2.1.106 kernel in place of the 2.0.? kernel.

I am writing a modular kernel-mode driver for a PCI video capture card
with bus mastering capability.
I am having difficulty deciding how to allocate the DMA buffer. The
buffer may need to be quite large for a whole image, 200K would not
be unusual. The card uses a scatter list so the buffer does not have to
be physically contiguous. The only requirement is that all the pages be
mapped to physical RAM, and stay that way, and don't physically move.

kmalloc() is inappropriate because the buffer is too big, and kmalloc() 
allocates physically contiguous pages which is unnecessary for me.
vmalloc() looks like what I want, but I am unable to use the pages of a
vmalloc()ed buffer. Are vmalloc()ed pages fixed in physical RAM, or are 
they subject to being swapped out to disk? If they are swappable, is there
a way to lock them in, like what the PageLock() function does in Windows?

What I would *like* to do (roughly):
buffer = vmalloc(...);
for (i = 0, addr = buffer; ....)
{
	scatterlist[i].addr = virt_to_bus(addr);
	scatterlist[i].len = PAGE_SIZE;
	addr += PAGE_SIZE;
}

The problem is virt_to_bus() does not return a valid physical address.
My system has 32M physical RAM, so addresses go 00000000-01FFFFFF. A call
to vmalloc() will return something like C2812000, and then virt_to_bus()
will return 02812000, which is beyond physical RAM. OTOH, get_free_page()
might return something like C0ABC000, and virt_to_bus() gives 00ABC000,
which is in physical RAM, and I can DMA to that buffer. I tried using
pte_offset() et al. to get the pte's, but they just bring compiler errors.

Can I do what I want? If I can't use vmalloc() for this then it looks like
I will have to allocate pages one by one with get_free_page(), and copy
the data out page by page, etc. But then I am effectively writing my own
software MMU and my own software page table, and that is senseless
duplication because there is a perfectly good hardware MMU that does
exacly what I want, if only I had PageLock() and a function to give me the
pte's! 

I'm ignorant of the in's and out's of Linux. Can anybody advise? 
Thanks!

Bill.

From: a...@muc.de
Subject: Re: vmalloc() and DMA
Date: 1998/07/18
Message-ID: <m3k95b6bjg.fsf@fred.muc.de>#1/1
X-Deja-AN: 372562481
Distribution: world
Sender: an...@fred.muc.de
References: <dirksEw9pCH.Grn@netcom.com>
Organization: [posted via] Leibniz-Rechenzentrum, Muenchen (Germany)
Newsgroups: comp.os.linux.development.system

di...@netcom.com (William R. Dirks) writes:

> kmalloc() is inappropriate because the buffer is too big, and kmalloc() 
> allocates physically contiguous pages which is unnecessary for me.
> vmalloc() looks like what I want, but I am unable to use the pages of a
> vmalloc()ed buffer. Are vmalloc()ed pages fixed in physical RAM, or are 
> they subject to being swapped out to disk? If they are swappable, is there
> a way to lock them in, like what the PageLock() function does in Windows?

vmalloc() pages are not swapped, so no special action is required. 

> Can I do what I want? If I can't use vmalloc() for this then it looks like
> I will have to allocate pages one by one with get_free_page(), and copy
> the data out page by page, etc. But then I am effectively writing my own
> software MMU and my own software page table, and that is senseless
> duplication because there is a perfectly good hardware MMU that does
> exacly what I want, if only I had PageLock() and a function to give me the
> pte's! 

You unfortunately have to do it by hand, because the hardware won't give
you the address.

Just vmalloc() the space and then use something like this:

/* Only pass page aligned addresses */ 
unsigned long vm_virt_to_bus(unsigned long addr)
{
		pgd_t *pgd;
		pmd_t *pmd; 
		pte_t **pte;

		pgd = pgd_offset(current->mm, addr); 
		if (pgd_none(pgd)) 
				return 0; 
		pmd = pmd_offset(pgd, addr); 
		if (pmd_none(pmd))
				return 0;
		pte = pte_offset(pmd, addr);
		if (pte_none(pte))
				return 0;
		return virt_to_bus((void *) pte_page(pte));
}
		

-Andi

From: di...@netcom.com (William R. Dirks)
Subject: Re: vmalloc() and DMA
Date: 1998/07/18
Message-ID: <dirksEwA56I.M43@netcom.com>#1/1
X-Deja-AN: 372591235
Sender: di...@netcom18.netcom.com
References: <dirksEw9pCH.Grn@netcom.com> <m3k95b6bjg.fsf@fred.muc.de>
Organization: Netcom
Newsgroups: comp.os.linux.development.system

In article <m3k95b6...@fred.muc.de>,  <a...@muc.de> wrote:
>di...@netcom.com (William R. Dirks) writes:
>>[how to get bus address of vmalloc()ed memory?]
>
>You unfortunately have to do it by hand, because the hardware won't give
>you the address.
>
>Just vmalloc() the space and then use something like this:
>[subroutine to get bus address]

Thanks, Andi! I got it working. It's just what I wanted!

Andi's code wouldn't compile exactly as he wrote it, so below is
a corrected version in case anyone else needs it. I also changed the
parameter to void * so it's the same as virt_to_bus().

Bill.

/* Only pass page aligned addresses */ 
unsigned long vm_virt_to_bus(void *virt)
{
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;
	unsigned long addr = (unsigned long)virt;

	pgd = pgd_offset(current->mm, addr); 
	if (pgd_none(*pgd)) 
		return 0; 
	pmd = pmd_offset(pgd, addr); 
	if (pmd_none(*pmd))
		return 0;
	pte = pte_offset(pmd, addr);
	if (pte_none(*pte))
		return 0;
	return virt_to_bus((void *)pte_page(*pte));
}