1 /*---------------------------------------------------------------------------*
2   Project:  TwlSDK - CARD - libraries
3   File:     card_hash.c
4 
5   Copyright 2008-2009 Nintendo.  All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law. They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Date:: 2009-06-04#$
14   $Rev: 10698 $
15   $Author: okubata_ryoma $
16 
17  *---------------------------------------------------------------------------*/
18 
19 
20 #include <nitro.h>
21 
22 #include "../include/card_rom.h"
23 
24 
25 // Use MI_CpuCopy, which uses a larger size, only in LIMITED mode to prevent it from being linked in a NITRO environment.
26 #if defined(SDK_TWLLTD)
27 #define CARDi_CopyMemory MI_CpuCopy
28 #else
29 #define CARDi_CopyMemory MI_CpuCopy8
30 #endif
31 
32 
33 // Enable this flag when making HMAC-SHA1 calculation high-speed
34 #if defined(SDK_TWL)
35 #define CARD_SUPPORT_SHA1OPTIMIZE
36 #endif
37 
38 #include <twl/ltdmain_begin.h>
39 
40 /*---------------------------------------------------------------------------*/
41 /* Constants */
42 
43 // Constants determined to be appropriate by the Card library at present.
44 // Proper changes are acceptable depending on the result of the performance calculation.
45 static const u32    CARD_ROM_HASH_BLOCK_MAX = 4;
46 static const u32    CARD_ROM_HASH_SECTOR_MAX = 32;
47 
48 #if defined(CARD_SUPPORT_SHA1OPTIMIZE)
49 static SVCSHA1Context   sha1_ipad_def;
50 static SVCSHA1Context   sha1_opad_def;
51 #endif
52 
53 static u8 CARDiHmacKey[] ATTRIBUTE_ALIGN(4) =
54 {
55     0x21, 0x06, 0xc0, 0xde, 0xba, 0x98, 0xce, 0x3f,
56     0xa6, 0x92, 0xe3, 0x9d, 0x46, 0xf2, 0xed, 0x01,
57     0x76, 0xe3, 0xcc, 0x08, 0x56, 0x23, 0x63, 0xfa,
58     0xca, 0xd4, 0xec, 0xdf, 0x9a, 0x62, 0x78, 0x34,
59     0x8f, 0x6d, 0x63, 0x3c, 0xfe, 0x22, 0xca, 0x92,
60     0x20, 0x88, 0x97, 0x23, 0xd2, 0xcf, 0xae, 0xc2,
61     0x32, 0x67, 0x8d, 0xfe, 0xca, 0x83, 0x64, 0x98,
62     0xac, 0xfd, 0x3e, 0x37, 0x87, 0x46, 0x58, 0x24,
63 };
64 
65 /*---------------------------------------------------------------------------*/
66 /* Functions */
67 
68 /*---------------------------------------------------------------------------*
69   Name:         CARD_CalcRomHashBufferLength
70 
71   Description:  Gets the buffer size required in ROM hashing context.
72 
73   Arguments:    header: ROM header buffer that held the ROM information necessary for management
74 
75   Returns:      The buffer size required for the ROM hash context.
76  *---------------------------------------------------------------------------*/
CARD_CalcRomHashBufferLength(const CARDRomHeaderTWL * header)77 u32 CARD_CalcRomHashBufferLength(const CARDRomHeaderTWL *header)
78 {
79     u32     length = 0;
80     length += MATH_ROUNDUP(header->digest_tabel2.length, 32);
81     length += MATH_ROUNDUP(header->digest_table1_size * CARD_ROM_HASH_SECTOR_MAX, 32);
82     length += MATH_ROUNDUP(MATH_ROUNDUP(CARD_ROM_HASH_SIZE * header->digest_table2_sectors, CARD_ROM_PAGE_SIZE * 3) * CARD_ROM_HASH_BLOCK_MAX, 32);
83     length += MATH_ROUNDUP(sizeof(CARDRomHashSector) * CARD_ROM_HASH_SECTOR_MAX, 32);
84     length += MATH_ROUNDUP(sizeof(CARDRomHashBlock) * CARD_ROM_HASH_BLOCK_MAX, 32);
85     return length;
86 }
87 
88 /*---------------------------------------------------------------------------*
89   Name:         CARDi_CompareHash
90 
91   Description:  Validity check using hashing (HMAC-SHA1)
92 
93   Arguments:    hash: Hash value to be used as the standard for comparison
94                 src: Image to be checked
95                 len: Size of the subject
96 
97   Returns:      None.
98  *---------------------------------------------------------------------------*/
CARDi_CompareHash(const void * hash,void * buffer,u32 length)99 static void CARDi_CompareHash(const void *hash, void *buffer, u32 length)
100 {
101 #if defined(CARD_SUPPORT_SHA1OPTIMIZE)
102     // Copy the entire structure of the ipad/opad that was calculated in advance
103     u8      tmphash[CARD_ROM_HASH_SIZE];
104     const u32  *h1 = (const u32 *)hash;
105     const u32  *h2 = (const u32 *)tmphash;
106     u32         dif = 0;
107     SVCSHA1Context  tmpcxt;
108     CARDi_CopyMemory(&sha1_ipad_def, &tmpcxt, sizeof(tmpcxt));
109     SVC_SHA1Update(&tmpcxt, buffer, length);
110     SVC_SHA1GetHash(&tmpcxt, tmphash);
111     CARDi_CopyMemory(&sha1_opad_def, &tmpcxt, sizeof(tmpcxt));
112     SVC_SHA1Update(&tmpcxt, tmphash, sizeof(tmphash));
113     SVC_SHA1GetHash(&tmpcxt, tmphash);
114     dif |= (h1[0] ^ h2[0]);
115     dif |= (h1[1] ^ h2[1]);
116     dif |= (h1[2] ^ h2[2]);
117     dif |= (h1[3] ^ h2[3]);
118     dif |= (h1[4] ^ h2[4]);
119     if (dif != 0)
120 #else
121     u8      tmphash[CARD_ROM_HASH_SIZE];
122 #ifdef SDK_TWL
123     if (OS_IsRunOnTwl())
124     {
125         SVC_CalcHMACSHA1(tmphash, buffer, length, CARDiHmacKey, sizeof(CARDiHmacKey));
126     }
127     else
128 #endif
129     {
130         MATH_CalcHMACSHA1(tmphash, buffer, length, CARDiHmacKey, sizeof(CARDiHmacKey));
131     }
132     if (MI_CpuComp8(hash, tmphash, sizeof(tmphash)) != 0)
133 #endif
134     {
135         // If the card is removed and shutdown processing is delayed, only output a warning and clear data here without forcibly stopping program execution
136         //
137         if ((OS_GetBootType() == OS_BOOTTYPE_ROM) && CARD_IsPulledOut())
138         {
139             OS_TWarning("ROM-hash comparation error. (CARD removal)\n");
140             MI_CpuFill(buffer, 0, length);
141         }
142         else
143         {
144             OS_TPanic("ROM-hash comparation error!\n");
145         }
146     }
147 }
148 
149 /*---------------------------------------------------------------------------*
150   Name:         CARDi_GetHashSectorIndex
151 
152   Description:  Gets sector number where the specified ROM offset belongs.
153 
154   Arguments:    context: CARDRomHashContext structure
155                 offset: ROM offset
156 
157   Returns:      Sector number where the specified ROM offset belongs.
158  *---------------------------------------------------------------------------*/
CARDi_GetHashSectorIndex(const CARDRomHashContext * context,u32 offset)159 SDK_INLINE u32 CARDi_GetHashSectorIndex(const CARDRomHashContext *context, u32 offset)
160 {
161     offset -= context->area_ntr.offset;
162     if (offset >= context->area_ntr.length)
163     {
164         offset += (context->area_ntr.offset - context->area_ltd.offset);
165         if (offset < context->area_ltd.length)
166         {
167             offset += context->area_ntr.length;
168         }
169         else
170         {
171             OS_TPanic("specified ROM address is outof-range.(unsafe without secure hash)\n");
172         }
173     }
174     return offset / context->bytes_per_sector;
175 }
176 
177 /*---------------------------------------------------------------------------*
178   Name:         CARDi_StartLoading
179 
180   Description:  Starts transfers for only one sector waiting for media load.
181 
182   Arguments:    context: CARDRomHashContext structure
183 
184   Returns:      None.
185  *---------------------------------------------------------------------------*/
CARDi_StartLoading(CARDRomHashContext * context)186 static void CARDi_StartLoading(CARDRomHashContext *context)
187 {
188     // Start the asynchronous process here if not already running
189     void   *buffer = NULL;
190     u32     offset = 0;
191     u32     length = 0;
192     OSIntrMode  bak_cpsr = OS_DisableInterrupts();
193     if (context->recent_load == NULL)
194     {
195         if (context->loading_block)
196         {
197             // A larger buffer has been allocated than actually needed so that asynchronous DMA transfer is possible.
198             // Align offset of the reference target to the table position that is actually required.
199             CARDRomHashBlock   *block = context->loading_block;
200             u32                 pos = block->offset;
201             u32                 mod = (pos & (CARD_ROM_PAGE_SIZE - 1UL));
202             block->hash = &block->hash_aligned[mod];
203             context->recent_load = block;
204             buffer = block->hash_aligned;
205             offset = pos - mod;
206             length = MATH_ROUNDUP(mod + CARD_ROM_HASH_SIZE * context->sectors_per_block, CARD_ROM_PAGE_SIZE);
207         }
208         else if (context->loading_sector)
209         {
210             CARDRomHashSector  *sector = context->loading_sector;
211             context->recent_load = sector;
212             buffer = sector->image;
213             offset = sector->offset;
214             length = context->bytes_per_sector;
215         }
216     }
217     (void)OS_RestoreInterrupts(bak_cpsr);
218     // Perform synchronous transfer here if the asynchronous transfer is not supported or currently unusable
219     if (buffer != NULL)
220     {
221         if ((*context->ReadAsync)(context->userdata, buffer, offset, length) == 0)
222         {
223             (void)(*context->ReadSync)(context->userdata, buffer, offset, length);
224             CARD_NotifyRomHashReadAsync(context);
225         }
226     }
227 }
228 
229 /*---------------------------------------------------------------------------*
230   Name:         CARD_NotifyRomHashReadAsync
231 
232   Description:  Notifies completion of the device asynchronous read.
233 
234   Arguments:    context: CARDRomHashContext structure
235 
236   Returns:      None.
237  *---------------------------------------------------------------------------*/
CARD_NotifyRomHashReadAsync(CARDRomHashContext * context)238 void CARD_NotifyRomHashReadAsync(CARDRomHashContext *context)
239 {
240     OSIntrMode  bak_cpsr = OS_DisableInterrupts();
241     // Move list from "waiting to load" to "waiting to verify"
242     if (context->recent_load == context->loading_sector)
243     {
244         CARDRomHashSector  *sector = context->loading_sector;
245         context->loading_sector = sector->next;
246         sector->next = context->loaded_sector;
247         context->loaded_sector = sector;
248     }
249     else
250     {
251         CARDRomHashBlock   *block = context->loading_block;
252         context->loading_block = block->next;
253         block->next = context->loaded_block;
254         context->loaded_block = block;
255     }
256     context->recent_load = NULL;
257     (void)OS_RestoreInterrupts(bak_cpsr);
258     // Notify wait thread that "waiting to verify" list was added
259     OS_WakeupThreadDirect(context->loader);
260     // Start transfer of next "waiting to load" list, if there is one
261     CARDi_StartLoading(context);
262 }
263 
264 /*---------------------------------------------------------------------------*
265   Name:         CARDi_TouchRomHashBlock
266 
267   Description:  Loads hash block data that includes the specified offset.
268                 Does nothing if already loaded.
269 
270   Arguments:    context: CARDRomHashContext structure
271                 sector: Sector to access
272 
273   Returns:      Corresponding hash block data.
274  *---------------------------------------------------------------------------*/
CARDi_TouchRomHashBlock(CARDRomHashContext * context,u32 sector)275 static CARDRomHashBlock* CARDi_TouchRomHashBlock(CARDRomHashContext *context, u32 sector)
276 {
277     // Note that the block region is handled by linking the NTR and LTD regions.
278     // (Do not insert any extra padding or alignment.)
279     u32     index = sector / context->sectors_per_block;
280     // Determine whether the corresponding block data is already loaded
281     CARDRomHashBlock  **pp = &context->valid_block;
282     CARDRomHashBlock   *block = NULL;
283     for (;; pp = &(*pp)->next)
284     {
285         block = *pp;
286         if (block->index == index)
287         {
288             // Move the touched block data to the top of the list
289             *pp = block->next;
290             block->next = context->valid_block;
291             context->valid_block = block;
292             break;
293         }
294         // If not loaded, destroy the oldest block data and reload
295         else if (block->next == NULL)
296         {
297             // Move to end of "waiting to load" list
298             OSIntrMode  bak_cpsr = OS_DisableInterrupts();
299             CARDRomHashBlock  **tail = pp;
300             CARDRomHashBlock   *loading = block;
301             // Do nothing if the same block is already waiting to load or waiting to verify
302             for (pp = &context->loaded_block; *pp; pp = &(*pp)->next)
303             {
304                 if ((*pp)->index == index)
305                 {
306                     block = (*pp);
307                     loading = NULL;
308                     break;
309                 }
310             }
311             if (loading)
312             {
313                 for (pp = &context->loading_block; *pp; pp = &(*pp)->next)
314                 {
315                     if ((*pp)->index == index)
316                     {
317                         block = (*pp);
318                         loading = NULL;
319                         break;
320                     }
321                 }
322                 if (loading)
323                 {
324                     *tail = NULL;
325                     *pp = loading;
326                     loading->index = index;
327                     loading->offset = context->sector_hash.offset + index * (CARD_ROM_HASH_SIZE * context->sectors_per_block);
328                 }
329             }
330             (void)OS_RestoreInterrupts(bak_cpsr);
331             if (loading)
332             {
333                 // Check the start timing of the asynchronous load
334                 CARDi_StartLoading(context);
335             }
336             break;
337         }
338     }
339     return block;
340 }
341 
342 /*---------------------------------------------------------------------------*
343   Name:         CARDi_TouchRomHashSector
344 
345   Description:  Loads the specified sector on the block.
346                 Does nothing if already loaded.
347 
348   Arguments:    context: CARDRomHashContext structure
349                 block: CARDRomHashBlock structure
350                 offset: ROM offset to access
351 
352   Returns:      Corresponding sector image or NULL.
353  *---------------------------------------------------------------------------*/
CARDi_TouchRomHashSector(CARDRomHashContext * context,u32 offset)354 static void* CARDi_TouchRomHashSector(CARDRomHashContext *context, u32 offset)
355 {
356     void               *image = NULL;
357     CARDRomHashSector **pp = &context->valid_sector;
358     CARDRomHashSector  *sector = NULL;
359     u32                 index = CARDi_GetHashSectorIndex(context, offset);
360     for (pp = &context->valid_sector; ; pp = &(*pp)->next)
361     {
362         // If the corresponding sector data is already loaded, returns that image
363         if ((*pp)->index == index)
364         {
365             // Move the touched sector data to the top of the list
366             sector = *pp;
367             *pp = (*pp)->next;
368             sector->next = context->valid_sector;
369             context->valid_sector = sector;
370             image = sector->image;
371             break;
372         }
373         // If not loaded, destroy the oldest sector data and reload
374         else if ((*pp)->next == NULL)
375         {
376             // Move to end of "waiting to load" list
377             OSIntrMode  bak_cpsr = OS_DisableInterrupts();
378             CARDRomHashSector  *loading = *pp;
379             *pp = NULL;
380             for (pp = &context->loading_sector; *pp; pp = &(*pp)->next)
381             {
382             }
383             *pp = loading;
384             loading->index = index;
385             loading->offset = MATH_ROUNDDOWN(offset, context->bytes_per_sector);
386             (void)OS_RestoreInterrupts(bak_cpsr);
387             // Search for the block that will be required for validity test
388             (void)CARDi_TouchRomHashBlock(context, index);
389             // Check the start timing of the asynchronous load
390             CARDi_StartLoading(context);
391             break;
392         }
393     }
394     return image;
395 }
396 
397 /*---------------------------------------------------------------------------*
398   Name:         CARD_InitRomHashContext
399 
400   Description:  Initializes the ROM hashing context.
401 
402   Arguments:    context: CARDRomHashContext structure that must be initialized
403                 header: ROM header buffer that held the ROM information necessary for management
404                            (No need to keep this because this is referenced only within the function)
405                 buffer: Buffer for management information used in the context
406                 length: Size of management information buffer
407                            (Can be calculated using the CARD_CalcRomHashBufferLength function)
408                 sync: Device synchronous read callback function
409                 async: Device asynchronous read callback function (if not supported, NULL)
410                 userdata: Arbitrary user-defined function passed to the device read callback
411 
412   Returns:      None.
413  *---------------------------------------------------------------------------*/
CARD_InitRomHashContext(CARDRomHashContext * context,const CARDRomHeaderTWL * header,void * buffer,u32 length,MIDeviceReadFunction sync,MIDeviceReadFunction async,void * userdata)414 void CARD_InitRomHashContext(CARDRomHashContext *context, const CARDRomHeaderTWL *header,
415                              void *buffer, u32 length,
416                              MIDeviceReadFunction sync, MIDeviceReadFunction async,
417                              void *userdata)
418 {
419     // Constant differs by the build
420     const u32   bytes_per_sector = header->digest_table1_size;
421     const u32   sectors_per_block = header->digest_table2_sectors;
422     const u32   master_hash_size = header->digest_tabel2.length;
423 
424     // Calculate whether the required variable length memory has been allocated
425     u8     *lo = (u8 *)MATH_ROUNDUP((u32)buffer, 32);
426     u8     *hi = (u8 *)MATH_ROUNDDOWN((u32)&lo[length], 32);
427     u8     *cur = lo;
428     context->master_hash = (u8 *)cur;
429     cur += MATH_ROUNDUP(master_hash_size, 32);
430     context->images = (u8 *)cur;
431     cur += MATH_ROUNDUP(bytes_per_sector * CARD_ROM_HASH_SECTOR_MAX, 32);
432     context->hashes = (u8 *)cur;
433     cur += MATH_ROUNDUP(MATH_ROUNDUP(CARD_ROM_HASH_SIZE * sectors_per_block, CARD_ROM_PAGE_SIZE * 3) * CARD_ROM_HASH_BLOCK_MAX, 32);
434     context->sectors = (CARDRomHashSector *)cur;
435     cur += MATH_ROUNDUP(sizeof(*context->sectors) * CARD_ROM_HASH_SECTOR_MAX, 32);
436     context->blocks = (CARDRomHashBlock *)cur;
437     cur += MATH_ROUNDUP(sizeof(*context->blocks) * CARD_ROM_HASH_BLOCK_MAX, 32);
438     if (cur > hi)
439     {
440         OS_TPanic("cannot allocate memory for ROM-hash from ARENA");
441     }
442     else
443     {
444         // Initialize the basic settings if the memory can be allocated
445         MI_CpuClear32(lo, (u32)(cur - lo));
446         context->area_ntr = header->digest_area_ntr;
447         context->area_ltd = header->digest_area_ltd;
448         context->sector_hash = header->digest_tabel1;
449         context->block_hash = header->digest_tabel2;
450         context->bytes_per_sector = bytes_per_sector;
451         context->sectors_per_block = sectors_per_block;
452         context->block_max = CARD_ROM_HASH_BLOCK_MAX;
453         context->sector_max = CARD_ROM_HASH_SECTOR_MAX;
454         // Initialize the device reader
455         context->ReadSync = sync;
456         context->ReadAsync = async;
457         context->userdata = userdata;
458 #if defined(CARD_SUPPORT_SHA1OPTIMIZE)
459         // Calculates the ipad/opad for hash calculation in advance
460         {
461             u8      ipad[MATH_HASH_BLOCK_SIZE];
462             u8      opad[MATH_HASH_BLOCK_SIZE];
463             int     i;
464             for (i = 0; i < MATH_HASH_BLOCK_SIZE; ++i)
465             {
466                 ipad[i] = (u8)(CARDiHmacKey[i] ^ 0x36);
467                 opad[i] = (u8)(CARDiHmacKey[i] ^ 0x5c);
468             }
469             SVC_SHA1Init(&sha1_ipad_def);
470             SVC_SHA1Init(&sha1_opad_def);
471             SVC_SHA1Update(&sha1_ipad_def, ipad, sizeof(ipad));
472             SVC_SHA1Update(&sha1_opad_def, opad, sizeof(opad));
473         }
474 #endif
475         // Determine validity by loading the master hash
476         (void)(*context->ReadSync)(context->userdata, context->master_hash, context->block_hash.offset, context->block_hash.length);
477         CARDi_CompareHash(header->digest_tabel2_digest, context->master_hash, context->block_hash.length);
478         // Initialize the sector data list
479         {
480             CARDRomHashSector  *sectors = context->sectors;
481             int     sector_index = 0;
482             int     i;
483             for (i = 0; i < context->sector_max; ++i)
484             {
485                 sectors[i].next = &sectors[i - 1];
486                 sectors[i].image = &context->images[i * context->bytes_per_sector];
487                 sectors[i].index = 0xFFFFFFFF;
488             }
489             context->sectors[0].next = NULL;
490             context->valid_sector = &context->sectors[context->sector_max - 1];
491             context->loading_sector = NULL;
492             context->loaded_sector = NULL;
493         }
494         // Initialize the block data list
495         {
496             CARDRomHashBlock   *blocks = context->blocks;
497             const u32           unit = MATH_ROUNDUP(CARD_ROM_HASH_SIZE * sectors_per_block, CARD_ROM_PAGE_SIZE * 3);
498             int     i;
499             for (i = 0; i < context->block_max; ++i)
500             {
501                 blocks[i].next = &blocks[i + 1];
502                 blocks[i].index = 0xFFFFFFFF;
503                 blocks[i].hash_aligned = &context->hashes[i * unit];
504             }
505             context->valid_block = &context->blocks[0];
506             context->blocks[context->block_max - 1].next = NULL;
507             context->loading_block = NULL;
508             context->loaded_block = NULL;
509         }
510     }
511 }
512 
513 /*---------------------------------------------------------------------------*
514   Name:         CARDi_ReadRomHashImageDirect
515 
516   Description:  Directly copies to the transfer destination without caching to the hash context.
517 
518   Arguments:    context: CARDRomHashContext structure
519                 buffer: Buffer to transfer to (must be 4-byte aligned)
520                 offset: ROM offset to access
521                 length: Transfer size
522 
523   Returns:      None.
524  *---------------------------------------------------------------------------*/
CARDi_ReadRomHashImageDirect(CARDRomHashContext * context,void * buffer,u32 offset,u32 length)525 static void CARDi_ReadRomHashImageDirect(CARDRomHashContext *context, void *buffer, u32 offset, u32 length)
526 {
527     const u32   sectunit = context->bytes_per_sector;
528     const u32   blckunit = context->sectors_per_block;
529     u32         position = offset;
530     u32         end = length + offset;
531     u32         sector = CARDi_GetHashSectorIndex(context, position);
532     while (position < end)
533     {
534         // Synchronously load images of minimum units that can be obtained this time
535         u32     available = (u32)(*context->ReadSync)(context->userdata, buffer, position, end - position);
536         // Access blocks in advance that may be needed in verification this time
537         (void)CARDi_TouchRomHashBlock(context, sector);
538         // Request load preparation for the portion that will be required next
539         if (context->ReadAsync && (position + available < end))
540         {
541             (void)(*context->ReadAsync)(context->userdata, NULL, position + available, end - (position + available));
542         }
543         // Test validity of obtained image
544         while (available >= sectunit)
545         {
546             CARDRomHashBlock   *block = CARDi_TouchRomHashBlock(context, sector);
547             u32                 slot = sector - block->index * blckunit;
548             while ((slot < blckunit) && (available >= sectunit))
549             {
550                 // Verify the hash table in block units here if required
551                 if (block != context->valid_block)
552                 {
553                     OSIntrMode  bak_cpsr = OS_DisableInterrupts();
554                     while (context->loading_block)
555                     {
556                         OS_SleepThread(NULL);
557                     }
558                     if (block == context->loaded_block)
559                     {
560                         context->loaded_block = block->next;
561                     }
562                     (void)OS_RestoreInterrupts(bak_cpsr);
563                     CARDi_CompareHash(&context->master_hash[block->index * CARD_ROM_HASH_SIZE],
564                                       block->hash, CARD_ROM_HASH_SIZE * blckunit);
565                     block->next = context->valid_block;
566                     context->valid_block = block;
567                 }
568                 // Calculate the image hash
569                 CARDi_CompareHash(&block->hash[slot * CARD_ROM_HASH_SIZE], buffer, sectunit);
570                 position += sectunit;
571                 available -= sectunit;
572                 buffer = ((u8 *)buffer) + sectunit;
573                 slot += 1;
574                 sector += 1;
575             }
576         }
577     }
578 }
579 
580 /*---------------------------------------------------------------------------*
581   Name:         CARDi_ReadRomHashImageCaching
582 
583   Description:  Directly copies to the transfer destination while caching to the hash context.
584 
585   Arguments:    context: CARDRomHashContext structure
586                 buffer: Buffer to transfer to
587                 offset: ROM offset to access
588                 length: Transfer size
589 
590   Returns:      None.
591  *---------------------------------------------------------------------------*/
CARDi_ReadRomHashImageCaching(CARDRomHashContext * context,void * buffer,u32 offset,u32 length)592 static void CARDi_ReadRomHashImageCaching(CARDRomHashContext *context, void *buffer, u32 offset, u32 length)
593 {
594     while (length > 0)
595     {
596         // Determine whether the next sector has undergone validity test
597         void   *image = CARDi_TouchRomHashSector(context, offset);
598         if (image)
599         {
600             // If verified, copy memory as is and go to next
601             u32     max = context->bytes_per_sector;
602             u32     mod = offset - MATH_ROUNDDOWN(offset, max);
603             u32     len = (u32)MATH_MIN(length, (max - mod));
604             CARDi_CopyMemory((u8*)image + mod, buffer, len);
605             buffer = (u8 *)buffer + len;
606             length -= len;
607             offset += len;
608         }
609         else
610         {
611             // If no hit was found, request to load the sector in advance as much as the capacity allows so that all unverified lists can be processed together.
612             // Related block hashes are also automatically read in advance, and the asynchronous transfer has already started if this is possible.
613             //
614             //
615             u32     hit = MATH_ROUNDDOWN(offset, context->bytes_per_sector);
616             while (context->valid_sector && context->valid_block)
617             {
618                 hit += context->bytes_per_sector;
619                 if (hit >= offset + length)
620                 {
621                     break;
622                 }
623                 (void)CARDi_TouchRomHashSector(context, hit);
624             }
625             for (;;)
626             {
627                 // Idle until a verification wait list of a load-complete state is generated
628                 OSIntrMode          bak_cpsr = OS_DisableInterrupts();
629                 CARDRomHashBlock   *block = NULL;
630                 CARDRomHashSector  *sector = NULL;
631                 while ((context->loading_sector && !context->loaded_sector) ||
632                         (context->loading_block && !context->loaded_block))
633                 {
634                     OS_SleepThread(NULL);
635                 }
636                 // Process the block before the sector considering dependency relationships
637                 block = context->loaded_block;
638                 if (block)
639                 {
640                     context->loaded_block = block->next;
641                 }
642                 else
643                 {
644                     sector = context->loaded_sector;
645                     if (sector)
646                     {
647                         context->loaded_sector = sector->next;
648                     }
649                 }
650                 (void)OS_RestoreInterrupts(bak_cpsr);
651                 // Compare with the higher table and verify by calculating block or sector hash
652                 if (block)
653                 {
654                     u32     len = CARD_ROM_HASH_SIZE * context->sectors_per_block;
655                     CARDi_CompareHash(&context->master_hash[block->index * CARD_ROM_HASH_SIZE], block->hash, len);
656                     // Move the touched block data to the top of the list
657                     block->next = context->valid_block;
658                     context->valid_block = block;
659                 }
660                 else if (sector)
661                 {
662                     CARDRomHashBlock   *block = CARDi_TouchRomHashBlock(context, sector->index);
663                     u32                 slot = sector->index - block->index * context->sectors_per_block;
664                     CARDi_CompareHash(&block->hash[slot * CARD_ROM_HASH_SIZE], sector->image, context->bytes_per_sector);
665                     // Move the touched sector data to the top of the list
666                     sector->next = context->valid_sector;
667                     context->valid_sector = sector;
668                 }
669                 // If there is nothing that must be asynchronously verified, end this loop
670                 else
671                 {
672                     break;
673                 }
674             }
675         }
676     }
677 }
678 
679 /*---------------------------------------------------------------------------*
680   Name:         CARD_ReadRomHashImage
681 
682   Description:  Reads the hash-verified ROM image from the specified offset
683 
684   Arguments:    context: CARDRomHashContext structure
685                 offset: ROM offset to access
686 
687   Returns:      None.
688  *---------------------------------------------------------------------------*/
CARD_ReadRomHashImage(CARDRomHashContext * context,void * buffer,u32 offset,u32 length)689 void CARD_ReadRomHashImage(CARDRomHashContext *context, void *buffer, u32 offset, u32 length)
690 {
691     // Initialize notification destination from the DMA
692     context->loader = OS_GetCurrentThread();
693     context->recent_load = NULL;
694     // When conducting a large-scale read where at least half of the sector cache could be lost at once, employ a direct transfer mode to speed up processing and protect the cache
695     //
696     if ((length >= context->bytes_per_sector * (CARD_ROM_HASH_SECTOR_MAX / 2))
697         // TODO:
698         //   For the time being, do not use the direct transfer mode with a card boot.
699         //   (Particular attention to the word boundary alignment of the transfer source and destination is not necessary. However, performance may actually be worse for synchronous ROM transfers, so conditions should be added when necessary.)
700         //
701         //
702         && (OS_GetBootType() != OS_BOOTTYPE_ROM))
703     {
704         // Split the leading end and trailing end portions without aligning the sector boundaries
705         const u32   sectmask = (u32)(context->bytes_per_sector - 1UL);
706         const u32   headlen = (u32)((context->bytes_per_sector - offset) & sectmask);
707         const u32   bodylen = (u32)((length - headlen) & ~sectmask);
708         // Leading End Portion
709         if (headlen > 0)
710         {
711             CARDi_ReadRomHashImageCaching(context, buffer, offset, headlen);
712             offset += headlen;
713             length -= headlen;
714             buffer = ((u8 *)buffer) + headlen;
715         }
716         // Middle Portion
717         if (bodylen > 0)
718         {
719             CARDi_ReadRomHashImageDirect(context, buffer, offset, bodylen);
720             offset += bodylen;
721             length -= bodylen;
722             buffer = ((u8 *)buffer) + bodylen;
723         }
724     }
725     // Process the remainder that could not be used with the direct transfer mode
726     CARDi_ReadRomHashImageCaching(context, buffer, offset, length);
727 }
728 #include <twl/ltdmain_end.h>
729