本番用MI/FATFSをFSに統合

git-svn-id: file:///Users/lillianskinner/Downloads/platinum/twl/TwlIPL/trunk@275 b08762b0-b915-fc4b-9d8c-17b2551a87ff
This commit is contained in:
yutaka 2007-11-28 00:08:02 +00:00
parent 7068897306
commit 042bbc08d3
5 changed files with 815 additions and 0 deletions

View File

@ -0,0 +1,54 @@
#! make -f
#----------------------------------------------------------------------------
# Project: TwlFirm - libraries - fs
# File: Makefile
#
# Copyright 2007 Nintendo. All rights reserved.
#
# These coded instructions, statements, and computer programs contain
# proprietary information of Nintendo of America Inc. and/or Nintendo
# Company Ltd., and are protected by Federal copyright law. They may
# not be disclosed to third parties or copied or duplicated in any form,
# in whole or in part, without the prior written consent of Nintendo.
#
# $Date:: $
# $Rev:$
# $Author:$
#----------------------------------------------------------------------------
SUBDIRS =
SUBMAKES =
#----------------------------------------------------------------------------
# build ARM & THUMB libraries
TWL_CODEGEN_ALL ?= TRUE
# Codegen for sub processer
TWL_PROC = ARM7
LINCLUDES = \
$(TWLSDK_ROOT)/build/libraries/fatfs/ARM7.TWL/include \
$(TWLSDK_ROOT)/build/libraries/fatfs/ARM7.TWL/include/twl/fatfs/ARM7
SRCS = fs_firm.c
TARGET_LIB = libfs_sp$(FIRM_LIBSUFFIX).a
#----------------------------------------------------------------------------
include $(TWLIPL_ROOT)/build/buildtools/commondefs
INSTALL_TARGETS = $(TARGETS)
INSTALL_DIR = $(FIRM_INSTALL_LIBDIR)
#----------------------------------------------------------------------------
do-build: $(TARGETS)
include $(TWLIPL_ROOT)/build/buildtools/modulerules
#===== End of Makefile =====

View File

