File Coverage

blib/lib/Device/Chip/SDCard.pm
Criterion Covered Total %
statement 131 146 89.7
branch 13 22 59.0
condition n/a
subroutine 22 24 91.6
pod 5 7 71.4
total 171 199 85.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2016-2023 -- leonerd@leonerd.org.uk
5              
6 5     5   1048974 use v5.26;
  5         45  
7 5     5   23 use warnings;
  5         9  
  5         137  
8 5     5   556 use Object::Pad 0.800;
  5         9151  
  5         216  
9              
10             package Device::Chip::SDCard 0.04;
11             class Device::Chip::SDCard
12 1     1   516 :isa(Device::Chip);
  1         14927  
  1         35  
13              
14 5     5   1236 use Future::AsyncAwait;
  5         10  
  5         21  
15              
16 5     5   2250 use Data::Bitfield qw( bitfield boolfield );
  5         10042  
  5         361  
17              
18 5     5   34 use constant PROTOCOL => "SPI";
  5         10  
  5         4270  
19              
20             =head1 NAME
21              
22             C - chip driver for F and F cards
23              
24             =head1 SYNOPSIS
25              
26             use Device::Chip::SDCard;
27             use Future::AsyncAwait;
28              
29             my $card = Device::Chip::SDCard->new;
30              
31             await $card->mount( Device::Chip::Adapter::...->new );
32              
33             await $card->initialise;
34              
35             my $bytes = await $card->read_block( 0 );
36              
37             print "Read block zero:\n";
38             printf "%v02X\n", $bytes;
39              
40             =head1 DESCRIPTION
41              
42             This L subclass provides specific communication to an F or
43             F storage card attached via an SPI adapter.
44              
45             At present it only supports MMC and SDSC ("standard capacity") cards, not SDHC
46             or SDXC.
47              
48             =cut
49              
50             method SPI_options
51 4     4 0 1480 {
52             return (
53 4         25 mode => 0,
54             max_bitrate => 1E6,
55             );
56             }
57              
58             =head1 METHODS
59              
60             The following methods documented in an C expression return L
61             instances.
62              
63             =cut
64              
65 5         6 async method send_command ( $cmd, $arg = 0, $readlen = 0 )
  5         10  
  5         7  
  5         8  
  5         7  
66 5         15 {
67 5         7 my $crcstop = 0x95;
68              
69             # TODO: until we can perform dynamic transactions with D:C:A we'll have to
70             # do this by presuming the maximum amount of time for the card to respond
71             # (8 words) and look for the response in what's returned
72              
73 5         14 my ( $resp ) = await $self->protocol->readwrite(
74             pack "C N C a*", 0x40 | $cmd, $arg, $crcstop, "\xFF" x ( 8 + $readlen ),
75             );
76              
77             # Trim to the start of the expected result
78 5         12295 substr $resp, 0, 7, "";
79              
80             # Look for a byte with top bit clear
81 5         13 while( length $resp ) {
82 9         22 my $ret = unpack( "C", $resp );
83 9 100       47 return ( $ret, unpack "x a$readlen", $resp ) if !( $ret & 0x80 );
84              
85 4         18 substr $resp, 0, 1, "";
86             }
87              
88 0         0 die sprintf "Timed out waiting for response to command %02X", $cmd;
89 5     5 0 294 }
90              
91 4         6 async method _recv_data_block ( $buf, $len )
  4         6  
  4         5  
  4         6  
92 4         12 {
93             # Wait for a token
94 4         6 while(1) {
95 7         3366 $buf =~ s/^\xFF+//;
96              
97 7 100       25 last if $buf =~ s/^\xFE//;
98              
99 3         7 $buf .= await $self->protocol->readwrite_no_ss( "\xFF" x 16 );
100             }
101              
102             # Now want the data + CRC
103 4 50       14 if( length $buf < $len + 2 ) {
104 4         13 $buf .= await $self->protocol->readwrite_no_ss( "\xFF" x ( $len + 2 - length $buf ) );
105             }
106              
107             # TODO: might want to verify the CRC?
108              
109 4         4387 return substr $buf, 0, $len;
110 4     4   6 }
111              
112             # Commands
113             use constant {
114 5         559 CMD_GO_IDLE_STATE => 0,
115             CMD_SEND_OP_COND => 1,
116             CMD_SEND_CSD => 9,
117             CMD_SET_BLOCKLEN => 16,
118             CMD_READ_SINGLE_BLOCK => 17,
119             CMD_READ_OCR => 58,
120 5     5   36 };
  5         8  
