gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Multiboot not working on real hardware

#152704 - Maximus32 - Wed Mar 19, 2008 10:10 pm

Hello,

Does anyone know if there is documentation on the MultiBootParam structure? GBATEK has some information but not much. And I can't find any working examples.

My problem is the following:
I have multiboot working in No$gba (up to three slaves), but when I try to run it on real hardware it fails...
The other gba already shows the orange nintendo logo so it seems to work up to a certain point. All values returned by the other gba are as reported on GBATEK (I check each and every one). But when calling the MultiBoot bios function it returns immediately with return value 1.

I think it must have something to do with the parameters in the MultiBootParam structure, but I can't find any documentation or a working example. The values I currently initialize are:

memset(&mbp, 0, sizeof(MultiBootParam));
mbp.handshake_data
mbp.probe_count
mbp.client_data[0]
mbp.client_data[1]
mbp.client_data[2]
mbp.palette_data
mbp.client_bit
mbp.boot_srcp
mbp.boot_endp

If someone has some example source or any documentation it would be really nice.

#152724 - eKid - Thu Mar 20, 2008 2:19 am

I've gotten it to work a couple times. If you post your code I can see what's wrong.

#152738 - Maximus32 - Thu Mar 20, 2008 8:49 am

The code is part of a big project, so I'll only post the multiboot function (big also :-)). Here it is:

Code:

