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   68903 use strict;
  1         10  
  1         28  
3 1     1   5 use warnings;
  1         2  
  1         61  
4 1     1   558 use integer;
  1         14  
  1         5  
5 1     1   525 use Time::HiRes;
  1         1370  
  1         4  
6 1     1   660 use Device::Chip::Adapter::BusPirate v0.15;
  1         80687  
  1         35  
7 1     1   476 use File::stat;
  1         6855  
  1         5  
8 1     1   589 use Term::ProgressBar;
  1         65711  
  1         49  
9              
10             # ABSTRACT: Perl Interface to the Gameboy Advance
11             our $VERSION = '0.003'; # VERSION
12              
13 1     1   7 use Carp;
  1         2  
  1         1111  
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.003
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             $self->{spi}->configure(mode => 3, max_bitrate => $self->{bitrate},
107 0           open_drain => 0)->get;
108             }
109              
110             =item upload
111              
112             $gba->upload($firmware_file)
113              
114             Reads in I<$firmware_file> and uploads it to the Gameboy Advance.
115              
116             =cut
117              
118             sub upload {
119 0     0 1   my $self = shift;
120 0           my $firmware = shift;
121              
122 0 0         open my $fh, "<:raw", $firmware or croak "Can't open file `$firmware': $!\n";
123 0           $self->log(".....Opening GBA file readonly");
124              
125 0           my $fsize = stat($firmware)->size;
126 0           $fsize = ($fsize+0x0f)&0xfffffff0;
127              
128 0 0         if($fsize > 256 * 1024)
129             {
130 0           croak ("Err: Max file size 256kB");
131             }
132              
133 0           my $fcnt;
134              
135 0           $self->log(".....GBA file length 0x%08x", $fsize);
136 0           $self->log("BusPirate(mstr) GBA(slave) ");
137              
138 0           $self->enter_spi;
139              
140 0           $self->spi_handshake(0x00006202, 0x72026202, "Looking for GBA");
141              
142 0           $self->spi_readwrite(0x00006202, "Found GBA");
143 0           $self->spi_readwrite(0x00006102, "Recognition OK");
144              
145             my $progress = Term::ProgressBar->new({
146             name => 'Upload',
147             count => $fsize,
148             ETA => 'linear',
149             silent => not $self->{verbose}
150 0           });
151 0           my $oldlog = $self->{log};
152 0 0   0     $self->{log} = sub { $progress->message(sprintf shift, @_) } if $self->{verbose};
  0            
153              
154 0           local $/ = \2;
155 0           for($fcnt = 0; $fcnt < 192; $progress->update($fcnt += 2)) {
156 0           $self->spi_readwrite(unpack 'S<', <$fh>);
157             }
158              
159 0           $self->spi_readwrite(0x00006200, "Transfer of header data complete");
160 0           $self->spi_readwrite(0x00006202, "Exchange master/slave info again");
161              
162 0           $self->spi_readwrite(0x000063d1, "Send palette data");
163              
164 0           my $r = $self->spi_readwrite(0x000063d1, "Send palette data, receive 0x73hh****");
165              
166 0           my $m = (($r & 0x00ff0000) >> 8) + 0xffff00d1;
167 0           my $h = (($r & 0x00ff0000) >> 16) + 0xf;
168              
169 0           $r = $self->spi_readwrite(((($r >> 16) + 0xf) & 0xff) | 0x00006400, "Send handshake data");
170 0           $r = $self->spi_readwrite(($fsize - 0x190) / 4, "Send length info, receive seed 0x**cc****");
171              
172 0           my $f = ((($r & 0x00ff0000) >> 8) + $h) | 0xffff0000;
173 0           my $c = 0x0000c387;
174              
175 0           local $/ = \4;
176 0           for (; $fcnt < $fsize; $progress->update($fcnt += 4)) {
177 0   0       my $chunk = <$fh> // '';
178 0           $chunk .= "\0" x (4 - length $chunk);
179 0           my $w = unpack('L<', $chunk);
180 0           $c = crc($w, $c);
181 0           $m = ((0x6f646573 * $m) & 0xFFFFFFFF) + 1;
182 0           my $data = $w ^ ((~(0x02000000 + $fcnt)) + 1) ^ $m ^ 0x43202f2f;
183 0           $self->spi_readwrite($data);
184             }
185              
186 0           $self->{log} = $oldlog;
187              
188 0           $c = crc($f, $c);
189              
190 0           $self->spi_handshake(0x00000065, 0x00750065, "\nWait for GBA to respond with CRC");
191              
192 0           $self->spi_readwrite(0x00000066, "GBA ready with CRC");
193 0           $self->spi_readwrite($c, "Let's exchange CRC!");
194              
195 0           $self->log("CRC ...hope they match!");
196 0           $self->log("MultiBoot done");
197             }
198              
199             =item spi_readwrite
200              
201             $miso = $gba->spi_readwrite($mosi)
202              
203             reads and writes 32 bit from the SPI bus.
204              
205             =cut
206              
207             sub spi_readwrite {
208 0     0 1   my $self = shift;
209 0           my ($w, $msg) = @_;
210 0           $self->enter_spi;
211 0           my $r = unpack 'L>', $self->{spi}->readwrite(pack 'L>', shift)->get;
212 0 0         $self->log("0x%08x 0x%08x ; %s", $r , $w, $msg) if defined $msg;
213 0           return $r;
214             }
215              
216             sub spi_handshake {
217 0     0 0   my $self = shift;
218 0           my ($w, $expected, $msg) = @_;
219 0 0         $self->log("%s 0x%08x", $msg, $expected) if defined $msg;
220              
221 0           while ($self->spi_readwrite($w) != $expected) {
222 0           sleep 0.01;
223             }
224             }
225              
226              
227             =item crc
228              
229             $c = Device::GBA::crc($w, [$c = 0x0000c387])
230              
231             Calculates CRC for word C<$w> and CRC C<$c> according to the algrithm used by the GBA multiboot protocol.
232              
233             =cut
234              
235             sub crc
236             {
237 0     0 1   my $w = shift;
238 0   0       my $c = shift // 0x0000c387;
239 0           for (my $bit = 0; $bit < 32; $bit++) {
240 0 0         if(($c ^ $w) & 0x01) {
241 0           $c = ($c >> 1) ^ 0x0000c37b;
242             } else {
243 0           $c = $c >> 1;
244             }
245              
246 0           $w = $w >> 1;
247             }
248              
249 0           return $c;
250             }
251              
252 0     0 0   sub log { my $log = shift->{'log'}; goto $log }
  0            
253              
254              
255             1;
256             __END__