121              
122             # Response first byte bitflags
123             use constant {
124 5         13918 RESP_PARAM_ERROR => 1<<6,
125             RESP_ADDR_ERROR => 1<<5,
126             RESP_ERASESEQ_ERROR => 1<<4,
127             RESP_CRC_ERROR => 1<<3,
128             RESP_ILLEGAL_CMD => 1<<2,
129             RESP_ERASE_RESET => 1<<1,
130             RESP_IDLE => 1<<0,
131 5     5   27 };
  5         10  
132              
133             =head2 initialise
134              
135             await $card->initialise;
136              
137             Checks that an SD card is present, switches it into SPI mode and waits for its
138             initialisation process to complete.
139              
140             =cut
141              
142 1         2 async method initialise ()
  1         1  
143 1         3 {
144             # Initialise first by switching the card into SPI mode
145 1         4 await $self->protocol->write( "\xFF" x 10 );
146              
147 1         7656 my $resp = await $self->send_command( CMD_GO_IDLE_STATE );
148 1 50       69 $resp == 1 or die "Expected 01 response; got $resp";
149              
150 1         4 foreach my $attempt ( 1 .. 200 ) {
151             # TODO: Consider using SEND_IF_COND and doing SDHC initialisation
152 2         6 $resp = await $self->send_command( CMD_SEND_OP_COND );
153 2 100       124 last unless $resp & RESP_IDLE;
154             }
155              
156 1 50       4 $resp & RESP_IDLE and die "Timed out waiting for card to leave IDLE mode";
157              
158 1         3 $resp = await $self->send_command( CMD_SET_BLOCKLEN, 512 );
159 1 50       61 $resp == 0 or die "Expected 00 response; got $resp";
160              
161 1         5 return;
162 1     1 1 419 }
163              
164             =head2 size
165              
166             $n_bytes = await $card->size;
167              
168             Returns the size of the media card in bytes.
169              
170             =cut
171              
172 0         0 async method size ()
  0         0  
173 0         0 {
174 0         0 my $csd = await $self->read_csd;
175              
176 0         0 return $csd->{bytes};
177 0     0 1 0 }
178              
179 4         5 method _spi_txn ( $code )
  4         6  
  4         5  