int
CGBASerial::multiBoot()
{
  if(eMode_ != SIO_MULTI_MODE)
  {
    if(setMode(SIO_MULTI_MODE) == -1)
    {
      printk("unable to set multi mode\n");
      return -1;
    }
  }

  uint16_t slave0, slave1, slave2, slave3;
  uint8_t  client_bit(0);

  // Detect slaves
  for(int i(0); i < 15; i++)
  {
    sendMulti(0x6200, &slave0, &slave1, &slave2, &slave3);
    if((slave1 & 0xfff0) == 0x7200)
      client_bit |= (slave1 & 0x000f);
    if((slave2 & 0xfff0) == 0x7200)
      client_bit |= (slave2 & 0x000f);
    if((slave3 & 0xfff0) == 0x7200)
      client_bit |= (slave3 & 0x000f);

    if(client_bit == 0)
    {
      // Wait 1/16 sec
      WAIT_62_5_MS();
    }
  }
  if(client_bit != 0)
  {
    printk("Detected slaves: ");
    if(client_bit & 0x02) printk("1 ");
    if(client_bit & 0x04) printk("2 ");
    if(client_bit & 0x08) printk("3 ");
    printk("\n");
  }
  else
  {
    printk("No slaves!\n");
    return -1;
  }

  // Check all slaves
  sendMulti(0x6100 + client_bit, &slave0, &slave1, &slave2, &slave3);
  if(client_bit & (1<<1))
  {
    if(slave1 != (0x7200 + (1<<1)))
    {
      printk("Slave1 ACK error (0x%x)\n", slave1);
      return -1;
    }
  }
  if(client_bit & (1<<2))
  {
    if(slave2 != (0x7200 + (1<<2)))
    {
      printk("Slave2 ACK error (0x%x)\n", slave2);
      return -1;
    }
  }
  if(client_bit & (1<<3))
  {
    if(slave3 != (0x7200 + (1<<3)))
    {
      printk("Slave3 ACK error (0x%x)\n", slave3);
      return -1;
    }
  }
  printk("1 - ACK ok\n");

  // Send multiboot header
  sendMulti((pHeader[0*2+1] << 8) + pHeader[0*2], &slave0, &slave1, &slave2, &slave3);
  sendMulti((pHeader[1*2+1] << 8) + pHeader[1*2], &slave0, &slave1, &slave2, &slave3);
  for(int i(2); i < 0x60; i++)
  {
    sendMulti((goodHeader[i*2+1] << 8) + goodHeader[i*2], &slave0, &slave1, &slave2, &slave3);
    if(client_bit & (1<<1))
    {
      if(slave1 != (((0x60 - i) << 8) | (1<<1)))
      {
        printk("Slave1 header invalid response (0x%x)\n", slave1);
        return -1;
      }
    }
    if(client_bit & (1<<2))
    {
      if(slave2 != (((0x60 - i) << 8) | (1<<2)))
      {
        printk("Slave2 header invalid response (0x%x)\n", slave2);
        return -1;
      }
    }
    if(client_bit & (1<<3))
    {
      if(slave3 != (((0x60 - i) << 8) | (1<<3)))
      {
        printk("Slave3 header invalid response (0x%x)\n", slave3);
        return -1;
      }
    }
  }
  printk("Header sent ok\n");

  // Check all slaves
  sendMulti(0x6200, &slave0, &slave1, &slave2, &slave3);
  if(client_bit & (1<<1))
  {
    if(slave1 != (0x0000 + (1<<1)))
    {
      printk("Slave1 ACK error (0x%x)\n", slave1);
      return -1;
    }
  }
  if(client_bit & (1<<2))
  {
    if(slave2 != (0x0000 + (1<<2)))
    {
      printk("Slave2 ACK error (0x%x)\n", slave2);
      return -1;
    }
  }
  if(client_bit & (1<<3))
  {
    if(slave3 != (0x0000 + (1<<3)))
    {
      printk("Slave3 ACK error (0x%x)\n", slave3);
      return -1;
    }
  }
  printk("2 - ACK ok\n");

  // Check all slaves
  sendMulti(0x6200 + client_bit, &slave0, &slave1, &slave2, &slave3);
  if(client_bit & (1<<1))
  {
    if(slave1 != (0x7200 + (1<<1)))
    {
      printk("Slave1 ACK error (0x%x)\n", slave1);
      return -1;
    }
  }
  if(client_bit & (1<<2))
  {
    if(slave2 != (0x7200 + (1<<2)))
    {
      printk("Slave2 ACK error (0x%x)\n", slave2);
      return -1;
    }
  }
  if(client_bit & (1<<3))
  {
    if(slave3 != (0x7200 + (1<<3)))
    {
      printk("Slave3 ACK error (0x%x)\n", slave3);
      return -1;
    }
  }
  printk("3 - ACK ok\n");

  // Send palette data and receive client data
  uint8_t palette_data(0xc1);
  uint8_t client_data[3] = {0xff, 0xff, 0xff};
  uint8_t check = client_bit;
  while(check != 0)
  {
    sendMulti(0x6300 + palette_data, &slave0, &slave1, &slave2, &slave3);
    if(check & (1<<1))
    {
      if((slave1 & 0xff00) == 0x7300)
      {
        client_data[0] = slave0 & 0x00ff;
        check &= ~(1<<1);
      }
    }
    if(check & (1<<2))
    {
      if((slave2 & 0xff00) == 0x7300)
      {
        client_data[1] = slave0 & 0x00ff;
        check &= ~(1<<2);
      }
    }
    if(check & (1<<3))
    {
      if((slave3 & 0xff00) == 0x7300)
      {
        client_data[2] = slave0 & 0x00ff;
        check &= ~(1<<3);
      }
    }
  }
  printk("4 - ACK ok\n");

  uint8_t handshake_data = 0x11 + client_data[0] + client_data[1] + client_data[2];
  sendMulti(0x6400 + handshake_data, &slave0, &slave1, &slave2, &slave3);
  if(client_bit & (1<<1))
  {
    if((slave1 & 0xff00) != 0x7300)
    {
      printk("Slave1 ACK error (0x%x)\n", slave1);
      return -1;
    }
  }
  if(client_bit & (1<<2))
  {
    if((slave2 & 0xff00) != 0x7300)
    {
      printk("Slave2 ACK error (0x%x)\n", slave2);
      return -1;
    }
  }
  if(client_bit & (1<<3))
  {
    if((slave3 & 0xff00) != 0x7300)
    {
      printk("Slave3 ACK error (0x%x)\n", slave3);
      return -1;
    }
  }
  printk("5 - ACK ok\n");

  uint32_t length = (256*1024) - 0xc0;
//  length = (length + 0xf) & ~0xf; // 16 byte units
//  if(length < 0x1c0)
//    length = 0x1c0;

  memset(&mbp, 0, sizeof(MultiBootParam));
  mbp.handshake_data    = handshake_data;
  mbp.probe_count       = 0xd1;
  mbp.client_data[0]    = client_data[0];
  mbp.client_data[1]    = client_data[1];
  mbp.client_data[2]    = client_data[2];
  mbp.palette_data      = palette_data;
  mbp.client_bit        = client_bit;
  mbp.boot_srcp         = pData;
  mbp.boot_endp         = pData + 0x3ff40; // length;

  printk("Sending...");
  uint32_t flags = local_save_flags();
  local_irq_disable();
  uint32_t status = MultiBoot(&mbp, MODE16_MULTI);
  local_irq_restore(flags);

  if(status == 0)
  {
    printk("ok\n");
  }
  else
  {
    printk("error (0x%x)\n", status);
    return -1;
  }

  return 0;
}


The last thing I see is: "Sending...error (0x1)"
The slave gba is already showing the orange nintendo logo.

#152766 - eKid - Thu Mar 20, 2008 4:47 pm

The problem is when you're reading the client data for the slaves (after sending palette data). There's a mistake that your reading slave0 for all of them rather than using slave1, slave2, and slave3. I changed that, and the upload worked.

BTW, no$gba doesn't really pay attention to those details when using the multiboot function (its better if you want instant multibooting). But! If you have a GBA BIOS dump, and tell it to use that, then it can emulate the real multiboot code.

