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 Method

 

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.

 

 

The Stitching Functions

 

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.

 

 

Putting It All Together

 

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.

 

 

Conclusions

 

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

h.s.fortuna@abertay.ac.uk