File Coverage

blib/lib/Device/GBA.pm
Criterion Covered Total %
statement 24 103 23.3
branch 0 18 0.0
condition 0 4 0.0
subroutine 8 16 50.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   57238 use strict;
  1         13  
  1         22  
3 1     1   4 use warnings;
  1         1  
  1         21  
4 1     1   459 use integer;
  1         11  
  1         4  
5 1     1   466 use Time::HiRes;
  1         1290  
  1         3  
6 1     1   561 use Device::BusPirate v0.15;
  1         58049  
  1         48  
7 1     1   436 use File::stat;
  1         6048  
  1         3  
8 1     1   587 use Term::ProgressBar;
  1         55707  
  1         44  
9              
10             # ABSTRACT: Perl Interface to the Gameboy Advance
11             our $VERSION = '0.001'; # VERSION
12              
13 1     1   7 use Carp;
  1         2  
  1         879  
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.001
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 125k instead. If you encounter problems with booting, use the next lower speed (30k) as bitrate.
37             This utility allows uploading multiboot GBA images with the L. 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              
44             Note: This is still work in progress!
45              
46             =head1 METHODS AND ARGUMENTS
47              
48             =over 4
49              
50             =item new()
51              
52             Opens specified device and returns the corresponding object reference. Returns undef
53             if an attempt to open the device has failed. Accepts following parameters:
54              
55             =over 4
56              
57             =item B
58              
59             COM port or handle of the BusPirate connected to the Gameboy Advance.
60              
61             =item B
62              
63             if true, methods on this instance will narrate what they're doing. Default is C<0>.
64              
65             =back
66              
67             =cut
68              
69             sub new {
70 0     0 1   my $class = shift;
71 0           my $self = {
72             verbose => 0,
73             bitrate => '125k',
74             @_
75             };
76              
77 0 0   0     $self->{log} = $self->{verbose} ? sub { printf @_ } : sub { };
  0            
78              
79 0 0         if (ref $self->{buspirate} ne 'Device::BusPirate') {
80 0 0         $self->{buspirate} = Device::BusPirate->new(serial => $self->{buspirate}, %$self)
81             or return;
82             }
83              
84 0           enter_spi($self);
85              
86 0           bless $self, $class;
87 0           return $self;
88             }
89              
90             sub enter_spi
91             {
92 0     0 0   my $self = shift;
93 0 0         return if defined $self->{spi};
94              
95 0           $self->{spi} = $self->{buspirate}->enter_mode( "SPI" )->get;
96 0           $self->{spi}->configure(mode => 3, speed => $self->{bitrate})->get;
97             }
98              
99             =item upload
100              
101             $gba->upload($firmware_file)
102              
103             Reads in I<$firmware_file> and uploads it to the Gameboy Advance.
104              
105             =cut
106              
107             sub upload {
108 0     0 1   my $self = shift;
109 0           my $firmware = shift;
110              
111 0 0         open my $fh, "<:raw", $firmware or croak "Can't open file `$firmware': $!\n";
112 0           $self->log(".....Opening GBA file readonly\r\n");
113              
114 0           my $fsize = stat($firmware)->size;
115 0           $fsize = ($fsize+0x0f)&0xfffffff0;
116              
117 0 0         if($fsize > 256 * 1024)
118             {
119 0           croak ("Err: Max file size 256kB\n");
120             }
121              
122 0           local $/ = \2;
123              
124 0           $self->log(".....GBA file length 0x%08x\r\n\n", $fsize);
125 0           $self->log("BusPirate(mstr) GBA(slave) \r\n\n");
126              
127 0           $self->enter_spi;
128              
129 0           $self->spi_handshake(0x00006202, 0x72026202, "Looking for GBA");
130              
131 0           $self->spi_writeread(0x00006202, "Found GBA");
132 0           $self->spi_writeread(0x00006102, "Recognition OK");
133              
134 0           my $fcnt;
135 0           for($fcnt = 0; $fcnt < 192; $fcnt += 2) {
136 0           $self->spi_writeread(unpack 'S<', <$fh>);
137             }
138              
139 0           $self->spi_writeread(0x00006200, "Transfer of header data complete");
140 0           $self->spi_writeread(0x00006202, "Exchange master/slave info again");
141              
142 0           $self->spi_writeread(0x000063d1, "Send palette data");
143              
144 0           my $r = $self->spi_writeread(0x000063d1, "Send palette data, receive 0x73hh****");
145              
146 0           my $m = (($r & 0x00ff0000) >> 8) + 0xffff00d1;
147 0           my $h = (($r & 0x00ff0000) >> 16) + 0xf;
148              
149 0           $r = $self->spi_writeread(((($r >> 16) + 0xf) & 0xff) | 0x00006400, "Send handshake data");
150 0           $r = $self->spi_writeread(($fsize - 0x190) / 4, "Send length info, receive seed 0x**cc****");
151              
152 0           my $f = ((($r & 0x00ff0000) >> 8) + $h) | 0xffff0000;
153 0           my $c = 0x0000c387;
154              
155              
156             my $progress = Term::ProgressBar->new({
157             name => 'Upload',
158             count => $fsize,
159             ETA => 'linear',
160             silent => not $self->{verbose}
161 0           });
162 0           local $/ = \4;
163              
164 0           for (; $fcnt < $fsize; $fcnt += 4) {
165 0   0       my $chunk = <$fh> // '';
166 0           $chunk .= "\0" x (4 - length $chunk);
167 0           my $w = unpack('L<', $chunk);
168 0           $c = crc($w, $c);
169 0           $m = ((0x6f646573 * $m) & 0xFFFFFFFF) + 1;
170 0           my $data = $w ^ ((~(0x02000000 + $fcnt)) + 1) ^ $m ^ 0x43202f2f;
171 0           $self->spi_writeread($data);
172              
173 0           $progress->update($fcnt);
174             }
175              
176 0           $c = crc($f, $c);
177              
178 0           $self->spi_handshake(0x00000065, 0x00750065, "\nWait for GBA to respond with CRC");
179              
180 0           $self->spi_writeread(0x00000066, "GBA ready with CRC");
181 0           $self->spi_writeread($c, "Let's exchange CRC!");
182              
183 0           $self->log("CRC ...hope they match!\n");
184 0           $self->log("MultiBoot done\n");
185             }
186              
187             =item spi_writeread
188              
189             $miso = $gba->spi_writeread($mosi)
190              
191             reads and writes 32 bit from the SPI bus.
192              
193             =cut
194              
195             sub spi_writeread {
196 0     0 1   my $self = shift;
197 0           my ($w, $msg) = @_;
198 0           $self->enter_spi;
199 0           my $r = unpack 'L>', $self->{spi}->writeread(pack 'L>', shift)->get;
200 0 0         $self->log("0x%08x 0x%08x ; %s\n", $r , $w, $msg) if defined $msg;
201 0           return $r;
202             }
203              
204             sub spi_handshake {
205 0     0 0   my $self = shift;
206 0           my ($w, $expected, $msg) = @_;
207 0 0         $self->log("%s 0x%08x\n", $msg, $expected) if defined $msg;
208              
209 0           while ($self->spi_writeread($w) != $expected) {
210 0           sleep 0.01;
211             }
212             }
213              
214              
215             =item crc
216              
217             $c = Device::GBA::crc($w, [$c = 0x0000c387])
218              
219             Calculates CRC for word C<$w> and CRC C<$c> according to the algrithm used by the GBA multiboot protocol.
220              
221             =cut
222              
223             sub crc
224             {
225 0     0 1   my $w = shift;
226 0   0       my $c = shift // 0x0000c387;
227 0           for (my $bit = 0; $bit < 32; $bit++) {
228 0 0         if(($c ^ $w) & 0x01) {
229 0           $c = ($c >> 1) ^ 0x0000c37b;
230             } else {
231 0           $c = $c >> 1;
232             }
233              
234 0           $w = $w >> 1;
235             }
236              
237 0           return $c;
238             }
239              
240 0     0 0   sub log { my $log = shift->{'log'}; goto $log }
  0            
241              
242              
243             1;
244             __END__