Node:Xfer,
Next:Move structs,
Previous:Zero SP,
Up:Low-level
Q: How can I move data between my program and the transfer buffer?
Q: How do I access my peripheral card which is memory-mapped to an
address between 640K and 1M?
Q: How can I read or change a value of one of the variables in the
BIOS data area?
Q: How can I peek at an address whose far pointer I get from an
INT 21h call?
A: Usually, memory-mapped devices or absolute addresses below 1MB mark are outside your program's address space, so you cannot access them directly. "Direct access", when you just dereference a pointer, means in DJGPP that you use your program's DS selector, and all the addresses are offsets relative to the base of that selector. So first, you will need a special selector that will allow you to access your device or absolute address. There are several methods you can get such a selector:
_dos_ds
macro (defined on the go32.h
header file). This
selector has base address of 0 and a limit of 1MB+64KB, so you can use
it to access any address in the conventional memory, including the UMBs,
but the relatively large limit allows a buggy program to overwrite
portions of DOS memory32. The advantage of _dos_ds
is obviously
that you don't have to create it, and that it is good for accessing
every region in the first MByte range.
_dos_ds
. For
example, here's a code snippet to set up a selector which provides
access to 32KB of text-mode video memory at 0xB800:0000
, courtesy
of Bill Currie33:
int TxtVRAMSetupSelector (void) { static char selectorData[8] = { 0xff, 0x7f, 0x00, 0x80, 0x0b, 0xf3, 0x40, 0x00 }; int screenSelector = __dpmi_allocate_ldt_descriptors (1); if (__dpmi_set_descriptor (screenSelector, selectorData) < 0) abort (); return screenSelector; }
Calling __dpmi_allocate_dos_memory
creates a protected-mode
selector that spans the allocated block. You can use that selector to
access the allocated memory.
The advantages of using a special selector are that (a) you can set up the selector limit such that it only covers the memory region that you need, thus protection of the rest of memory is retained; and (b) you may set the base address to point to the beginning of the specific memory region you need to access, so that you don't have to add the base address for every access, making the access faster.
__dpmi_segment_to_descriptor
which is a wrapper around that DPMI
service. It is easier to use than the __dpmi_set_descriptor
function above, since you don't have to mess with the 8-byte descriptor
buffer, but it always defines a 64KB limit by default. Here is an
example of code which gets a selector to access 64KB of video RAM
beginning at 0xA000:0000
:
short video = __dpmi_segment_to_descriptor(0xa000);
Note that descriptors created by this function should never be modified
or freed. For this reason, you should use this function sparingly. For
instance, if your program needs to examine various real mode addresses
using the same selector, you should allocate a descriptor and change the
base using the __dpmi_set_segment_base_address
library function
instead of using __dpmi_segment_to_descriptor
to allocate
separate descriptor for each address.
Once you have a selector, you can use one of three methods to access your absolute addresses using that selector:
<sys/farptr.h>
header file. You should convert any real-mode far pointer
segment:offset pair into a linear address (i.e., segment*16 +
offset), and use _dos_ds
or any other selector which allows
access to conventional memory, like this:
unsigned char value = _farpeekb(_dos_ds, segment * 16 + offset);
To access DOS memory allocated by __dpmi_allocate_dos_memory
, use
the selector returned by that function; a zero offset designates the
beginning of the allocated block.
For access to memory-mapped devices for which you have allocated a
dedicated descriptor, use the selector of that descriptor instead of
_dos_ds
in the above example, and use the offset into the on-board
device memory as the offset. For example, the following snippet
writes a value of 3 to the 10th dword of the device:
long lword = 3; _farpokel (selector, 9, lword);
Use _farpeekw
to peek at 16-bit shorts and _farpeekl
to
peek at 32-bit longs. If you need to access several (non-contiguous)
values in a loop, use the corresponding _farnspeekX
functions which
allow you to set the selector only once, as opposed to passing it with
every call (but be sure the loop doesn't call any function that itself
sets the selector; see the library reference for more details).
There is a corresponding set of _farpokeX
and _farnspokeX
functions to poke (change the values of) such memory locations.
These functions have an advantage of emitting inline assembly code when you compile with optimizations, so they are very fast. See the library reference Info file for further details about these functions.
dosmemget
and dosmemput
library functions. They also require you to
convert the segment:offset pair into a linear address, but they don't need
the conventional memory selector, as they can only be used to access the
conventional memory (they use _dos_ds
internally).
Note that some memory-mapped peripheral devices might require 16-bit word
accesses to work properly, so if dosmemXXX
yields garbled
results, try dosmemXXXw
or set up a loop which calls "farptr"
functions.
_dos_ds
(e.g.,
selectors created by one of the methods explained above), use the
movedata
library function. It requires that you pass a selector
and an offset for both the memory-mapped address and for the buffer in
your program's address space. Use the _my_ds()
function (note
that it's a function, not a variable!) to get the selector of
any variable in your program, and use the address of the variable (cast
to an int
) as its "offset" or linear address. movedata
is fast because it moves by 32-bit longs, but be careful with its use
when moving data to and from peripheral cards: some of them only support
8- or 16-bit wide data path, so moving data 4 bytes at a time won't gain
you much, and might even get you in trouble with some buggy BIOSes. The
functions movedatab
and movedataw
are provided for moving
by bytes and by 16-bit words, respectively.
For example, here is a code snippet that combines one of the methods for
allocating a descriptor for video RAM access with a call to
movedata
to move a buffer to the graphics screen:
short video = __dpmi_segment_to_descriptor(0xa000); movedata(_my_ds(), buffer, video, 0, 320*200);
sys/nearptr.h
header file; see the library
reference for more details. Also see description of how to get the fastest direct access to peripheral devices, below.