stm32h7: Switched to the unified mass erase command, breaking up the implementation cleanly and added progress dots to stop GDB timing out
This commit is contained in:
parent
980a1f140c
commit
787d66fe95
|
@ -37,7 +37,6 @@
|
||||||
#include "target_internal.h"
|
#include "target_internal.h"
|
||||||
#include "cortexm.h"
|
#include "cortexm.h"
|
||||||
|
|
||||||
static bool stm32h7_cmd_erase_mass(target *t, int argc, const char **argv);
|
|
||||||
/* static bool stm32h7_cmd_option(target *t, int argc, char *argv[]); */
|
/* static bool stm32h7_cmd_option(target *t, int argc, char *argv[]); */
|
||||||
static bool stm32h7_uid(target *t, int argc, const char **argv);
|
static bool stm32h7_uid(target *t, int argc, const char **argv);
|
||||||
static bool stm32h7_crc(target *t, int argc, const char **argv);
|
static bool stm32h7_crc(target *t, int argc, const char **argv);
|
||||||
|
@ -45,29 +44,22 @@ static bool stm32h7_cmd_psize(target *t, int argc, char *argv[]);
|
||||||
static bool stm32h7_cmd_rev(target *t, int argc, const char **argv);
|
static bool stm32h7_cmd_rev(target *t, int argc, const char **argv);
|
||||||
|
|
||||||
const struct command_s stm32h7_cmd_list[] = {
|
const struct command_s stm32h7_cmd_list[] = {
|
||||||
{"erase_mass", (cmd_handler)stm32h7_cmd_erase_mass,
|
/*{"option", (cmd_handler)stm32h7_cmd_option, "Manipulate option bytes"},*/
|
||||||
"Erase entire flash memory"},
|
{"psize", (cmd_handler)stm32h7_cmd_psize, "Configure flash write parallelism: (x8|x16|x32|x64(default))"},
|
||||||
/* {"option", (cmd_handler)stm32h7_cmd_option,
|
|
||||||
"Manipulate option bytes"},*/
|
|
||||||
{"psize", (cmd_handler)stm32h7_cmd_psize,
|
|
||||||
"Configure flash write parallelism: (x8|x16|x32|x64(default))"},
|
|
||||||
{"uid", (cmd_handler)stm32h7_uid, "Print unique device ID"},
|
{"uid", (cmd_handler)stm32h7_uid, "Print unique device ID"},
|
||||||
{"crc", (cmd_handler)stm32h7_crc, "Print CRC of both banks"},
|
{"crc", (cmd_handler)stm32h7_crc, "Print CRC of both banks"},
|
||||||
{"revision", (cmd_handler)stm32h7_cmd_rev,
|
{"revision", (cmd_handler)stm32h7_cmd_rev, "Returns the Device ID and Revision"},
|
||||||
"Returns the Device ID and Revision"},
|
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static int stm32h7_flash_erase(struct target_flash *f, target_addr addr,
|
static int stm32h7_flash_erase(struct target_flash *f, target_addr addr, size_t len);
|
||||||
size_t len);
|
static int stm32h7_flash_write(struct target_flash *f, target_addr dest, const void *src, size_t len);
|
||||||
static int stm32h7_flash_write(struct target_flash *f,
|
static bool stm32h7_mass_erase(target *t);
|
||||||
target_addr dest, const void *src, size_t len);
|
|
||||||
|
|
||||||
static const char stm32h7_driver_str[] = "STM32H7";
|
static const char stm32h7_driver_str[] = "STM32H7";
|
||||||
|
|
||||||
enum stm32h7_regs
|
enum stm32h7_regs {
|
||||||
{
|
|
||||||
FLASH_ACR = 0x00,
|
FLASH_ACR = 0x00,
|
||||||
FLASH_KEYR = 0x04,
|
FLASH_KEYR = 0x04,
|
||||||
FLASH_OPTKEYR = 0x08,
|
FLASH_OPTKEYR = 0x08,
|
||||||
|
@ -169,18 +161,15 @@ struct stm32h7_priv_s {
|
||||||
uint32_t dbg_cr;
|
uint32_t dbg_cr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void stm32h7_add_flash(target *t,
|
static void stm32h7_add_flash(target *t, uint32_t addr, size_t length, size_t blocksize)
|
||||||
uint32_t addr, size_t length, size_t blocksize)
|
|
||||||
{
|
{
|
||||||
struct stm32h7_flash *sf = calloc(1, sizeof(*sf));
|
struct stm32h7_flash *sf = calloc(1, sizeof(*sf));
|
||||||
struct target_flash *f;
|
|
||||||
|
|
||||||
if (!sf) { /* calloc failed: heap exhaustion */
|
if (!sf) { /* calloc failed: heap exhaustion */
|
||||||
DEBUG_WARN("calloc: failed in %s\n", __func__);
|
DEBUG_WARN("calloc: failed in %s\n", __func__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
f = &sf->f;
|
struct target_flash *f = &sf->f;
|
||||||
f->start = addr;
|
f->start = addr;
|
||||||
f->length = length;
|
f->length = length;
|
||||||
f->blocksize = blocksize;
|
f->blocksize = blocksize;
|
||||||
|
@ -237,6 +226,7 @@ bool stm32h7_probe(target *t)
|
||||||
{
|
{
|
||||||
uint32_t idcode = t->idcode;
|
uint32_t idcode = t->idcode;
|
||||||
if (idcode == ID_STM32H74x || idcode == ID_STM32H7Bx || idcode == ID_STM32H72x) {
|
if (idcode == ID_STM32H74x || idcode == ID_STM32H7Bx || idcode == ID_STM32H72x) {
|
||||||
|
t->mass_erase = stm32h7_mass_erase;
|
||||||
t->driver = stm32h7_driver_str;
|
t->driver = stm32h7_driver_str;
|
||||||
t->attach = stm32h7_attach;
|
t->attach = stm32h7_attach;
|
||||||
t->detach = stm32h7_detach;
|
t->detach = stm32h7_detach;
|
||||||
|
@ -255,21 +245,20 @@ bool stm32h7_probe(target *t)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool stm32h7_flash_unlock(target *t, uint32_t addr)
|
static bool stm32h7_flash_unlock(target *t, const uint32_t addr)
|
||||||
{
|
{
|
||||||
uint32_t regbase = FPEC1_BASE;
|
uint32_t regbase = FPEC1_BASE;
|
||||||
if (addr >= BANK2_START) {
|
if (addr >= BANK2_START)
|
||||||
regbase = FPEC2_BASE;
|
regbase = FPEC2_BASE;
|
||||||
}
|
|
||||||
|
|
||||||
while(target_mem_read32(t, regbase + FLASH_SR) & FLASH_SR_BSY) {
|
while (target_mem_read32(t, regbase + FLASH_SR) & FLASH_SR_BSY) {
|
||||||
if(target_check_error(t))
|
if (target_check_error(t))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint32_t sr = target_mem_read32(t, regbase + FLASH_SR) & FLASH_SR_ERROR_MASK;
|
const uint32_t status = target_mem_read32(t, regbase + FLASH_SR) & FLASH_SR_ERROR_MASK;
|
||||||
if (sr) {
|
if (status) {
|
||||||
DEBUG_WARN("%s error 0x%08" PRIx32, __func__, sr);
|
DEBUG_WARN("%s error 0x%08" PRIx32, __func__, status);
|
||||||
target_mem_write32(t, regbase + FLASH_CCR, sr);
|
target_mem_write32(t, regbase + FLASH_CCR, status);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (target_mem_read32(t, regbase + FLASH_CR) & FLASH_CR_LOCK) {
|
if (target_mem_read32(t, regbase + FLASH_CR) & FLASH_CR_LOCK) {
|
||||||
|
@ -277,77 +266,72 @@ static bool stm32h7_flash_unlock(target *t, uint32_t addr)
|
||||||
target_mem_write32(t, regbase + FLASH_KEYR, KEY1);
|
target_mem_write32(t, regbase + FLASH_KEYR, KEY1);
|
||||||
target_mem_write32(t, regbase + FLASH_KEYR, KEY2);
|
target_mem_write32(t, regbase + FLASH_KEYR, KEY2);
|
||||||
}
|
}
|
||||||
if (target_mem_read32(t, regbase + FLASH_CR) & FLASH_CR_LOCK)
|
return !(target_mem_read32(t, regbase + FLASH_CR) & FLASH_CR_LOCK);
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stm32h7_flash_erase(struct target_flash *f, target_addr addr,
|
static int stm32h7_flash_erase(struct target_flash *f, target_addr addr, size_t len)
|
||||||
size_t len)
|
|
||||||
{
|
{
|
||||||
target *t = f->t;
|
target *t = f->t;
|
||||||
struct stm32h7_flash *sf = (struct stm32h7_flash *)f;
|
struct stm32h7_flash *sf = (struct stm32h7_flash *)f;
|
||||||
if (stm32h7_flash_unlock(t, addr) == false)
|
if (!stm32h7_flash_unlock(t, addr))
|
||||||
return -1;
|
return -1;
|
||||||
/* We come out of reset with HSI 64 MHz. Adapt FLASH_ACR.*/
|
/* We come out of reset with HSI 64 MHz. Adapt FLASH_ACR.*/
|
||||||
target_mem_write32(t, sf->regbase + FLASH_ACR, 0);
|
target_mem_write32(t, sf->regbase + FLASH_ACR, 0);
|
||||||
addr &= (NUM_SECTOR_PER_BANK * FLASH_SECTOR_SIZE) - 1;
|
addr &= (NUM_SECTOR_PER_BANK * FLASH_SECTOR_SIZE) - 1;
|
||||||
int start_sector = addr / FLASH_SECTOR_SIZE;
|
size_t start_sector = addr / FLASH_SECTOR_SIZE;
|
||||||
int end_sector = (addr + len - 1) / FLASH_SECTOR_SIZE;
|
const size_t end_sector = (addr + len - 1) / FLASH_SECTOR_SIZE;
|
||||||
|
|
||||||
enum align psize = ((struct stm32h7_flash *)f)->psize;
|
enum align psize = ((struct stm32h7_flash *)f)->psize;
|
||||||
uint32_t sr;
|
|
||||||
while (start_sector <= end_sector) {
|
while (start_sector <= end_sector) {
|
||||||
uint32_t cr = (psize * FLASH_CR_PSIZE16) | FLASH_CR_SER |
|
uint32_t ctrl_reg = (psize * FLASH_CR_PSIZE16) | FLASH_CR_SER | (start_sector * FLASH_CR_SNB_1);
|
||||||
(start_sector * FLASH_CR_SNB_1);
|
target_mem_write32(t, sf->regbase + FLASH_CR, ctrl_reg);
|
||||||
target_mem_write32(t, sf->regbase + FLASH_CR, cr);
|
ctrl_reg |= FLASH_CR_START;
|
||||||
cr |= FLASH_CR_START;
|
target_mem_write32(t, sf->regbase + FLASH_CR, ctrl_reg);
|
||||||
target_mem_write32(t, sf->regbase + FLASH_CR, cr);
|
|
||||||
DEBUG_INFO(" started cr %08" PRIx32 " sr %08" PRIx32 "\n",
|
DEBUG_INFO(" started cr %08" PRIx32 " sr %08" PRIx32 "\n",
|
||||||
target_mem_read32(t, sf->regbase + FLASH_CR),
|
target_mem_read32(t, sf->regbase + FLASH_CR),
|
||||||
target_mem_read32(t, sf->regbase + FLASH_SR));
|
target_mem_read32(t, sf->regbase + FLASH_SR));
|
||||||
|
uint32_t status = 0;
|
||||||
do {
|
do {
|
||||||
sr = target_mem_read32(t, sf->regbase + FLASH_SR);
|
status = target_mem_read32(t, sf->regbase + FLASH_SR);
|
||||||
if (target_check_error(t)) {
|
if (target_check_error(t)) {
|
||||||
DEBUG_WARN("stm32h7_flash_erase: comm failed\n");
|
DEBUG_WARN("stm32h7_flash_erase: comm failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// target_mem_write32(t, H7_IWDG_BASE, 0x0000aaaa);
|
// target_mem_write32(t, H7_IWDG_BASE, 0x0000aaaa);
|
||||||
}while (sr & (FLASH_SR_QW | FLASH_SR_BSY));
|
} while (status & (FLASH_SR_QW | FLASH_SR_BSY));
|
||||||
if (sr & FLASH_SR_ERROR_MASK) {
|
|
||||||
DEBUG_WARN("stm32h7_flash_erase: error, sr: %08" PRIx32 "\n", sr);
|
if (status & FLASH_SR_ERROR_MASK) {
|
||||||
|
DEBUG_WARN("stm32h7_flash_erase: error, sr: %08" PRIx32 "\n", status);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
start_sector++;
|
++start_sector;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stm32h7_flash_write(struct target_flash *f, target_addr dest,
|
static int stm32h7_flash_write(struct target_flash *f, target_addr dest, const void *src, size_t len)
|
||||||
const void *src, size_t len)
|
|
||||||
{
|
{
|
||||||
target *t = f->t;
|
target *t = f->t;
|
||||||
struct stm32h7_flash *sf = (struct stm32h7_flash *)f;
|
struct stm32h7_flash *sf = (struct stm32h7_flash *)f;
|
||||||
enum align psize = sf->psize;
|
enum align psize = sf->psize;
|
||||||
if (stm32h7_flash_unlock(t, dest) == false)
|
if (!stm32h7_flash_unlock(t, dest))
|
||||||
return -1;
|
return -1;
|
||||||
uint32_t cr = psize * FLASH_CR_PSIZE16;
|
uint32_t cr = psize * FLASH_CR_PSIZE16;
|
||||||
target_mem_write32(t, sf->regbase + FLASH_CR, cr);
|
target_mem_write32(t, sf->regbase + FLASH_CR, cr);
|
||||||
cr |= FLASH_CR_PG;
|
cr |= FLASH_CR_PG;
|
||||||
target_mem_write32(t, sf->regbase + FLASH_CR, cr);
|
target_mem_write32(t, sf->regbase + FLASH_CR, cr);
|
||||||
/* does H7 stall?*/
|
/* does H7 stall?*/
|
||||||
uint32_t sr_reg = sf->regbase + FLASH_SR;
|
uint32_t status_reg = sf->regbase + FLASH_SR;
|
||||||
uint32_t sr;
|
uint32_t status = 0;
|
||||||
target_mem_write(t, dest, src, len);
|
target_mem_write(t, dest, src, len);
|
||||||
while ((sr = target_mem_read32(t, sr_reg)) & FLASH_SR_BSY) {
|
while ((status = target_mem_read32(t, status_reg)) & FLASH_SR_BSY) {
|
||||||
if(target_check_error(t)) {
|
if(target_check_error(t)) {
|
||||||
DEBUG_WARN("stm32h7_flash_write: BSY comm failed\n");
|
DEBUG_WARN("stm32h7_flash_write: BSY comm failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sr & FLASH_SR_ERROR_MASK) {
|
if (status & FLASH_SR_ERROR_MASK) {
|
||||||
DEBUG_WARN("stm32h7_flash_write: error sr %08" PRIx32 "\n", sr);
|
DEBUG_WARN("stm32h7_flash_write: error sr %08" PRIx32 "\n", status);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
/* Close write windows.*/
|
/* Close write windows.*/
|
||||||
|
@ -355,97 +339,62 @@ static int stm32h7_flash_write(struct target_flash *f, target_addr dest,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Both banks are erased in parallel.*/
|
static bool stm32h7_erase_bank(target *const t, const enum align psize,
|
||||||
static bool stm32h7_cmd_erase(target *t, int bank_mask)
|
const uint32_t start_addr, const uint32_t reg_base)
|
||||||
{
|
{
|
||||||
const char spinner[] = "|/-\\";
|
if (!stm32h7_flash_unlock(t, start_addr)) {
|
||||||
int spinindex = 0;
|
DEBUG_WARN("mass erase: Unlock bank failed\n");
|
||||||
bool do_bank1 = bank_mask & 1, do_bank2 = bank_mask & 2;
|
return false;
|
||||||
uint32_t cr;
|
|
||||||
bool result = false;
|
|
||||||
enum align psize = ALIGN_DWORD;
|
|
||||||
for (struct target_flash *f = t->flash; f; f = f->next) {
|
|
||||||
if (f->write == stm32h7_flash_write) {
|
|
||||||
psize = ((struct stm32h7_flash *)f)->psize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cr = (psize * FLASH_CR_PSIZE16) | FLASH_CR_BER | FLASH_CR_START;
|
/* BER and start can be merged (3.3.10).*/
|
||||||
/* Flash mass erase start instruction */
|
const uint32_t ctrl_reg = (psize * FLASH_CR_PSIZE16) | FLASH_CR_BER | FLASH_CR_START;
|
||||||
if (do_bank1) {
|
target_mem_write32(t, reg_base + FLASH_CR, ctrl_reg);
|
||||||
if (stm32h7_flash_unlock(t, BANK1_START) == false) {
|
DEBUG_INFO("mass erase of bank started\n");
|
||||||
DEBUG_WARN("ME: Unlock bank1 failed\n");
|
return true;
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
uint32_t regbase = FPEC1_BASE;
|
|
||||||
/* BER and start can be merged (3.3.10).*/
|
|
||||||
target_mem_write32(t, regbase + FLASH_CR, cr);
|
|
||||||
DEBUG_INFO("ME bank1 started\n");
|
|
||||||
}
|
|
||||||
if (do_bank2) {
|
|
||||||
if (stm32h7_flash_unlock(t, BANK2_START) == false) {
|
|
||||||
DEBUG_WARN("ME: Unlock bank2 failed\n");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
uint32_t regbase = FPEC2_BASE;
|
|
||||||
/* BER and start can be merged (3.3.10).*/
|
|
||||||
target_mem_write32(t, regbase + FLASH_CR, cr);
|
|
||||||
DEBUG_INFO("ME bank2 started\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read FLASH_SR to poll for QW bit */
|
|
||||||
if (do_bank1) {
|
|
||||||
uint32_t regbase = FPEC1_BASE;
|
|
||||||
while (target_mem_read32(t, regbase + FLASH_SR) & FLASH_SR_QW) {
|
|
||||||
// target_mem_write32(t, H7_IWDG_BASE, 0x0000aaaa);
|
|
||||||
tc_printf(t, "\b%c", spinner[spinindex++ % 4]);
|
|
||||||
if(target_check_error(t)) {
|
|
||||||
DEBUG_WARN("ME bank1: comm failed\n");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (do_bank2) {
|
|
||||||
uint32_t regbase = FPEC2_BASE;
|
|
||||||
while (target_mem_read32(t, regbase + FLASH_SR) & FLASH_SR_QW) {
|
|
||||||
// target_mem_write32(t, H7_IWDG_BASE 0x0000aaaa);
|
|
||||||
tc_printf(t, "\b%c", spinner[spinindex++ % 4]);
|
|
||||||
if(target_check_error(t)) {
|
|
||||||
DEBUG_WARN("ME bank2: comm failed\n");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (do_bank1) {
|
|
||||||
/* Check for error */
|
|
||||||
uint32_t regbase = FPEC1_BASE;
|
|
||||||
uint32_t sr = target_mem_read32(t, regbase + FLASH_SR);
|
|
||||||
if (sr & FLASH_SR_ERROR_MASK) {
|
|
||||||
DEBUG_WARN("ME bank1, error sr %" PRIx32 "\n", sr);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (do_bank2) {
|
|
||||||
/* Check for error */
|
|
||||||
uint32_t regbase = FPEC2_BASE;
|
|
||||||
uint32_t sr = target_mem_read32(t, regbase + FLASH_SR);
|
|
||||||
if (sr & FLASH_SR_ERROR_MASK) {
|
|
||||||
DEBUG_WARN("ME bank2, error: sr %" PRIx32 "\n", sr);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = true;
|
|
||||||
done:
|
|
||||||
tc_printf(t, "\n");
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool stm32h7_cmd_erase_mass(target *t, int argc, const char **argv)
|
static bool stm32h7_wait_erase_bank(target *const t, platform_timeout *timeout, const uint32_t reg_base)
|
||||||
{
|
{
|
||||||
(void)argc;
|
while (target_mem_read32(t, reg_base + FLASH_SR) & FLASH_SR_QW) {
|
||||||
(void)argv;
|
if (target_check_error(t)) {
|
||||||
tc_printf(t, "Erasing flash... This may take a few seconds. ");
|
DEBUG_WARN("mass erase bank: comm failed\n");
|
||||||
return stm32h7_cmd_erase(t, 3);
|
return false;
|
||||||
|
}
|
||||||
|
target_print_progress(timeout);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stm32h7_check_bank(target *const t, const uint32_t reg_base)
|
||||||
|
{
|
||||||
|
uint32_t status = target_mem_read32(t, reg_base + FLASH_SR);
|
||||||
|
if (status & FLASH_SR_ERROR_MASK)
|
||||||
|
DEBUG_WARN("mass erase bank: error sr %" PRIx32 "\n", status);
|
||||||
|
return !(status & FLASH_SR_ERROR_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Both banks are erased in parallel.*/
|
||||||
|
static bool stm32h7_mass_erase(target *t)
|
||||||
|
{
|
||||||
|
enum align psize = ALIGN_DWORD;
|
||||||
|
for (struct target_flash *flash = t->flash; flash; flash = flash->next) {
|
||||||
|
if (flash->write == stm32h7_flash_write)
|
||||||
|
psize = ((struct stm32h7_flash *)flash)->psize;
|
||||||
|
}
|
||||||
|
/* Send mass erase Flash start instruction */
|
||||||
|
if (!stm32h7_erase_bank(t, psize, BANK1_START, FPEC1_BASE) ||
|
||||||
|
stm32h7_erase_bank(t, psize, BANK2_START, FPEC2_BASE))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
platform_timeout timeout;
|
||||||
|
platform_timeout_set(&timeout, 500);
|
||||||
|
/* Wait for the banks to finish erasing */
|
||||||
|
if (!stm32h7_wait_erase_bank(t, &timeout, FPEC1_BASE) ||
|
||||||
|
!stm32h7_wait_erase_bank(t, &timeout, FPEC2_BASE))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Check the banks for final errors */
|
||||||
|
return stm32h7_check_bank(t, FPEC1_BASE) && stm32h7_check_bank(t, FPEC2_BASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print the Unique device ID.
|
/* Print the Unique device ID.
|
||||||
|
|
Loading…
Reference in New Issue