2018-03-27 00:04:16 +01:00
|
|
|
/*
|
2018-08-05 12:40:32 +01:00
|
|
|
* Copyright (c) 2018 naehrwert
|
2024-03-12 23:44:58 +00:00
|
|
|
* Copyright (c) 2018-2024 CTCaer
|
2018-11-30 21:20:15 +00:00
|
|
|
* Copyright (c) 2018 balika011
|
2018-08-05 12:40:32 +01:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2018-03-27 00:04:16 +01:00
|
|
|
|
2018-03-14 23:26:19 +00:00
|
|
|
#include <string.h>
|
2018-08-05 12:40:32 +01:00
|
|
|
|
2020-06-14 14:45:45 +01:00
|
|
|
#include "tsec.h"
|
|
|
|
#include "tsec_t210.h"
|
2022-07-11 20:10:11 +01:00
|
|
|
#include <memory_map.h>
|
|
|
|
#include <mem/heap.h>
|
|
|
|
#include <mem/mc.h>
|
|
|
|
#include <mem/smmu.h>
|
2020-06-14 14:45:45 +01:00
|
|
|
#include <sec/se_t210.h>
|
|
|
|
#include <soc/bpmp.h>
|
|
|
|
#include <soc/clock.h>
|
|
|
|
#include <soc/kfuse.h>
|
2021-09-17 21:10:57 +01:00
|
|
|
#include <soc/pmc.h>
|
2020-06-14 14:45:45 +01:00
|
|
|
#include <soc/t210.h>
|
2022-07-11 20:10:11 +01:00
|
|
|
#include <soc/timer.h>
|
2018-11-30 21:20:15 +00:00
|
|
|
|
2020-06-14 14:45:45 +01:00
|
|
|
// #include <gfx_utils.h>
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2020-06-13 23:32:54 +01:00
|
|
|
#define PKG11_MAGIC 0x31314B50
|
2021-08-28 14:42:03 +01:00
|
|
|
|
|
|
|
#define TSEC_HOS_KB_620 6
|
2020-06-13 23:32:54 +01:00
|
|
|
|
2018-03-14 23:26:19 +00:00
|
|
|
static int _tsec_dma_wait_idle()
|
|
|
|
{
|
2018-07-04 16:39:26 +01:00
|
|
|
u32 timeout = get_tmr_ms() + 10000;
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2018-12-01 18:49:31 +00:00
|
|
|
while (!(TSEC(TSEC_DMATRFCMD) & TSEC_DMATRFCMD_IDLE))
|
2018-07-04 16:39:26 +01:00
|
|
|
if (get_tmr_ms() > timeout)
|
2018-03-14 23:26:19 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int _tsec_dma_pa_to_internal_100(int not_imem, int i_offset, int pa_offset)
|
|
|
|
{
|
|
|
|
u32 cmd;
|
|
|
|
|
|
|
|
if (not_imem)
|
2018-12-01 18:49:31 +00:00
|
|
|
cmd = TSEC_DMATRFCMD_SIZE_256B; // DMA 256 bytes
|
2018-03-14 23:26:19 +00:00
|
|
|
else
|
2018-12-01 18:49:31 +00:00
|
|
|
cmd = TSEC_DMATRFCMD_IMEM; // DMA IMEM (Instruction memmory)
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2022-07-11 20:10:11 +01:00
|
|
|
TSEC(TSEC_DMATRFMOFFS) = i_offset;
|
2018-12-01 18:49:31 +00:00
|
|
|
TSEC(TSEC_DMATRFFBOFFS) = pa_offset;
|
2022-07-11 20:10:11 +01:00
|
|
|
TSEC(TSEC_DMATRFCMD) = cmd;
|
2018-03-14 23:26:19 +00:00
|
|
|
|
|
|
|
return _tsec_dma_wait_idle();
|
|
|
|
}
|
|
|
|
|
2021-08-28 14:42:03 +01:00
|
|
|
int tsec_query(void *tsec_keys, tsec_ctxt_t *tsec_ctxt)
|
2018-03-14 23:26:19 +00:00
|
|
|
{
|
|
|
|
int res = 0;
|
2018-11-30 21:20:15 +00:00
|
|
|
u8 *fwbuf = NULL;
|
2021-08-28 14:42:03 +01:00
|
|
|
u32 type = tsec_ctxt->type;
|
2018-11-30 21:20:15 +00:00
|
|
|
u32 *pdir, *car, *fuse, *pmc, *flowctrl, *se, *mc, *iram, *evec;
|
2018-12-16 14:55:56 +00:00
|
|
|
u32 *pkg11_magic_off;
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2019-06-30 01:15:46 +01:00
|
|
|
bpmp_mmu_disable();
|
2021-05-11 07:21:12 +01:00
|
|
|
bpmp_freq_t prev_fid = bpmp_clk_rate_set(BPMP_CLK_NORMAL);
|
2019-06-30 01:15:46 +01:00
|
|
|
|
2019-09-11 00:19:41 +01:00
|
|
|
// Enable clocks.
|
2018-03-14 23:26:19 +00:00
|
|
|
clock_enable_tsec();
|
|
|
|
clock_enable_sor_safe();
|
|
|
|
clock_enable_sor0();
|
|
|
|
clock_enable_sor1();
|
|
|
|
clock_enable_kfuse();
|
2019-10-22 16:57:51 +01:00
|
|
|
kfuse_wait_ready();
|
|
|
|
|
2022-07-11 20:28:09 +01:00
|
|
|
// Disable AHB aperture.
|
|
|
|
mc_disable_ahb_redirect();
|
|
|
|
|
2021-08-28 14:42:03 +01:00
|
|
|
if (type == TSEC_FW_TYPE_NEW)
|
2021-09-17 21:10:57 +01:00
|
|
|
{
|
|
|
|
// Disable all CCPLEX core rails.
|
|
|
|
pmc_enable_partition(POWER_RAIL_CE0, DISABLE);
|
|
|
|
pmc_enable_partition(POWER_RAIL_CE1, DISABLE);
|
|
|
|
pmc_enable_partition(POWER_RAIL_CE2, DISABLE);
|
|
|
|
pmc_enable_partition(POWER_RAIL_CE3, DISABLE);
|
|
|
|
|
|
|
|
// Enable AHB aperture and set it to full mmio.
|
2022-01-28 23:29:02 +00:00
|
|
|
mc_enable_ahb_redirect();
|
2021-09-17 21:10:57 +01:00
|
|
|
}
|
2021-08-28 14:42:03 +01:00
|
|
|
|
2019-09-11 00:19:41 +01:00
|
|
|
// Configure Falcon.
|
2018-12-01 18:49:31 +00:00
|
|
|
TSEC(TSEC_DMACTL) = 0;
|
|
|
|
TSEC(TSEC_IRQMSET) =
|
|
|
|
TSEC_IRQMSET_EXT(0xFF) |
|
2022-07-11 20:10:11 +01:00
|
|
|
TSEC_IRQMSET_WDTMR |
|
|
|
|
TSEC_IRQMSET_HALT |
|
|
|
|
TSEC_IRQMSET_EXTERR |
|
|
|
|
TSEC_IRQMSET_SWGEN0 |
|
2018-12-01 18:49:31 +00:00
|
|
|
TSEC_IRQMSET_SWGEN1;
|
|
|
|
TSEC(TSEC_IRQDEST) =
|
|
|
|
TSEC_IRQDEST_EXT(0xFF) |
|
2022-07-11 20:10:11 +01:00
|
|
|
TSEC_IRQDEST_HALT |
|
|
|
|
TSEC_IRQDEST_EXTERR |
|
|
|
|
TSEC_IRQDEST_SWGEN0 |
|
2018-12-01 18:49:31 +00:00
|
|
|
TSEC_IRQDEST_SWGEN1;
|
|
|
|
TSEC(TSEC_ITFEN) = TSEC_ITFEN_CTXEN | TSEC_ITFEN_MTHDEN;
|
2018-03-14 23:26:19 +00:00
|
|
|
if (!_tsec_dma_wait_idle())
|
|
|
|
{
|
|
|
|
res = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:19:41 +01:00
|
|
|
// Load firmware or emulate memio environment for newer TSEC fw.
|
2021-08-28 14:42:03 +01:00
|
|
|
if (type == TSEC_FW_TYPE_EMU)
|
2019-02-23 22:59:33 +00:00
|
|
|
TSEC(TSEC_DMATRFBASE) = (u32)tsec_ctxt->fw >> 8;
|
|
|
|
else
|
2018-11-30 21:20:15 +00:00
|
|
|
{
|
2021-10-01 13:03:18 +01:00
|
|
|
fwbuf = (u8 *)malloc(SZ_16K);
|
2019-02-23 22:59:33 +00:00
|
|
|
u8 *fwbuf_aligned = (u8 *)ALIGN((u32)fwbuf, 0x100);
|
2018-11-30 21:20:15 +00:00
|
|
|
memcpy(fwbuf_aligned, tsec_ctxt->fw, tsec_ctxt->size);
|
2022-07-11 20:10:11 +01:00
|
|
|
|
2018-11-30 21:20:15 +00:00
|
|
|
TSEC(TSEC_DMATRFBASE) = (u32)fwbuf_aligned >> 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (u32 addr = 0; addr < tsec_ctxt->size; addr += 0x100)
|
|
|
|
{
|
|
|
|
if (!_tsec_dma_pa_to_internal_100(false, addr, addr))
|
2018-03-14 23:26:19 +00:00
|
|
|
{
|
|
|
|
res = -2;
|
2018-05-01 06:15:48 +01:00
|
|
|
goto out_free;
|
2018-03-14 23:26:19 +00:00
|
|
|
}
|
2018-11-30 21:20:15 +00:00
|
|
|
}
|
|
|
|
|
2021-08-28 14:42:03 +01:00
|
|
|
if (type == TSEC_FW_TYPE_EMU)
|
2018-11-30 21:20:15 +00:00
|
|
|
{
|
|
|
|
// Init SMMU translation for TSEC.
|
2024-03-12 23:54:46 +00:00
|
|
|
pdir = smmu_init_domain(MC_SMMU_TSEC_ASID);
|
2024-03-12 23:44:58 +00:00
|
|
|
smmu_init();
|
|
|
|
|
|
|
|
// Enable SMMU.
|
|
|
|
smmu_enable();
|
2018-11-30 21:20:15 +00:00
|
|
|
|
|
|
|
// Clock reset controller.
|
2024-03-12 23:54:46 +00:00
|
|
|
car = smmu_page_zalloc(1);
|
2021-10-01 13:03:18 +01:00
|
|
|
memcpy(car, (void *)CLOCK_BASE, SZ_PAGE);
|
2018-11-30 21:20:15 +00:00
|
|
|
car[CLK_RST_CONTROLLER_CLK_SOURCE_TSEC / 4] = 2;
|
|
|
|
smmu_map(pdir, CLOCK_BASE, (u32)car, 1, _WRITABLE | _READABLE | _NONSECURE);
|
|
|
|
|
|
|
|
// Fuse driver.
|
2024-03-12 23:54:46 +00:00
|
|
|
fuse = smmu_page_zalloc(1);
|
2021-10-01 13:03:18 +01:00
|
|
|
memcpy((void *)&fuse[0x800/4], (void *)FUSE_BASE, SZ_1K);
|
2018-12-01 18:49:31 +00:00
|
|
|
fuse[0x82C / 4] = 0;
|
2021-08-28 14:42:03 +01:00
|
|
|
fuse[0x9E0 / 4] = (1 << (TSEC_HOS_KB_620 + 2)) - 1;
|
|
|
|
fuse[0x9E4 / 4] = (1 << (TSEC_HOS_KB_620 + 2)) - 1;
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, (FUSE_BASE - 0x800), (u32)fuse, 1, _READABLE | _NONSECURE);
|
|
|
|
|
|
|
|
// Power management controller.
|
2024-03-12 23:54:46 +00:00
|
|
|
pmc = smmu_page_zalloc(1);
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, RTC_BASE, (u32)pmc, 1, _READABLE | _NONSECURE);
|
|
|
|
|
|
|
|
// Flow control.
|
2024-03-12 23:54:46 +00:00
|
|
|
flowctrl = smmu_page_zalloc(1);
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, FLOW_CTLR_BASE, (u32)flowctrl, 1, _WRITABLE | _NONSECURE);
|
|
|
|
|
|
|
|
// Security engine.
|
2024-03-12 23:54:46 +00:00
|
|
|
se = smmu_page_zalloc(1);
|
2021-10-01 13:03:18 +01:00
|
|
|
memcpy(se, (void *)SE_BASE, SZ_PAGE);
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, SE_BASE, (u32)se, 1, _READABLE | _WRITABLE | _NONSECURE);
|
2019-10-18 16:02:06 +01:00
|
|
|
|
2018-11-30 21:20:15 +00:00
|
|
|
// Memory controller.
|
2024-03-12 23:54:46 +00:00
|
|
|
mc = smmu_page_zalloc(1);
|
2021-10-01 13:03:18 +01:00
|
|
|
memcpy(mc, (void *)MC_BASE, SZ_PAGE);
|
2018-11-30 21:20:15 +00:00
|
|
|
mc[MC_IRAM_BOM / 4] = 0;
|
2022-07-11 20:10:11 +01:00
|
|
|
mc[MC_IRAM_TOM / 4] = DRAM_START;
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, MC_BASE, (u32)mc, 1, _READABLE | _NONSECURE);
|
|
|
|
|
|
|
|
// IRAM
|
2024-03-12 23:54:46 +00:00
|
|
|
iram = smmu_page_zalloc(0x30);
|
2018-11-30 21:20:15 +00:00
|
|
|
memcpy(iram, tsec_ctxt->pkg1, 0x30000);
|
2018-12-16 14:55:56 +00:00
|
|
|
// PKG1.1 magic offset.
|
|
|
|
pkg11_magic_off = (u32 *)(iram + ((tsec_ctxt->pkg11_off + 0x20) / 4));
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, 0x40010000, (u32)iram, 0x30, _READABLE | _WRITABLE | _NONSECURE);
|
|
|
|
|
|
|
|
// Exception vectors
|
2024-03-12 23:54:46 +00:00
|
|
|
evec = smmu_page_zalloc(1);
|
2018-11-30 21:20:15 +00:00
|
|
|
smmu_map(pdir, EXCP_VEC_BASE, (u32)evec, 1, _READABLE | _WRITABLE | _NONSECURE);
|
|
|
|
}
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2019-09-11 00:19:41 +01:00
|
|
|
// Execute firmware.
|
|
|
|
HOST1X(HOST1X_CH0_SYNC_SYNCPT_160) = 0x34C2E1DA;
|
2023-06-09 08:28:28 +01:00
|
|
|
TSEC(TSEC_MAILBOX1) = 0;
|
|
|
|
TSEC(TSEC_MAILBOX0) = 1; // Set HOS key version.
|
|
|
|
TSEC(TSEC_BOOTVEC) = 0;
|
|
|
|
TSEC(TSEC_CPUCTL) = TSEC_CPUCTL_STARTCPU;
|
2018-11-30 21:20:15 +00:00
|
|
|
|
2021-08-28 14:42:03 +01:00
|
|
|
if (type == TSEC_FW_TYPE_EMU)
|
2018-11-30 21:20:15 +00:00
|
|
|
{
|
2021-02-06 00:55:58 +00:00
|
|
|
u32 k = se[SE_CRYPTO_KEYTABLE_DATA_REG / 4];
|
2022-07-11 20:28:09 +01:00
|
|
|
u32 timeout = get_tmr_us() + 125000;
|
2018-11-30 21:20:15 +00:00
|
|
|
u32 key[16] = {0};
|
|
|
|
u32 kidx = 0;
|
|
|
|
|
2020-06-13 23:32:54 +01:00
|
|
|
while (*pkg11_magic_off != PKG11_MAGIC)
|
2018-11-30 21:20:15 +00:00
|
|
|
{
|
|
|
|
smmu_flush_all();
|
2018-12-16 14:55:56 +00:00
|
|
|
|
2021-02-06 00:55:58 +00:00
|
|
|
if (k != se[SE_CRYPTO_KEYTABLE_DATA_REG / 4])
|
2019-04-19 16:54:09 +01:00
|
|
|
{
|
2021-02-06 00:55:58 +00:00
|
|
|
k = se[SE_CRYPTO_KEYTABLE_DATA_REG / 4];
|
2019-04-19 16:54:09 +01:00
|
|
|
key[kidx++] = k;
|
|
|
|
}
|
2018-11-30 21:20:15 +00:00
|
|
|
|
2018-12-01 18:49:31 +00:00
|
|
|
// Failsafe.
|
2022-07-11 20:28:09 +01:00
|
|
|
if ((u32)get_tmr_us() > timeout)
|
2018-12-01 18:49:31 +00:00
|
|
|
break;
|
2018-11-30 21:20:15 +00:00
|
|
|
}
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2018-11-30 21:20:15 +00:00
|
|
|
if (kidx != 8)
|
|
|
|
{
|
|
|
|
res = -6;
|
2024-03-12 23:54:46 +00:00
|
|
|
smmu_deinit_domain(MC_SMMU_TSEC_ASID);
|
2018-11-30 21:20:15 +00:00
|
|
|
|
2019-12-04 19:31:39 +00:00
|
|
|
goto out_free;
|
2018-11-30 21:20:15 +00:00
|
|
|
}
|
|
|
|
|
2018-12-16 14:55:56 +00:00
|
|
|
// Give some extra time to make sure PKG1.1 is decrypted.
|
|
|
|
msleep(50);
|
|
|
|
|
2018-11-30 21:20:15 +00:00
|
|
|
memcpy(tsec_keys, &key, 0x20);
|
|
|
|
memcpy(tsec_ctxt->pkg1, iram, 0x30000);
|
2019-10-18 16:02:06 +01:00
|
|
|
|
2024-03-12 23:54:46 +00:00
|
|
|
smmu_deinit_domain(MC_SMMU_TSEC_ASID);
|
2018-11-30 21:20:15 +00:00
|
|
|
|
|
|
|
// for (int i = 0; i < kidx; i++)
|
2019-04-14 00:30:14 +01:00
|
|
|
// gfx_printf("key %08X\n", key[i]);
|
2018-11-30 21:20:15 +00:00
|
|
|
|
2023-06-09 08:28:28 +01:00
|
|
|
// gfx_printf("cpuctl (%08X) mbox (%08X)\n", TSEC(TSEC_CPUCTL), TSEC(TSEC_MAILBOX1));
|
2018-11-30 21:20:15 +00:00
|
|
|
|
|
|
|
// u32 errst = MC(MC_ERR_STATUS);
|
2019-04-14 00:30:14 +01:00
|
|
|
// gfx_printf(" MC %08X %08X %08X\n", MC(MC_INTSTATUS), errst, MC(MC_ERR_ADR));
|
|
|
|
// gfx_printf(" type: %02X\n", errst >> 28);
|
|
|
|
// gfx_printf(" smmu: %02X\n", (errst >> 25) & 3);
|
|
|
|
// gfx_printf(" dir: %s\n", (errst >> 16) & 1 ? "W" : "R");
|
|
|
|
// gfx_printf(" cid: %02x\n", errst & 0xFF);
|
2018-11-30 21:20:15 +00:00
|
|
|
}
|
2019-02-23 22:59:33 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!_tsec_dma_wait_idle())
|
|
|
|
{
|
|
|
|
res = -3;
|
|
|
|
goto out_free;
|
|
|
|
}
|
2023-06-09 08:28:28 +01:00
|
|
|
|
2019-02-23 22:59:33 +00:00
|
|
|
u32 timeout = get_tmr_ms() + 2000;
|
2023-06-09 08:28:28 +01:00
|
|
|
while (!TSEC(TSEC_MAILBOX1))
|
|
|
|
{
|
2019-02-23 22:59:33 +00:00
|
|
|
if (get_tmr_ms() > timeout)
|
|
|
|
{
|
|
|
|
res = -4;
|
|
|
|
goto out_free;
|
|
|
|
}
|
2023-06-09 08:28:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (TSEC(TSEC_MAILBOX1) != 0xB0B0B0B0)
|
2019-02-23 22:59:33 +00:00
|
|
|
{
|
|
|
|
res = -5;
|
|
|
|
goto out_free;
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:19:41 +01:00
|
|
|
// Fetch result.
|
|
|
|
HOST1X(HOST1X_CH0_SYNC_SYNCPT_160) = 0;
|
2019-02-23 22:59:33 +00:00
|
|
|
u32 buf[4];
|
2023-06-09 08:28:28 +01:00
|
|
|
buf[0] = SOR1(SOR_DP_HDCP_BKSV_LSB);
|
|
|
|
buf[1] = SOR1(SOR_TMDS_HDCP_BKSV_LSB);
|
|
|
|
buf[2] = SOR1(SOR_TMDS_HDCP_CN_MSB);
|
|
|
|
buf[3] = SOR1(SOR_TMDS_HDCP_CN_LSB);
|
|
|
|
SOR1(SOR_DP_HDCP_BKSV_LSB) = 0;
|
|
|
|
SOR1(SOR_TMDS_HDCP_BKSV_LSB) = 0;
|
|
|
|
SOR1(SOR_TMDS_HDCP_CN_MSB) = 0;
|
|
|
|
SOR1(SOR_TMDS_HDCP_CN_LSB) = 0;
|
2019-02-23 22:59:33 +00:00
|
|
|
|
2021-02-06 00:55:58 +00:00
|
|
|
memcpy(tsec_keys, &buf, SE_KEY_128_SIZE);
|
2019-02-23 22:59:33 +00:00
|
|
|
}
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2022-01-28 23:29:02 +00:00
|
|
|
out_free:
|
2018-05-01 06:15:48 +01:00
|
|
|
free(fwbuf);
|
|
|
|
|
2022-01-28 23:29:02 +00:00
|
|
|
out:
|
2019-09-11 00:19:41 +01:00
|
|
|
// Disable clocks.
|
2018-03-14 23:26:19 +00:00
|
|
|
clock_disable_kfuse();
|
|
|
|
clock_disable_sor1();
|
|
|
|
clock_disable_sor0();
|
|
|
|
clock_disable_sor_safe();
|
|
|
|
clock_disable_tsec();
|
2019-06-30 01:15:46 +01:00
|
|
|
bpmp_mmu_enable();
|
2021-05-11 07:21:12 +01:00
|
|
|
bpmp_clk_rate_set(prev_fid);
|
2018-03-14 23:26:19 +00:00
|
|
|
|
2022-07-11 20:28:09 +01:00
|
|
|
#ifdef BDK_MC_ENABLE_AHB_REDIRECT
|
|
|
|
// Re-enable AHB aperture.
|
|
|
|
mc_enable_ahb_redirect();
|
|
|
|
#endif
|
|
|
|
|
2018-03-14 23:26:19 +00:00
|
|
|
return res;
|
|
|
|
}
|