@ -0,0 +1,320 @@
/*---------------------------------------------------------------------------*
Project: TwlIPL - libraries - fs
File: fs_firm.c
Copyright 2007 Nintendo. All rights reserved.
These coded instructions, statements, and computer programs contain
proprietary information of Nintendo of America Inc. and/or Nintendo
Company Ltd., and are protected by Federal copyright law. They may
not be disclosed to third parties or copied or duplicated in any form,
in whole or in part, without the prior written consent of Nintendo.
$Date:: $
$Rev$
$Author$
*---------------------------------------------------------------------------*/
#include <symbols.h>
#include <firm.h>
#include <rtfs.h>
#define WORKAROUND_NAND_2KB_BUG
#define FS_HEADER_AUTH_SIZE 0xe00
#define MODULE_ALIGNMENT 0x10 // 16バイト単位で読み込む
//#define MODULE_ALIGNMENT 0x200 // 512バイト単位で読み込む
#define RoundUpModuleSize(value) (((value) + MODULE_ALIGNMENT - 1) & -MODULE_ALIGNMENT)
static ROM_Header* const rh= (ROM_Header*)HW_TWL_ROM_HEADER_BUF;
static void ConvertPath( u16* dest, const char* src, u32 max)
{
dest[0] = 0;
{
const OSMountInfo *info;
int len;
// デバイス部分を取得
for (len = 0; src[len] && src[len] != ':'; len++)
{
if (len >= max) // もっと手前で止めても良い?
{
OS_TPanic("%s: Cannot detect ':' in %d charactors.\n", __func__, len);
}
}
// ドライブ名の解決
for (info = OS_GetMountInfo(); *info->drive; ++info)
{
if ((STD_CompareNString(src, info->archiveName, len) == 0) &&
(info->archiveName[len] == 0))
{
if (info->target != OS_MOUNT_TGT_ROOT) // 多重マウント未対応
{
return;
}
dest[0] = (u16)*info->drive;
break;
}
}
max --;
dest++;
src += len;
}
{
int len;
// 残りunicode化 (ASCIIのみ)
for (len = 0; len < max && src[len]; len++)
{
if (src[len] == '/')
{
dest[len] = L'\\';
}
#ifndef SDK_FINALROM
else if (src[len] & 0x80)
{
OS_TPanic("%s: Multi-byte charactor was detected (0x%02X).\n", __func__, src[len]);
}
#endif
else
{
dest[len] = (u16)src[len];
}
}
if (len < max)
{
dest[len] = 0;
}
else // l type (ensure to terminate zero)
{
dest[max-1] = 0;
}
}
}
/*---------------------------------------------------------------------------*
Name: FS_OpenSrl
Description: open srl file named in HW_TWL_FS_BOOT_SRL_PATH_BUF
Arguments: None
Returns: file discriptor
*---------------------------------------------------------------------------*/
int FS_OpenSrl( void )
{
const char *fspath = (char*)HW_TWL_FS_BOOT_SRL_PATH_BUF;
u16 fatpath[OS_MOUNT_PATH_LEN];
ConvertPath(fatpath, fspath, OS_MOUNT_PATH_LEN);
return FATFSi_rtfs_po_open((u8*)fatpath, 0, 0);
}
static void EnableAes( AESCounter* pCounter ) // ドライバAPIと置き換える
{
(void)pCounter;
}
static void DisableAes( void ) // ドライバAPIと置き換える
{
}
static void GetAesCounter( AESCounter* pCounter, u32 offset )
{
MI_CpuCopy32( rh->s.main_static_digest, pCounter, AES_BLOCK_SIZE );
AESi_AddCounter( pCounter, offset - rh->s.aes_target_rom_offset );
}
static u32 GetTransferSize( u32 offset, u32 size )
{
u32 aes_offset = rh->s.aes_target_rom_offset;
u32 aes_end = aes_offset + RoundUpModuleSize(rh->s.aes_target_size);
u32 end = offset + RoundUpModuleSize(size);
if ( rh->s.enable_aes )
{
if ( offset >= aes_offset && offset < aes_end )
{
AESCounter counter;
if ( end > aes_end )
{
size = aes_end - offset;
}
AESi_WaitKey();
if (rh->s.developer_encrypt)
{
AESi_LoadKey( AES_KEY_SLOT_C );
}
else
{
AESi_LoadKey( AES_KEY_SLOT_A );
}
GetAesCounter( &counter, offset );
EnableAes( &counter );
}
else
{
if ( offset < aes_offset && offset + size > aes_offset )
{
size = aes_offset - offset;
}
DisableAes();
}
}
else
{
DisableAes();
}
return size;
}
/*---------------------------------------------------------------------------*
Name: FS_LoadBuffer
Description: load data in file and pass to ARM9 via WRAM-B
Arguments: fd file discriptor to read
offset offset to start to read in bytes
size total length to read in bytes
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadBuffer( int fd, u32 offset, u32 size )
{
static int count = 0;
if (fd < 0)
{
return FALSE;
}
// seek
if ( FATFSi_rtfs_po_lseek(fd, (s32)offset, PSEEK_SET) < 0 )
{
return FALSE;
}
while ( size > 0 )
{
u8* dest = (u8*)HW_FIRM_LOAD_BUFFER_BASE + count * HW_FIRM_LOAD_BUFFER_UNIT_SIZE;
u32 unit = size < HW_FIRM_LOAD_BUFFER_UNIT_SIZE ? size : HW_FIRM_LOAD_BUFFER_UNIT_SIZE;
while ( MI_GetWramBankMaster_B(count) != MI_WRAM_ARM7 ) // wait to be ready
{
}
#ifdef WORKAROUND_NAND_2KB_BUG
{
u32 done;
for ( done = 0; done < unit; done += 2048 )
{
u8* d = dest + done;
u32 u = unit - done < 2048 ? unit - done : 2048;
if ( FATFSi_rtfs_po_read(fd, (u8*)d, (int)u) < 0 )
{
return FALSE;
}
}
}
#else
if ( FATFSi_rtfs_po_read(fd, (u8*)dest, (int)unit) < 0 )
{
return FALSE;
}
#endif
PXI_NotifyID( FIRM_PXI_ID_LOAD_PIRIOD );
count = (count + 1) % HW_FIRM_LOAD_BUFFER_UNIT_NUMS;
size -= unit;
}
return TRUE;
}
/*---------------------------------------------------------------------------*
Name: FS_LoadModule
Description: load data in file and pass to ARM9 via WRAM-B in view of AES
settings in the ROM header in HW_TWL_ROM_HEADER_BUF
Arguments: fd file discriptor to read
offset offset to start to read in bytes
length total length to read in bytes
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadModule( int fd, u32 offset, u32 size )
{
size = RoundUpModuleSize( size );
while ( size > 0 )
{
u32 unit = GetTransferSize( offset, size );
if ( !FS_LoadBuffer( fd, offset, unit ) )
{
return FALSE;
}
offset += unit;
size -= unit;
}
return TRUE;
}
/*---------------------------------------------------------------------------*
Name: FS_LoadHeader
Description: load ROM header in the head of file and pass to ARM9 via WRAM-B
Arguments: fd file discriptor to read
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadHeader( int fd )
{
if (fd < 0)
{
return FALSE;
}
DisableAes();
if ( !FS_LoadBuffer(fd, 0, FS_HEADER_AUTH_SIZE) ||
!FS_LoadBuffer(fd, FS_HEADER_AUTH_SIZE, HW_TWL_ROM_HEADER_BUF_SIZE - FS_HEADER_AUTH_SIZE) )
{
return FALSE;
}
return TRUE;
}
/*---------------------------------------------------------------------------*
Name: FS_LoadStatic
Description: load static regions in file and pass to ARM9 via WRAM-B
specified by ROM header at HW_TWL_ROM_HEADER_BUF
Arguments: fd file discriptor to read
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadStatic( int fd )
{
if ( rh->s.main_size > 0 )
{
if ( !FS_LoadModule( fd, rh->s.main_rom_offset, rh->s.main_size ) )
{
return FALSE;
}
}
if ( rh->s.sub_size > 0 )
{
if ( !FS_LoadModule( fd, rh->s.sub_rom_offset, rh->s.sub_size ) )
{
return FALSE;
}
}
if ( rh->s.main_ltd_size > 0 )
{
if ( !FS_LoadModule( fd, rh->s.main_ltd_rom_offset, rh->s.main_ltd_size ) )
{
return FALSE;
}
}
if ( rh->s.sub_ltd_size > 0 )
{
if ( !FS_LoadModule( fd, rh->s.sub_ltd_rom_offset, rh->s.sub_ltd_size ) )
{
return FALSE;
}
}
return TRUE;
}

View File

@ -0,0 +1,47 @@
#! make -f
#----------------------------------------------------------------------------
# Project: TwlFirm - libraries - fs
# File: Makefile
#
# Copyright 2007 Nintendo. All rights reserved.
#
# These coded instructions, statements, and computer programs contain
# proprietary information of Nintendo of America Inc. and/or Nintendo
# Company Ltd., and are protected by Federal copyright law. They may
# not be disclosed to third parties or copied or duplicated in any form,
# in whole or in part, without the prior written consent of Nintendo.
#
# $Date:: $
# $Rev:$
# $Author:$
#----------------------------------------------------------------------------
SUBDIRS =
SUBMAKES =
#----------------------------------------------------------------------------
LINCLUDES = $(ES_ROOT)/include
SRCS = fs_firm.c
TARGET_LIB = libfs$(FIRM_LIBSUFFIX).a
#----------------------------------------------------------------------------
include $(TWLIPL_ROOT)/build/buildtools/commondefs
include $(TWLSDK_ROOT)/add-ins/es/commondefs.es
INSTALL_TARGETS = $(TARGETS)
INSTALL_DIR = $(FIRM_INSTALL_LIBDIR)
#----------------------------------------------------------------------------
do-build: $(TARGETS)
include $(TWLIPL_ROOT)/build/buildtools/modulerules
#===== End of Makefile =====

View File

@ -0,0 +1,363 @@
/*---------------------------------------------------------------------------*
Project: TwlIPL - libraries - fs
File: fs_firm.c
Copyright 2007 Nintendo. All rights reserved.
These coded instructions, statements, and computer programs contain
proprietary information of Nintendo of America Inc. and/or Nintendo
Company Ltd., and are protected by Federal copyright law. They may
not be disclosed to third parties or copied or duplicated in any form,
in whole or in part, without the prior written consent of Nintendo.
$Date:: $
$Rev$
$Author$
*---------------------------------------------------------------------------*/
#include <firm.h>
#include <estypes.h>
#include <es.h>
#define FS_HEADER_AUTH_SIZE 0xe00
#define CONTENT_INDEX_SRL 0
#define HASH_UNIT 0x1000
static ROM_Header* const rh = (ROM_Header*)HW_TWL_ROM_HEADER_BUF;
static u8 currentKey[ SVC_SHA1_BLOCK_SIZE ];
static const u8 defaultKey[ SVC_SHA1_BLOCK_SIZE ] =
{
0x21, 0x06, 0xc0, 0xde,
0xba, 0x98, 0xce, 0x3f,
0xa6, 0x92, 0xe3, 0x9d,
0x46, 0xf2, 0xed, 0x01,
0x76, 0xe3, 0xcc, 0x08,
0x56, 0x23, 0x63, 0xfa,
0xca, 0xd4, 0xec, 0xdf,
0x9a, 0x62, 0x78, 0x34,
0x8f, 0x6d, 0x63, 0x3c,
0xfe, 0x22, 0xca, 0x92,
0x20, 0x88, 0x97, 0x23,
0xd2, 0xcf, 0xae, 0xc2,
0x32, 0x67, 0x8d, 0xfe,
0xca, 0x83, 0x64, 0x98,
0xac, 0xfd, 0x3e, 0x37,
0x87, 0x46, 0x58, 0x24,
};
/*---------------------------------------------------------------------------*
Name: FS_ResolveSrl
Description: resolve srl filename and store to HW_TWL_FS_BOOT_SRL_PATH_BUF
Arguments: titleId title id for srl file
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_ResolveSrl( u64 titleId )
{
if ( ES_ERR_OK != ES_InitLib() ||
ES_ERR_OK != ES_GetContentPath(titleId, CONTENT_INDEX_SRL, (char*)HW_TWL_FS_BOOT_SRL_PATH_BUF) ||
ES_ERR_OK != ES_CloseLib() )
{
return FALSE;
}
return TRUE;
}
/*---------------------------------------------------------------------------*
Name: FS_SetDigestKey
Description: set specified key or default key for HMAC-SHA-1
Arguments: digestKey pointer to key
if NULL, use default key
Returns: TRUE if success
*---------------------------------------------------------------------------*/
void FS_SetDigestKey( const u8* digestKey )
{
if ( digestKey )
{
MI_CpuCopy8(digestKey, currentKey, SVC_SHA1_BLOCK_SIZE);
}
else
{
MI_CpuCopy8(defaultKey, currentKey, SVC_SHA1_BLOCK_SIZE);
}
}
static inline BOOL CheckDigest( u8* a, u8* b, BOOL aClr, BOOL bClr )
{
BOOL result = TRUE;
int i;
for ( i = 0; i < SVC_SHA1_BLOCK_SIZE; i++ )
{
if ( a[i] != b[i] )
{
result = FALSE;
}
}
if ( aClr ) MI_CpuClear8(a, SVC_SHA1_BLOCK_SIZE);
if ( bClr ) MI_CpuClear8(b, SVC_SHA1_BLOCK_SIZE);
return result;
}
/*---------------------------------------------------------------------------*
Name: CheckRomCertificate
Description: check the certification in the ROM
ROMヘッダに付加された証明書のチェックを行います
makerom.TWL内のコードに依存します
Arguments: pool pointer to the SVCSignHeapContext
pCert pointer to the certification
pCAPubKey pointer to the public key for the certification
gameCode initial code
Returns: TRUE if success
*---------------------------------------------------------------------------*/
static BOOL CheckRomCertificate( SVCSignHeapContext* pool, const RomCertificate *pCert, const void* pCAPubKey, u32 gameCode )
{
u8 digest[SVC_SHA1_BLOCK_SIZE];
u8 md[SVC_SHA1_BLOCK_SIZE];
// 証明書ヘッダのマジックナンバーチェック
if( pCert->header.magicNumber != TWL_ROM_CERT_MAGIC_NUMBER ||
// 証明書ヘッダとROMヘッダのゲームコード一致チェック
pCert->header.gameCode != gameCode )
{
return FALSE;
}
// 証明書署名チェック
SVC_DecryptSign( pool, &digest, pCert->sign, pCAPubKey );
// ダイジェストの計算
SVC_CalcSHA1( md, pCert, ROM_CERT_SIGN_OFFSET );
// 比較
return CheckDigest(md, digest, TRUE, TRUE);
}
/*---------------------------------------------------------------------------*
Name: FS_LoadBuffer
Description: receive data from ARM7 via WRAM-B and store in destination address,
calculate SHA1 in parallel if ctx is specified
Arguments: dest destination address to read
size total length to read in bytes
ctx pointer to SHA1 context or NULL
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadBuffer( u8* dest, u32 size, SVCSHA1Context *ctx )
{
static int count = 0;
while ( size > 0 )
{
u8* src = (u8*)HW_FIRM_LOAD_BUFFER_BASE + count * HW_FIRM_LOAD_BUFFER_UNIT_SIZE;
u32 unit = size < HW_FIRM_LOAD_BUFFER_UNIT_SIZE ? size : HW_FIRM_LOAD_BUFFER_UNIT_SIZE;
if ( PXI_RecvID() != FIRM_PXI_ID_LOAD_PIRIOD )
{
return FALSE;
}
MIi_SetWramBankMaster_B(count, MI_WRAM_ARM9);
if (ctx)
{
int done;
for ( done = 0; done < unit; done += HASH_UNIT )
{
u8* s = src + done;
u8* d = dest + done;
u32 u = unit - done < HASH_UNIT ? unit - done : HASH_UNIT;
SVC_SHA1Update( ctx, s, u );
MI_CpuCopyFast( s, d, u );
MI_CpuClearFast( s, u );
}
}
else
{
MI_CpuCopyFast( src, dest, unit );
MI_CpuClearFast( src, unit );
}
DC_FlushRange( src, unit );
MIi_SetWramBankMaster_B(count, MI_WRAM_ARM7);
count = (count + 1) % HW_FIRM_LOAD_BUFFER_UNIT_NUMS;
size -= unit;
dest += unit;
}
return TRUE;
}
/*---------------------------------------------------------------------------*
Name: GetTransferSize
Description: get size to transfer once
AES領域をまたぐ場合は (
)
makerom.TWLまたはIPLの使用に依存します
Arguments: offset offset of region from head of ROM_Header
size size of region
Returns: size to transfer once
*---------------------------------------------------------------------------*/
static u32 GetTransferSize( u32 offset, u32 size )
{
u32 aes_offset = rh->s.aes_target_rom_offset;
u32 aes_end = aes_offset + rh->s.aes_target_size;
u32 end = offset + size;
if ( rh->s.enable_aes )
{
if ( offset >= aes_offset && offset < aes_end )
{
if ( end > aes_end )
{
size = aes_end - offset;
}
}
else
{
if ( offset < aes_offset && offset + size > aes_offset )
{
size = aes_offset - offset;
}
}
}
return size;
}
/*---------------------------------------------------------------------------*
Name: FS_LoadModule
Description: receive data from ARM7 via WRAM-B and store in destination address
in view of AES settings in the ROM header at HW_TWL_ROM_HEADER_BUF,
then verify the digest
Arguments: dest destination address to read
offset file offset to start to read in bytes
size total length to read in bytes
digest digest to verify
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadModule( u8* dest, u32 offset, u32 size, const u8 digest[SVC_SHA1_BLOCK_SIZE] )
{
SVCHMACSHA1Context ctx;
u8 md[SVC_SHA1_BLOCK_SIZE];
SVC_HMACSHA1Init(&ctx, currentKey, SVC_SHA1_BLOCK_SIZE );
while ( size > 0 )
{
u32 unit = GetTransferSize( offset, size );
if ( !FS_LoadBuffer( dest, unit, &ctx.sha1_ctx ) )
{
return FALSE;
}
dest += unit;
offset += unit;
size -= unit;
}
SVC_HMACSHA1GetHash(&ctx, md);
return CheckDigest(md, (u8*)digest, TRUE, FALSE);
}
/*---------------------------------------------------------------------------*
Name: FS_LoadHeader
Description: receive ROM header, store to HW_TWL_ROM_HEADER_BUF,
and verify signature
Arguments: pool heap context to call SVC_DecryptSign
rsa_key public key to verify the signature
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadHeader( SVCSignHeapContext* pool, const void* rsa_key )
{
SVCSHA1Context ctx;
u8 md[SVC_SHA1_BLOCK_SIZE];
u8 digest[SVC_SHA1_BLOCK_SIZE];
SignatureData sd;
SVC_SHA1Init( &ctx );
if ( !FS_LoadBuffer( (u8*)rh, FS_HEADER_AUTH_SIZE, &ctx ) )
{
return FALSE;
}
SVC_SHA1GetHash( &ctx, md );
if ( !FS_LoadBuffer( (u8*)rh + FS_HEADER_AUTH_SIZE, HW_TWL_ROM_HEADER_BUF_SIZE - FS_HEADER_AUTH_SIZE, NULL ) )
{
return FALSE;
}
// コンテンツ証明書
if ( CheckRomCertificate( pool, &rh->certificate, rsa_key, *(u32*)rh->s.game_code ) )
{
rsa_key = rh->certificate.pubKeyMod; // ヘッダ用の鍵の取り出し
}
else
{
// とりあえずコンテンツ証明書用の鍵がそのまま使えると仮定
}
// ヘッダ署名チェック
SVC_DecryptSign( pool, &sd, rh->signature, rsa_key );
MI_CpuCopy8( sd.digest, digest, SVC_SHA1_BLOCK_SIZE ); // ダイジェストの取り出し
MI_CpuClear8( &sd, sizeof(sd) ); // 残り削除 (他に必要なものはない?)
return CheckDigest( md, digest, TRUE, TRUE );
}
/*---------------------------------------------------------------------------*
Name: FS_LoadStatic
Description: receive static regions from ARM6 via WRAM-B and store them
specified by ROM header at HW_TWL_ROM_HEADER_BUF
Arguments: None
Returns: TRUE if success
*---------------------------------------------------------------------------*/
BOOL FS_LoadStatic( void )
{
if ( rh->s.main_size > 0 )
{
if ( !FS_LoadModule( rh->s.main_ram_address, rh->s.main_rom_offset, rh->s.main_size, rh->s.main_static_digest ) )
{
return FALSE;
}
}
if ( rh->s.sub_size > 0 )
{
if ( !FS_LoadModule( rh->s.sub_ram_address, rh->s.sub_rom_offset, rh->s.sub_size, rh->s.sub_static_digest ) )
{
return FALSE;
}
}
if ( rh->s.main_ltd_size > 0 )
{
if ( !FS_LoadModule( rh->s.main_ltd_ram_address, rh->s.main_ltd_rom_offset, rh->s.main_ltd_size, rh->s.main_ltd_static_digest ) )
{
return FALSE;
}
}
if ( rh->s.sub_ltd_size > 0 )
{
if ( !FS_LoadModule( rh->s.sub_ltd_ram_address, rh->s.sub_ltd_rom_offset, rh->s.sub_ltd_size, rh->s.sub_ltd_static_digest ) )
{
return FALSE;
}
}
return TRUE;
}

View File

@ -0,0 +1,31 @@
#! make -f
#----------------------------------------------------------------------------
# Project: TwlIPL - libraries - fatfs
# File: Makefile
#
# Copyright 2007 Nintendo. All rights reserved.
#
# These coded instructions, statements, and computer programs contain
# proprietary information of Nintendo of America Inc. and/or Nintendo
# Company Ltd., and are protected by Federal copyright law. They may
# not be disclosed to third parties or copied or duplicated in any form,
# in whole or in part, without the prior written consent of Nintendo.
#
# $Date:: $
# $Rev:$
# $Author:$
#----------------------------------------------------------------------------
include $(TWLIPL_ROOT)/build/buildtools/commondefs
#----------------------------------------------------------------------------
SUBDIRS = ARM7 ARM9
#----------------------------------------------------------------------------
include $(TWLIPL_ROOT)/build/buildtools/modulerules
#===== End of Makefile =====