PS2 Linux Programming
DMAC Packet Stitching
Introduction
As discussed in previous tutorials, memory
allocated with the sps2Allocate() function is only physically continuous in 4k
blocks. If the GS (or other type of) packet is larger than 4k, then some method
is needed to stitch the individual sections together in order to perform a
continuous transfer. This tutorial will illustrate one method for building a
large GS Packet (greater than 4 kBytes) and transferring the packet to the GS
using suitable DMA tags embedded within the body of the packet.
The sample code accompanying this tutorial stitches
one large GS packet over as many 4k SPS2 pages that are needed. The technique
is quite simple, but the code can be quite cryptic. The DMA tags that will be
used for the stitching process are the CNT, NEXT, and END tags. The CNT tag
tells the DMAC to transfer the QWC of data immediately following the tag, and
then read the QW after that as the next tag in the chain. The END tag does the
same, except it doesn’t read in a new tag, it simply ends the transfer.
Finally, the NEXT tag tells the DMAC to read QWC of data immediately following
the tag, then find the next tag at the address in its ADDR field. The layout
for a stitched packet might be as follows:
Quad Word Address |
DMA Tag |
DMA Tag QWC |
0 |
CNT |
254 |
255 |
NEXT(Points to address
256) |
0 |
256 |
CNT |
254 |
511 |
NEXT(Points to address
512) |
0 |
512 |
CNT |
86 |
600 |
END |
0 |
As can be seen, the first quad word in a 4K block
is a CNT tag that will transfer the rest of the 4K page, minus the last quad
word. At the end of the block is a next tag that doesn’t transfer any data; it
simply points the DMAC to the start of the next DMA page. This format continues
for all of the filled 4k pages. In the final page there is a CNT tag to
transfer the required amount of data then the packet is ended with an empty END
tag. (As an alternative, it is possible to detect that the final packet doesn’t
require further stitching and switch it’s CNT tag for an END tag which will end
the transfer automatically).
The stitching process can be incorporated into the
packet building function, hiding it away as illustrated in the example code.
(In fact, it is possible to abstract the stitching function/class away entirely
so that the programmer does not have to be concerned with the stitching process
at all, but this is beyond the scope of this tutorial). In the case of the
example code, the big advantage is that the number of primitives to be drawn
can be increased without having to worry about creating packet larger than 4k.
One word of warning though, remember to allocate enough memory to hold the
entire packer – a judiciously placed assert(), as included in the example code,
can help prevent undesirable events.
Three functions, contained within stitch.c, control
the stitching process. InitStitch() initialises stitching and is called at the
start of the process. Stitch() performs the stitching and is called before a
quad word of data is written to the DMAC chain. EndStitch() is called at the
end of the process. Two variable global to the stitch.c file are also used,
iQWC, which records the number of qwords written per 4k block, and pDMATag
which is a pointer to a DMATag of type sps2DMATag_t.
Before any data is written to the DMA chain
InitStitch() is called:
void InitStitch(void * pMem)
{
iQWC = 0;
pDMATag =
(sps2DMATag_t *)pMem;
pDMATag->i64
= 0;
pDMATag->s.ID
= DMA_ID_SOURCE_CNT;
}
This function initialises iQWC to zero then puts
the first DMA tag in the chain at the start of the allocated memory. The first
tag is a cnt tag. The qword count of this tag is set later once the amount of
data to be sent with this tag is known.
Next comes the stitch() function.
int Stitch(void * pMem)
{
if((((int32)pMem)
& 0xFFF) == 0xFF0)
{
pDMATag->s.QWC
= iQWC;
iQWC =
1;
pDMATag
= (sps2DMATag_t *)pMem;
pDMATag->i64
= 0;
pDMATag->s.QWC
= 0;
pDMATag->s.ID
= DMA_ID_SOURCE_NEXT;
pDMATag->s.ADDR
= sps2GetPhysicalAddress((void *)(((int32)pMem) + 0x10), pMemory);
++pDMATag;
pDMATag->i64
= 0;
pDMATag->s.QWC
= 0;
pDMATag->s.ID
= DMA_ID_SOURCE_CNT;
return
2;
}
iQWC++;
return 0;
}
The stitch() function should be called just before
1 quad word of data is written to a DMA chain. It is important that this
function is call EVERY time before a quad word is written to the DMA chain, (no
more or no less or stitching won’t work).
The variable iQWC counts how many times the
function is called and therefore keeps a count of how many qwords of data have
been written to the page, (hence why the function should be called before every
QWORD is written). The variable pDMATag is used to write the first CNT tag
within a block. PDMATag then remains untouched until the end of the block is
reached and it is then used again (if required) to perform the stitching.
The first line of the stitch function checks to see
if the address passed to this function is the last QWORD of a page. If so, the
function sets the QWC of the previously written CNT tag. Then it resets QWC to
1 (remember the function is called before the data is written, so it is
expected that 1 qword of data is to be written immediately after this
function). Now that the CNT tag written at the start of the block is complete
the pointer to it is no longer needed. The pDMATag pointer is now set to the address
passed into the function which is the last QWORD of this block, and a NEXT tag
is written that will point the DMAC to the start of the next 4k block in physical
address space. Note that the QWC of the NEXT tag is set to zero so that no data
will be sent and the ADDR field is set with the physical address of the start
of the next block of memory. The pointer is then moved on by one so that it
points to the first qword of the next page. The first CNT tag of the next page
is then written, notice that the QWC field of the tag is set to zero at this
stage since its value is not known. At this point the function returns 2, this
value being used to move a pointer on (in the function that called stitch() ) to
account for the two DMAC Tags that have been written by the function.
Now, if the stitch() function is called and the memory
pointer passed to it is within the body of the block (i.e. not the last qword) the
function simply increments the qword count variable and returns 0 to indicate
that no tags have been written.
Once all the data has been written to the packet
the EndStitch() function is called.
void EndStitch(void * pMem)
{
pDMATag->s.QWC
= iQWC;
pDMATag =
(sps2DMATag_t *)pMem;
pDMATag->i64
= 0;
pDMATag->s.QWC
= 0;
pDMATag->s.ID
= DMA_ID_SOURCE_END;
}
Firstly, this function sets the QWC of the last
DMATag that was added. If no stitching has been done, this will be the first
cnt tag of the first and only block, otherwise it will be the most recently
added cnt tag. Once this has been done, an empty END tag with QWC = 0 is
created to stop the DMAC.
To reiterate a point made above, note that the stitch()
function returns the number of tags it has written. Therefore if no stitching
has been done it returns 0, and if it did stitch it returns 2. If stitching is
being done with a pointer that points to 128 bit data (e.g. a GIFTag) then it
is simply a case of adding on the amount returned by Stitch() to the pointer. If
stitching with a pointer that points to a different size of data, then something
manually will need to be done.
Most of the main program is similar to what has
been presented in previous tutorials and the details will not be discussed
again. In the current program, the GS Packet is not changed as the program
executes so it is built only once with the LoadPacketData() function. This
function, which is contained within packet.c, takes as a parameter the start
address of the memory where the packet is to be written.
Looking at LoadPacketData() it can be seen that InitStitch()
is called to write the first DMATag, then before each qword of data is written
to the GS Packet, the Stitch() function is called. The main loop of the
LoadPacketData() function loops through every triangle that is to be drawn and writes
the positions and colours for each vertex. An assert() is made each time
through the loop to check that the total amount of data written is less then
the memory allocated. Finally, EndStitch() is called at the end of the function
to complete the stitching process.
The techniques introduced in this tutorial are hopefully
presented in a manner that will aid the understanding of the stitching process.
Many PS2 Linux developers wrap the DMA packet stitching into a C++ class to
abstract the stitching process. For those of you familiar with such techniques
a useful exercise might be to write such a packet class that can perform
stitching automatically. With a good packet class, packet stitching can be
totally hidden.
An alternative method to the one presented in this
tutorial is to use a collection of DMAC ref tags to reference a large data
packet which can be split over many 4k Blocks. This method will be illustrated
in a later tutorial when uploading textures to GSmem in discussed.
Dr Henry S Fortuna
University of Abertay
Dundee