180 4     4   9 {
181             $self->protocol->assert_ss->then(
182             $code
183             )->followed_by( sub {
184 4     4   611 my ( $f ) = @_;
185 4         11 $self->protocol->release_ss->then( sub { $f } );
  4         4585  
186 4         19 });
187             }
188              
189             =head2 read_csd
190              
191             $data = await $card->read_csd;
192              
193             Returns a C reference containing decoded fields from the SD card's CSD
194             ("card-specific data") register.
195              
196             This hash will contain the following fields:
197              
198             TAAC
199             NSAC
200             TRAN_SPEED
201             CCC
202             READ_BL_LEN
203             READ_BL_LEN_PARTIAL
204             WRITE_BLK_MISALIGN
205             READ_BLK_MISALIGN
206             DSR_IMP
207             C_SIZE
208             VDD_R_CURR_MIN
209             VDD_R_CURR_MAX
210             VDD_W_CURR_MIN
211             VDD_W_CURR_MAX
212             C_SIZE_MULT
213             ERASE_BLK_EN
214             SECTOR_SIZE
215             WP_GRP_SIZE
216             WP_GRP_ENABLE
217             R2W_FACTOR
218             WRITE_BL_LEN
219             WRITE_BL_PARTIAL
220             FILE_FORMAT_GRP
221             COPY
222             PERM_WRITE_PROTECT
223             TEMP_WRITE_PROTECT
224             FILE_FORMAT
225              
226             The hash will also contain the following calculated fields, derived from the
227             decoded fields above for convenience of calling code.
228              
229             blocks # number of blocks implied by C_SIZE / C_SIZE_MULT
230             bytes # number of bytes of storage, implied by blocks and READ_BL_LEN
231              
232             =cut
233              
234             # This code is most annoying to write as it involves lots of bitwise unpacking
235             # at non-byte boundaries. It's easier (though inefficient) to perform this on
236             # an array of 128 1-bit values
237             sub _bits_to_uint ( @vals )
238 17     17   20 {
  17         42  
  17         20  
239 17         18 my $n = 0;
240 17         40 ( $n <<= 1 ) |= $_ for reverse @vals;
241 17         86 return $n;
242             }
243              
244             my %_DECSCALE = (
245             1 => 1.0, 2 => 1.2, 3 => 1.3, 4 => 1.5, 5 => 2.0, 6 => 2.5,
246             7 => 3.0, 8 => 3.5, 9 => 4.0, 0xA => 4.5, 0xB => 5.0,
247             0xC => 5.5, 0xD => 6.0, 0xE => 7.0, 0xF => 8.0
248             );
249              
250 2         3 sub _convert_decimal ( $unit, $val )
251 2     2   3 {
  2         3  
  2         2  
252 2         5 my $mult = $unit % 3;
253 2         2 $unit -= $mult;
254 2         4 $unit /= 3;
255              
256 2         7 $val = $_DECSCALE{$val} * ( 10 ** $mult );
257              
258 2         15 return $val . substr( "num kMG", $unit + 3, 1 );
259             }
260              
261             my %_CURRMIN = (
262             0 => 0.5, 1 => 1, 2 => 5, 3 => 10,
263             4 => 25, 5 => 35, 6 => 60, 7 => 100,
264             );
265             my %_CURRMAX = (
266             0 => 1, 1 => 5, 2 => 10, 3 => 25,
267             4 => 35, 5 => 45, 6 => 80, 7 => 200,
268             );
269              
270             sub _unpack_csd_v0 ( $bytes )
271 1     1   2 {
  1         2  
  1         2  
272 1         31 my @bits = reverse split //, unpack "B128", $bytes;
273              
274             my %csd = (
275             TAAC => _convert_decimal( _bits_to_uint( @bits[112 .. 114] ) - 9, _bits_to_uint( @bits[115 .. 118] ) ) . "s",
276             NSAC => 100*_bits_to_uint( @bits[104 .. 111] ) . "ck",
277             TRAN_SPEED => _convert_decimal( _bits_to_uint( @bits[ 96 .. 98] ) + 5, _bits_to_uint( @bits[ 99 .. 102] ) ) . "bit/s",
278 12         22 CCC => [ grep { $bits[84+$_] } 0 .. 11 ],
279             READ_BL_LEN => 2**_bits_to_uint( @bits[ 80 .. 83] ),
280             READ_BL_LEN_PARTIAL => $bits[79],
281             WRITE_BLK_MISALIGN => $bits[78],
282             READ_BLK_MISALIGN => $bits[77],
283             DSR_IMP => $bits[76],
284             C_SIZE => _bits_to_uint( @bits[ 62 .. 73] ),
285             VDD_R_CURR_MIN => $_CURRMIN{ _bits_to_uint( @bits[ 59 .. 61] ) } . "mA",
286             VDD_R_CURR_MAX => $_CURRMAX{ _bits_to_uint( @bits[ 56 .. 58] ) } . "mA",
287             VDD_W_CURR_MIN => $_CURRMIN{ _bits_to_uint( @bits[ 53 .. 55] ) } . "mA",
288 1         7 VDD_W_CURR_MAX => $_CURRMAX{ _bits_to_uint( @bits[ 50 .. 52] ) } . "mA",
289             C_SIZE_MULT => _bits_to_uint( @bits[ 47 .. 49] ),
290             ERASE_BLK_EN => $bits[46],
291             SECTOR_SIZE => 1+_bits_to_uint( @bits[ 39 .. 45] ),
292             WP_GRP_SIZE => 1+_bits_to_uint( @bits[ 32 .. 38] ),
293             WP_GRP_ENABLE => $bits[31],
294             R2W_FACTOR => 2**_bits_to_uint( @bits[ 26 .. 28] ),
295             WRITE_BL_LEN => 2**_bits_to_uint( @bits[ 22 .. 25] ),
296             WRITE_BL_PARTIAL => $bits[21],
297             FILE_FORMAT_GRP => $bits[15],
298             COPY => $bits[14],
299             PERM_WRITE_PROTECT => $bits[13],
300             TEMP_WRITE_PROTECT => $bits[12],
301             FILE_FORMAT => _bits_to_uint( @bits[ 10 .. 11] ),
302             # Final bits are the CRC, which we ignore
303             );
304              
305 1         5 $csd{blocks} = ( 1 + $csd{C_SIZE} ) * ( 2 ** ( $csd{C_SIZE_MULT} + 2 ) );
306 1         4 $csd{bytes} = $csd{blocks} * $csd{READ_BL_LEN};
307              
308 1         24 return \%csd;
309             }
310              
311 1         2 async method read_csd ()
  1         1  