Also BTW, multibooting in multi-player mode is very slow! It takes over a minute to transfer the full 256kb. What I did in my last game was make a very small application to multiboot to the slaves (which handles the special transfer), and then transfer the actual game image using 'normal' communication mode (cuts transfer time to about 1/10).

#152779 - Maximus32 - Thu Mar 20, 2008 7:49 pm

Thanks for finding that bug! Guess I was staring at the code for too long. However it still doesn't work here?! How did you manage to get it running? Can you post the modified code?

I'm testing on 2 GBA-Micro's with a 2-player cable (not really a standard setup). Could there perhaps be something wrong with that setup in combination with multi-player mode?

I'll try to find a gba bios and I'll try the normal 32 bit mode. Perhaps that works for me.

Thanks for your help so far!

#152791 - dantheman - Thu Mar 20, 2008 10:29 pm

If you search Google for "GBAMP BIOS Dumper" you should be able to find a program tepples made to extract your own BIOS from your GBM. It's DLDI-capable so it works on more than just the GBAMP.

#152797 - Maximus32 - Thu Mar 20, 2008 11:01 pm

I got a bios for no$gba, and guess what... same problem. When calling the MultiBoot finction it immediately returns 1.

At least now I can see if my changes have any effect much faster using the emulator, than using real hardware.

#152812 - eKid - Fri Mar 21, 2008 2:11 am

I tested using 2 GBA SPs with the standard multi-play cable (with the little hub in the middle). I'm not sure if the micro's are different...

Hmm, multi-player communication mode only works if the cable has crossed SO/SI lines. no$gba emulates both types of cables, it can be changed in the emulation options.

Here's my modified code, http://ekid.nintendev.com/mbcode.c
I kinda messed it up a bit (made it C :P) to get it to compile. Another thing I changed was resetting the '15' counter when you do the 1/16 second wait. That shouldn't be an issue though. Also, ignore those delay numbers I used, they are just random. :)

EDIT: oh, but your code won't make it to the Multiboot function if your cable is incompatible with multi-player mode (but it does, so the problem isn't the cable).


Last edited by eKid on Fri Mar 21, 2008 3:56 am; edited 1 time in total

#152813 - eKid - Fri Mar 21, 2008 2:18 am

Oh, also, I changed this to use the header of my test rom (at 0x2000000)
Code:

// Send multiboot header
sendMulti((pHeader[0*2+1] << 8) + pHeader[0*2], &slave0, &slave1, &slave2, &slave3);
sendMulti((pHeader[1*2+1] << 8) + pHeader[1*2], &slave0, &slave1, &slave2, &slave3);
for(int i(2); i < 0x60; i++)
{
  sendMulti((goodHeader[i*2+1] << 8) + goodHeader[i*2], &slave0, &slave1, &slave2, &slave3);


I'm not sure what pHeader is, and goodHeader, but there is a complement check in the header files. If you're using a different value for the first word, the checksum may be wrong in goodHeader. The GBA will refuse to boot without a proper checksum.

#152822 - Maximus32 - Fri Mar 21, 2008 6:59 am

They are two different headers. First I used pHeader only (a pointer to 0x8000000) but it showed a scrambled nintendo logo. So then I found this example multiboot program that used a (known good) header, and replaces the branch instruction at the beginning.
I think the header works though, becouse I see a proper nintendo logo, and not a communication error.

pData, by the way, is a pointer to 0x80000c0.

The full source can be found here:
https://bricks-os.svn.sourceforge.net/svnroot/bricks-os/trunk/kernel/arch/arm/gbands/gbaSerial.cpp

#152829 - eKid - Fri Mar 21, 2008 11:34 am

The Nintendo logo will still show up fine, but the complement check may still be wrong (thus still preventing execution). Are you running your main code from ewram? Why are you transferring a different first word anyway? That's the program entry point when running from a cartridge, it isn't used when multibooting though. The Value at 0xC0 is the multiboot entry point... (not transferred with goodHeader) You only need to transfer goodHeader.

Also, the nintendo logo probably isn't set right in your main program. There is a 'gbafix' tool to correct it (and calculate the checksum).

#152830 - Maximus32 - Fri Mar 21, 2008 12:24 pm

IT WORKS!!!

Sometimes the problem is not what it seems to be...

After building your example it worked perfectly! So then I placed your modified code in my project, and to my surprise it failed! After moving the function call to the beginning of my project it all of a sudden worked again! Turns out it only works BEFORE I enable interrupts (even if I disable interrupt before calling the bios function).

So why?? Turns out the problem is my preemptive scheduler (yes, on a gba) is messing up the cpu mode or the stacks.

I am no longer using a separate header, and everything works (logo showing up fine, in both gba's).

Only thing I need to do now is dive into assembly again and fix the multithreading...

Anyway, thanks a lot for all the help!