File Coverage

blib/lib/Device/GBA.pm
Criterion Covered Total %
statement 24 104 23.0
branch 0 16 0.0
condition 0 4 0.0
subroutine 8 17 47.0
pod 4 7 57.1
total 36 148 24.3


line stmt bran cond sub pod time code
1             package Device::GBA;
2 1     1   64651 use strict;
  1         9  
  1         28  
3 1     1   5 use warnings;
  1         2  
  1         22  
4 1     1   503 use integer;
  1         14  
  1         4  
5 1     1   507 use Time::HiRes;
  1         1614  
  1         4  
6 1     1   698 use Device::Chip::Adapter::BusPirate v0.15;
  1         77539  
  1         35  
7 1     1   439 use File::stat;
  1         6575  
  1         4  
8 1     1   584 use Term::ProgressBar;
  1         63711  
  1         54  
9              
10             # ABSTRACT: Perl Interface to the Gameboy Advance
11             our $VERSION = '0.004'; # VERSION
12              
13 1     1   8 use Carp;
  1         3  
  1         1050  
14              
15             =pod
16              
17             =encoding utf8
18              
19             =head1 NAME
20              
21             Device::GBA - Perl Interface to the Gameboy Advance
22              
23             =head1 VERSION
24              
25             version 0.004
26              
27             =head1 SYNOPSIS
28              
29             use Device::GBA;
30              
31             my $gba = Device::GBA->new(buspirate => '/dev/ttyUSB0') or die "No such device!\n";
32             $gba->upload('helloworld.gba');
33              
34             =head1 DESCRIPTION
35              
36             The Nintendo Gameboy Advance can either boot from cartridge or over link cable. The latter is caled multiboot mode and is basically SPI and a homebrew encoding scheme. Unfortunately, the Bus Pirate doesn't have a 100k SPI mode, so we are using 125000 instead. If you encounter problems with booting, use the next lower speed (30000) as bitrate.
37             This utility allows uploading multiboot GBA images via Ls. Don't forget to pass C<-specs=gba_mb.specs> to devkitARM GCC if you want to link a multiboot image. The package's C subdirectory contains an L for cross-compilation. The wiring is as follows:
38              
39             GBA Bus Pirate
40             SO --> MISO
41             SI <-- MOSI
42             CLK <-- CLK
43             GND --- GND
44              
45             (Note to myself:) The cable I made looks like this:
46              
47             ___________________
48             .--------GND (white)----/ .-------._ |
49             | .-----SD (black)------------|SD (B) |_ |
50             | | .--SO (yellow)---, -|SC (R) |_--+-.
51             _|__|__|_ \ -|GND (W)|_--' |
52             / 6 4 2 \ \____-|SO (Y) |_ |
53             \_5_ 3 _1_/ ___-|SI (O) |_ |
54             | \_/ '-- VDD (n/a) / '-------' |
55             | '----- SI (orange) ---/ |
56             '-------- SC (red) --------------------------'
57              
58              
59             Note: This is still work in progress!
60              
61             =head1 METHODS AND ARGUMENTS
62              
63             =over 4
64              
65             =item new()
66              
67             Opens specified device and returns the corresponding object reference. Returns undef
68             if an attempt to open the device has failed. Accepts following parameters:
69              
70             =over 4
71              
72             =item B
73              
74             An instance of L capable of SPI communication.
75              
76             =item B
77              
78             if true, methods on this instance will narrate what they're doing. Default is C<0>.
79              
80             =back
81              
82             =cut
83              
84             sub new {
85 0     0 1   my $class = shift;
86 0           my $self = {
87             verbose => 0,
88             bitrate => '125000',
89             @_
90             };
91              
92 0 0   0     $self->{log} = $self->{verbose} ? sub { printf shift . "\n", @_ } : sub { };
  0            
93              
94 0           enter_spi($self);
95              
96 0           bless $self, $class;
97 0           return $self;
98             }
99              
100             sub enter_spi
101             {
102 0     0 0   my $self = shift;
103 0 0         return if defined $self->{spi};
104              
105 0           $self->{spi} = $self->{adapter}->make_protocol("SPI")->get;
106 0           $self->{spi}->configure(mode => 3, max_bitrate => $self->{bitrate})->get;
107             }
108              
109             =item upload
110              
111             $gba->upload($firmware_file)
112              
113             Reads in I<$firmware_file> and uploads it to the Gameboy Advance.
114              
115             =cut
116              
117             sub upload {
118 0     0 1   my $self = shift;
119 0           my $firmware = shift;
120              
121 0 0         open my $fh, "<:raw", $firmware or croak "Can't open file `$firmware': $!\n";
122 0           $self->log(".....Opening GBA file readonly");
123              
124 0           my $fsize = stat($firmware)->size;
125 0           $fsize = ($fsize+0x0f)&0xfffffff0;
126              
127 0 0         if($fsize > 256 * 1024)
128             {
129 0           croak ("Err: Max file size 256kB");
130             }
131              
132 0           my $fcnt;
133              
134 0           $self->log(".....GBA file length 0x%08x", $fsize);
135 0           $self->log("BusPirate(mstr) GBA(slave) ");
136              
137 0           $self->enter_spi;
138              
139 0           $self->spi_handshake(0x00006202, 0x72026202, "Looking for GBA");
140              
141 0           $self->spi_readwrite(0x00006202, "Found GBA");
142 0           $self->spi_readwrite(0x00006102, "Recognition OK");
143              
144             my $progress = Term::ProgressBar->new({
145             name => 'Upload',
146             count => $fsize,
147             ETA => 'linear',
148             silent => not $self->{verbose}
149 0           });
150 0           my $oldlog = $self->{log};
151 0 0   0     $self->{log} = sub { $progress->message(sprintf shift, @_) } if $self->{verbose};
  0            
152              
153 0           local $/ = \2;
154 0           for($fcnt = 0; $fcnt < 192; $progress->update($fcnt += 2)) {
155 0           $self->spi_readwrite(unpack 'S<', <$fh>);
156             }
157              
158 0           $self->spi_readwrite(0x00006200, "Transfer of header data complete");
159 0           $self->spi_readwrite(0x00006202, "Exchange master/slave info again");
160              
161 0           $self->spi_readwrite(0x000063d1, "Send palette data");
162              
163 0           my $r = $self->spi_readwrite(0x000063d1, "Send palette data, receive 0x73hh****");
164              
165 0           my $m = (($r & 0x00ff0000) >> 8) + 0xffff00d1;
166 0           my $h = (($r & 0x00ff0000) >> 16) + 0xf;
167              
168 0           $r = $self->spi_readwrite(((($r >> 16) + 0xf) & 0xff) | 0x00006400, "Send handshake data");
169 0           $r = $self->spi_readwrite(($fsize - 0x190) / 4, "Send length info, receive seed 0x**cc****");
170              
171 0           my $f = ((($r & 0x00ff0000) >> 8) + $h) | 0xffff0000;
172 0           my $c = 0x0000c387;
173              
174 0           local $/ = \4;
175 0           for (; $fcnt < $fsize; $progress->update($fcnt += 4)) {
176 0   0       my $chunk = <$fh> // '';
177 0           $chunk .= "\0" x (4 - length $chunk);
178 0           my $w = unpack('L<', $chunk);
179 0           $c = crc($w, $c);
180 0           $m = ((0x6f646573 * $m) & 0xFFFFFFFF) + 1;
181 0           my $data = $w ^ ((~(0x02000000 + $fcnt)) + 1) ^ $m ^ 0x43202f2f;
182 0           $self->spi_readwrite($data);
183             }
184              
185 0           $self->{log} = $oldlog;
186              
187 0           $c = crc($f, $c);
188              
189 0           $self->spi_handshake(0x00000065, 0x00750065, "\nWait for GBA to respond with CRC");
190              
191 0           $self->spi_readwrite(0x00000066, "GBA ready with CRC");
192 0           $self->spi_readwrite($c, "Let's exchange CRC!");
193              
194 0           $self->log("CRC ...hope they match!");
195 0           $self->log("MultiBoot done");
196             }
197              
198             =item spi_readwrite
199              
200             $miso = $gba->spi_readwrite($mosi)
201              
202             reads and writes 32 bit from the SPI bus.
203              
204             =cut
205              
206             sub spi_readwrite {
207 0     0 1   my $self = shift;
208 0           my ($w, $msg) = @_;
209 0           $self->enter_spi;
210 0           my $r = unpack 'L>', $self->{spi}->readwrite(pack 'L>', shift)->get;
211 0 0         $self->log("0x%08x 0x%08x ; %s", $r , $w, $msg) if defined $msg;
212 0           return $r;
213             }
214              
215             sub spi_handshake {
216 0     0 0   my $self = shift;
217 0           my ($w, $expected, $msg) = @_;
218 0 0         $self->log("%s 0x%08x", $msg, $expected) if defined $msg;
219              
220 0           while ($self->spi_readwrite($w) != $expected) {
221 0           sleep 0.01;
222             }
223             }
224              
225              
226             =item crc
227              
228             $c = Device::GBA::crc($w, [$c = 0x0000c387])
229              
230             Calculates CRC for word C<$w> and CRC C<$c> according to the algrithm used by the GBA multiboot protocol.
231              
232             =cut
233              
234             sub crc
235             {
236 0     0 1   my $w = shift;
237 0   0       my $c = shift // 0x0000c387;
238 0           for (my $bit = 0; $bit < 32; $bit++) {
239 0 0         if(($c ^ $w) & 0x01) {
240 0           $c = ($c >> 1) ^ 0x0000c37b;
241             } else {
242 0           $c = $c >> 1;
243             }
244              
245 0           $w = $w >> 1;
246             }
247              
248 0           return $c;
249             }
250              
251 0     0 0   sub log { my $log = shift->{'log'}; goto $log }
  0            
252              
253              
254             1;
255             __END__