312 1         3 {
313 1         3 my $protocol = $self->protocol;
314              
315 1     1   7796 my $csd = await $self->_spi_txn( async sub {
316 1         6 await $protocol->write_no_ss(
317             pack "C N C a*", 0x40 | CMD_SEND_CSD, 0, 0xFF, "\xFF"
318             );
319              
320 1         1201 my $buf = await $protocol->readwrite_no_ss( "\xFF" x 8 );
321              
322 1         1119 $buf =~ s/^\xFF*//;
323 1 50       6 $buf =~ s/^\0// or
324             return Future->fail( sprintf "Expected response 00; got %02X to SEND_CSD", ord $buf );
325              
326 1         6 return await $self->_recv_data_block( $buf, 16 );
327 1         11 });
328              
329             # Top two bits give the structure version
330 1         143 my $ver = vec( $csd, 0, 2 );
331 1 50       5 if( $ver == 0 ) {
    0          
332 1         5 return _unpack_csd_v0( $csd );
333             }
334             elsif( $ver == 1 ) {
335 0         0 return _unpack_csd_v1( $csd );
336             }
337             else {
338 0         0 die "Bad CSD structure version $ver";
339             }
340 1     1 1 422 }
341              
342             =head2 read_ocr
343              
344             $fields = await $card->read_ocr;
345              
346             Returns a C reference containing decoded fields from the card's OCR
347             ("operating conditions register").
348              
349             This hash will contain the following fields:
350              
351             BUSY
352             CCS
353             UHS_II
354             1V8_ACCEPTED
355             3V5, 3V4, 3V3, ..., 2V7
356              
357             =cut
358              
359             bitfield OCR =>
360             BUSY => boolfield( 31 ),
361             CCS => boolfield( 30 ),
362             UHS_II => boolfield( 29 ),
363             '1V8_ACCEPTED' => boolfield( 24 ),
364             '3V5' => boolfield( 23 ),
365             '3V4' => boolfield( 22 ),
366             '3V3' => boolfield( 21 ),
367             '3V2' => boolfield( 20 ),
368             '3V1' => boolfield( 19 ),
369             '3V0' => boolfield( 18 ),
370             '2V9' => boolfield( 17 ),
371             '2V8' => boolfield( 16 ),
372             '2V7' => boolfield( 15 );
373              
374 0         0 async method read_ocr ()
  0         0  
375 0         0 {
376 0         0 my ( $resp, $ocr ) = await $self->send_command( CMD_READ_OCR, undef, 4 );
377              
378 0         0 return { unpack_OCR( unpack "N", $ocr ) };
379 0     0 1 0 }
380              
381             =head2 read_block
382              
383             $bytes = await $card->read_block( $lba );
384              
385             Returns a 512-byte bytestring containing data read from the given sector of
386             the card.
387              
388             =cut
389              
390 3         4 async method read_block ( $lba )
  3         4  
  3         5  
391 3         7 {
392 3         5 my $byteaddr = $lba * 512;
393              
394 3         8 my $protocol = $self->protocol;
395              
396 3         14 my $buf;
397              
398 3     3   10128 return await $self->_spi_txn( async sub {
399 3         20 await $protocol->write_no_ss(
400             pack "C N C a*", 0x40 | CMD_READ_SINGLE_BLOCK, $byteaddr, 0xFF, "\xFF"
401             );
402              
403 3         3301 my $buf = await $protocol->readwrite_no_ss( "\xFF" x 8 );
404              
405 3         3213 $buf =~ s/^\xFF*//;
406 3 50       12 $buf =~ s/^\0// or
407             die sprintf "Expected response 00; got %02X to READ_SINGLE_BLOCK", ord $buf;
408              
409 3         10 return await $self->_recv_data_block( $buf, 512 );
410 3         16 });
411 3     3 1 13871 }
412              
413             =head1 TODO
414              
415             =over 4
416              
417             =item *
418              
419             Support block writing.
420              
421             =item *
422              
423             Support the different initialisation sequence (and block size requirements) of
424             SDHC cards.
425              
426             =back
427              
428             =head1 AUTHOR
429              
430             Paul Evans
431              
432             =cut
433              
434             0x55AA;