Professional Documents
Culture Documents
Driver Cointerra
Driver Cointerra
#include "config.h"
#include "miner.h"
#include "driver-cointerra.h"
int opt_ps_load;
diff /= 0.9999847412109375;
diff *= (double)2147483648.0;
if (diff > 0x8000000000000000ULL)
diff = 0x8000000000000000ULL;
/* Convert it to an integer */
diff64 = diff;
for (i = 0; diff64; i++, diff64 >>= 1);
return i;
}
if (cointerra->usbinfo.nodev)
return false;
applog(LOG_INFO, "CTA_OPEN");
cta_gen_message(buf, CTA_SEND_RESET);
// set the initial difficulty
buf[CTA_RESET_TYPE] = CTA_RESET_INIT | CTA_RESET_DIFF;
buf[CTA_RESET_DIFF] = diff_to_bits(CTA_INIT_DIFF);
buf[CTA_RESET_LOAD] = opt_cta_load ? opt_cta_load : 255;
buf[CTA_RESET_PSLOAD] = opt_ps_load;
if (cointerra->usbinfo.nodev)
return ret;
cgtimer_time(&ts_start);
/* Read from the device for up to 2 seconds discarding any data that
* doesn't match a reset complete acknowledgement. */
while (42) {
cgtimer_t ts_now, ts_diff;
char *msg;
cgtimer_time(&ts_now);
cgtimer_sub(&ts_now, &ts_start, &ts_diff);
if (cgtimer_to_ms(&ts_diff) > 2000) {
applog(LOG_DEBUG, "%s %d: Timed out waiting for response to reset
init",
cointerra->drv->name, cointerra->device_id);
break;
}
if (cointerra->usbinfo.nodev)
break;
return ret;
}
wr_lock(&cgpu->qlock);
HASH_ITER(hh, cgpu->queued_work, work, tmp) {
__work_completed(cgpu, work);
free_work(work);
}
wr_unlock(&cgpu->qlock);
}
mutex_destroy(&info->lock);
mutex_destroy(&info->sendlock);
/* Don't free info here to avoid trying to access dereferenced members
* once a device is unplugged. */
cta_clear_work(cointerra);
}
if (!add_cgpu(cointerra))
goto fail_close;
update_usb_stats(cointerra);
applog(LOG_INFO, "%s %d: Successfully set up %s", cointerra->drv->name,
cointerra->device_id, cointerra->device_path);
return cointerra;
fail_close:
cta_close(cointerra);
failed_open:
applog(LOG_INFO, "%s %d: Failed to initialise %s", cointerra->drv->name,
cointerra->device_id, cointerra->device_path);
fail:
usb_free_cgpu(cointerra);
return NULL;
}
/* This function will remove a work item from the hashtable if it matches the
* id in work->subid and return a pointer to the work but it will not free the
* work. It may return NULL if it cannot find matching work. */
static struct work *take_work_by_id(struct cgpu_info *cgpu, uint16_t id)
{
struct work *work, *tmp, *ret = NULL;
wr_lock(&cgpu->qlock);
HASH_ITER(hh, cgpu->queued_work, work, tmp) {
if (work->subid == id) {
ret = work;
break;
}
}
if (ret)
__work_completed(cgpu, ret);
wr_unlock(&cgpu->qlock);
return ret;
}
/* This function will look up a work item in the hashtable if it matches the
* id in work->subid and return a cloned work item if it matches. It may return
* NULL if it cannot find matching work. */
static struct work *clone_work_by_id(struct cgpu_info *cgpu, uint16_t id)
{
struct work *work, *tmp, *ret = NULL;
rd_lock(&cgpu->qlock);
HASH_ITER(hh, cgpu->queued_work, work, tmp) {
if (work->subid == id) {
ret = work;
break;
}
}
if (ret)
ret = copy_work(ret);
rd_unlock(&cgpu->qlock);
return ret;
}
mutex_lock(&info->lock);
info->requested = retwork;
/* Wake up the main scanwork loop since we need more
* work. */
pthread_cond_signal(&info->wake_cond);
mutex_unlock(&info->lock);
}
/* No endian switch needs doing here since it's sent and returned as
* the same 4 bytes */
retwork = *(uint16_t *)(&buf[CTA_DRIVER_TAG]);
mcu_tag = hu32_from_msg(buf, CTA_MCU_TAG);
applog(LOG_DEBUG, "%s %d: Match message for id 0x%04x MCU id 0x%08x
received",
cointerra->drv->name, cointerra->device_id, retwork, mcu_tag);
/* Test against the difficulty we asked for along with the work */
wdiff = bits_to_diff(wdiffbits);
ret = test_nonce_diff(work, nonce, wdiff);
if (opt_debug) {
/* Debugging, remove me */
swab256(rhash, work->hash);
__bin2hex(outhash, rhash, 8);
applog(LOG_WARNING, "submit work %s 0x%04x 0x%08x %d 0x%08x",
outhash, retwork, mcu_tag, timestamp_offset, nonce);
}
if (likely(ret)) {
uint8_t asic, core, pipe, coreno;
int pipeno, bitchar, bitbit;
uint64_t hashes;
mutex_lock(&info->lock);
info->no_matching_work++;
mutex_unlock(&info->lock);
}
}
if (likely(work)) {
free_work(work);
applog(LOG_DEBUG, "%s %d: Done work found id 0x%X %d",
cointerra->drv->name, cointerra->device_id, retwork, __LINE__);
} else {
applog(LOG_WARNING, "%s %d: Done work not found id 0x%X %d",
cointerra->drv->name, cointerra->device_id, retwork, __LINE__);
inc_hw_errors(thr);
}
mutex_lock(&info->lock);
info->hashes += hashes;
mutex_unlock(&info->lock);
}
static void u16array_from_msg(uint16_t *u16, int entries, int var, char *buf)
{
int i, j;
mutex_lock(&info->lock);
u16array_from_msg(info->coretemp, CTA_CORES, CTA_STAT_CORETEMPS, buf);
info->ambtemp_low = hu16_from_msg(buf, CTA_STAT_AMBTEMP_LOW);
info->ambtemp_avg = hu16_from_msg(buf, CTA_STAT_AMBTEMP_AVG);
info->ambtemp_high = hu16_from_msg(buf, CTA_STAT_AMBTEMP_HIGH);
u16array_from_msg(info->pump_tachs, CTA_PUMPS, CTA_STAT_PUMP_TACHS, buf);
u16array_from_msg(info->fan_tachs, CTA_FANS, CTA_STAT_FAN_TACHS, buf);
u16array_from_msg(info->corevolts, CTA_CORES, CTA_STAT_CORE_VOLTS, buf);
info->volts33 = hu16_from_msg(buf, CTA_STAT_VOLTS33);
info->volts12 = hu16_from_msg(buf, CTA_STAT_VOLTS12);
info->inactive = hu16_from_msg(buf, CTA_STAT_INACTIVE);
info->active = hu16_from_msg(buf, CTA_STAT_ACTIVE);
mutex_unlock(&info->lock);
static void u8array_from_msg(uint8_t *u8, int entries, int var, char *buf)
{
int i;
mutex_lock(&info->lock);
info->irstat_vin[channel] = hu16_from_msg(buf,CTA_IRSTAT_VIN);
info->irstat_iin[channel] = hu16_from_msg(buf,CTA_IRSTAT_IIN);
info->irstat_vout[channel] = hu16_from_msg(buf,CTA_IRSTAT_VOUT);
info->irstat_iout[channel] = hu16_from_msg(buf,CTA_IRSTAT_IOUT);
info->irstat_temp1[channel] = hu16_from_msg(buf,CTA_IRSTAT_TEMP1);
info->irstat_temp2[channel] = hu16_from_msg(buf,CTA_IRSTAT_TEMP2);
info->irstat_pout[channel] = hu16_from_msg(buf,CTA_IRSTAT_POUT);
info->irstat_pin[channel] = hu16_from_msg(buf,CTA_IRSTAT_PIN);
info->irstat_efficiency[channel] = hu16_from_msg(buf,CTA_IRSTAT_EFF);
info->irstat_status[channel] = hu16_from_msg(buf,CTA_IRSTAT_STATUS);
mutex_unlock(&info->lock);
}
if (!cointerra->unique_id) {
uint32_t b32 = htobe32(info->serial);
reset_type = buf[CTA_RESET_TYPE];
diffbits = buf[CTA_RESET_DIFF];
wdone = hu64_from_msg(buf, CTA_WDONE_NONCES);
if (wdone) {
applog(LOG_INFO, "%s %d: Reset done type %u message %u diffbits
%"PRIu64" done received",
cointerra->drv->name, cointerra->device_id, reset_type, diffbits,
wdone);
mutex_lock(&info->lock);
info->hashes += wdone;
mutex_unlock(&info->lock);
}
/* Note that the cgsem that is posted here must not be waited on while
* holding the info->lock to not get into a livelock since this
* function also grabs the lock first and it's always best to not sleep
* while holding a lock. */
if (reset_type == CTA_RESET_NEW) {
cta_clear_work(cointerra);
/* Tell reset sender that the reset is complete
* and it may resume. */
cgsem_post(&info->reset_sem);
}
}
mutex_unlock(&info->lock);
info->autovoltage_complete = true;
cgtime(&cointerra->dev_start_tv);
cta_zero_stats(cointerra);
cointerra->total_mhashes = 0;
cointerra->accepted = 0;
cointerra->rejected = 0;
cointerra->hw_errors = 0;
cointerra->utility = 0.0;
cointerra->last_share_pool_time = 0;
cointerra->diff1 = 0;
cointerra->diff_accepted = 0;
cointerra->diff_rejected = 0;
cointerra->last_share_diff = 0;
}
}
switch (buf[CTA_MSG_TYPE]) {
default:
case CTA_RECV_UNUSED:
applog(LOG_INFO, "%s %d: Unidentified message type %u",
cointerra->drv->name, cointerra->device_id,
buf[CTA_MSG_TYPE]);
break;
case CTA_RECV_REQWORK:
cta_parse_reqwork(cointerra, info, buf);
break;
case CTA_RECV_MATCH:
cta_parse_recvmatch(thr, cointerra, info, buf);
break;
case CTA_RECV_WDONE:
applog(LOG_DEBUG, "%s %d: Work done message received",
cointerra->drv->name, cointerra->device_id);
cta_parse_wdone(thr, cointerra, info, buf);
break;
case CTA_RECV_STATREAD:
applog(LOG_DEBUG, "%s %d: Status readings message received",
cointerra->drv->name, cointerra->device_id);
cta_parse_statread(cointerra, info, buf);
break;
case CTA_RECV_STATSET:
applog(LOG_DEBUG, "%s %d: Status settings message received",
cointerra->drv->name, cointerra->device_id);
cta_parse_statset(info, buf);
break;
case CTA_RECV_INFO:
applog(LOG_DEBUG, "%s %d: Info message received",
cointerra->drv->name, cointerra->device_id);
cta_parse_info(cointerra, info, buf);
break;
case CTA_RECV_MSG:
applog(LOG_NOTICE, "%s %d: MSG: %s",
cointerra->drv->name, cointerra->device_id,
&buf[CTA_MSG_RECVD]);
break;
case CTA_RECV_RDONE:
cta_parse_rdone(cointerra, info, buf);
break;
case CTA_RECV_STATDEBUG:
cta_parse_debug(info, buf);
break;
case CTA_RECV_IRSTAT:
cta_parse_irstat(info, buf);
break;
}
}
while (likely(!cointerra->shutdown)) {
char buf[CTA_READBUF_SIZE];
int amount, err;
if (unlikely(cointerra->usbinfo.nodev)) {
applog(LOG_DEBUG, "%s %d: Device disappeared, disabling recv
thread",
cointerra->drv->name, cointerra->device_id);
break;
}
if (unlikely(!msg)) {
applog(LOG_WARNING, "%s %d: No message header found,
discarding buffer",
cointerra->drv->name, cointerra->device_id);
inc_hw_errors(thr);
/* Save the last byte in case it's the fist
* byte of a header. */
begin = CTA_MSG_SIZE - 1;
offset -= begin;
memmove(buf, buf + begin, offset);
continue;
}
if (unlikely(msg != buf)) {
begin = msg - buf;
applog(LOG_WARNING, "%s %d: Reads out of sync, discarding
%d bytes",
cointerra->drv->name, cointerra->device_id, begin);
inc_hw_errors(thr);
offset -= begin;
memmove(buf, msg, offset);
if (offset < CTA_MSG_SIZE)
break;
}
return NULL;
}
if (unlikely(cointerra->usbinfo.nodev))
return false;
if (unlikely(cointerra->usbinfo.nodev))
return false;
if (unlikely(!info))
quit(1, "Failed to calloc info in cta_detect_one");
cointerra->device_data = info;
/* Nominally set a requested value when starting, preempting the need
* for a req-work message. */
info->requested = CTA_MAX_QUEUE;
info->thr = thr;
mutex_init(&info->lock);
mutex_init(&info->sendlock);
if (unlikely(pthread_cond_init(&info->wake_cond, NULL)))
quit(1, "Failed to create cta pthread cond");
cgsem_init(&info->reset_sem);
if (pthread_create(&info->read_thr, NULL, cta_recv_thread, (void *)thr))
quit(1, "Failed to create cta_recv_thread");
return true;
}
if (unlikely(info->thr->work_restart))
cta_flush_work(cointerra);
mutex_lock(&info->lock);
if (!info->requested)
goto out_unlock;
work = get_queued(cointerra);
if (unlikely(!work)) {
ret = false;
goto out_unlock;
}
if (--info->requested > 0)
ret = false;
diffbits = diff_to_bits(work->device_diff);
cta_gen_message(buf, CTA_SEND_WORK);
flip32(swab, work->midstate);
memcpy(buf + CTA_WORK_MIDSTATE, swab, 32);
flip12(swab, &work->data[64]);
memcpy(buf + CTA_WORK_DATA, swab, 12);
nroll_limit = htole16(work->drv_rolllimit);
memcpy(buf + CTA_WORK_NROLL, &nroll_limit, 2);
out_unlock:
mutex_unlock(&info->lock);
if (work) {
cgtime(&work->tv_work_start);
applog(LOG_DEBUG, "%s %d: Sending work job_id %s work_id %u",
cointerra->drv->name,
cointerra->device_id, work->job_id, work->subid);
if (unlikely(!cta_send_msg(cointerra, buf))) {
work_completed(cointerra, work);
applog(LOG_INFO, "%s %d: Failed to send work",
cointerra->drv->name, cointerra->device_id);
/* The device will fail after this */
}
}
return ret;
}
buf[CTA_RESET_TYPE] = reset_type;
buf[CTA_RESET_LOAD] = opt_cta_load ? opt_cta_load : 255;
buf[CTA_RESET_PSLOAD] = opt_ps_load;
applog(LOG_INFO, "%s %d: Sending Reset type %u with diffbits %u", cointerra-
>drv->name,
cointerra->device_id, reset_type, diffbits);
cta_send_msg(cointerra, buf);
/* Wait for read thread to parse a reset message and signal us we may
* return to submitting other messages. Use a timeout in case we have
* a problem and the reset done message never returns. */
if (reset_type == CTA_RESET_NEW) {
ret = cgsem_mswait(&info->reset_sem, CTA_RESET_TIMEOUT);
if (ret) {
if (++retries < 5) {
applog(LOG_INFO, "%s %d: Timed out waiting for reset done
msg, retrying",
cointerra->drv->name, cointerra->device_id);
goto resend;
}
applog(LOG_WARNING, "%s %d: Timed out waiting for reset done
msg",
cointerra->drv->name, cointerra->device_id);
}
/* Good place to flush any work we have */
flush_queue(cointerra);
}
}
if (unlikely(cointerra->usbinfo.nodev)) {
hashes = -1;
goto out;
}
cgtime(&now);
if (unlikely(thr->work_restart)) {
applog(LOG_INFO, "%s %d: Flush work line %d",
cointerra->drv->name, cointerra->device_id,__LINE__);
cta_flush_work(cointerra);
} else {
struct timespec abstime, tsdiff = {0, 500000000};
time_t now_t;
int i;
timeval_to_spec(&abstime, &now);
timeraddspec(&abstime, &tsdiff);
if (thr->work_restart) {
applog(LOG_INFO, "%s %d: Flush work line %d",
cointerra->drv->name, cointerra->device_id,__LINE__);
cta_flush_work(cointerra);
}
}
mutex_lock(&info->lock);
hashes = info->share_hashes;
info->tot_share_hashes += info->share_hashes;
info->tot_calc_hashes += info->hashes;
runtime = cgpu_runtime(thr->cgpu);
runtime /= 30;
info->old_hashes[runtime % 32] = info->tot_calc_hashes;
info->hashes = info->share_hashes = 0;
mutex_unlock(&info->lock);
if (unlikely(cointerra->usbinfo.nodev))
hashes = -1;
out:
return hashes;
}
/* This is used for a work restart. We don't actually perform the work restart
* here but wake up the scanwork loop if it's waiting on the conditional so
* that it can test for the restart message. */
static void cta_wake(struct cgpu_info *cointerra)
{
struct cointerra_info *info = cointerra->device_data;
mutex_lock(&info->lock);
pthread_cond_signal(&info->wake_cond);
mutex_unlock(&info->lock);
}
cta_close(cointerra);
}
info->tot_calc_hashes = 0;
info->tot_reset_hashes = info->tot_hashes;
info->tot_share_hashes = 0;
cta_zero_corehashes(info);
for (i = 0; i < 16 * 2; i++)
info->old_hashes[i] = 0;
}
for (c = 0; v; c++)
v &= v - 1;
return c;
}
/* Info data */
root = api_add_uint16(root, "HW Revision", &info->hwrev, false);
root = api_add_uint32(root, "Serial", &info->serial, false);
root = api_add_uint8(root, "Asics", &info->asics, false);
root = api_add_uint8(root, "Dies", &info->dies, false);
root = api_add_uint16(root, "Cores", &info->cores, false);
root = api_add_uint8(root, "Board number", &info->board_number, false);
sprintf(buf, "%u.%u.%u", info->fwrev[0], info->fwrev[1], info->fwrev[2]);
root = api_add_string(root, "FW Revision", buf, true);
sprintf(buf, "%04u-%02u-%02u", info->fw_year, info->fw_month, info->fw_day);
root = api_add_string(root, "FW Date", buf, true);
root = api_add_uint8(root, "Init diffbits", &info->init_diffbits, false);
root = api_add_uint8(root, "Min diffbits", &info->min_diffbits, false);
root = api_add_uint8(root, "Max diffbits", &info->max_diffbits, false);
/* Status readings */
for (i = 0; i < CTA_CORES; i++) {
sprintf(buf, "CoreTemp%d", i);
root = api_add_int16(root, buf, &info->coretemp[i], false);
}
root = api_add_int16(root, "Ambient Low", &info->ambtemp_low, false);
root = api_add_int16(root, "Ambient Avg", &info->ambtemp_avg, false);
root = api_add_int16(root, "Ambient High", &info->ambtemp_high, false);
for (i = 0; i < CTA_PUMPS; i++) {
sprintf(buf, "PumpRPM%d", i);
root = api_add_uint16(root, buf, &info->pump_tachs[i], false);
}
for (i = 0; i < CTA_FANS; i++) {
sprintf(buf, "FanRPM%d", i);
root = api_add_uint16(root, buf, &info->fan_tachs[i], false);
}
for (i = 0; i < CTA_CORES; i++) {
sprintf(buf, "CoreFreqs%d", i);
root = api_add_uint16(root, buf, &info->corefreqs[i], false);
}
/* Status settings */
for (i = 0; i < CTA_CORES; i++) {
sprintf(buf, "CorePerfMode%d", i);
root = api_add_uint8(root, buf, &info->coreperf[i], false);
}
for (i = 0; i < CTA_FANS; i++) {
sprintf(buf, "FanSpeed%d", i);
root = api_add_uint8(root, buf, &info->fanspeed[i], false);
}
root = api_add_uint8(root, "DiesActive", &info->dies_active, false);
for (i = 0; i < CTA_CORES; i++) {
sprintf(buf, "PipesEnabled%d", i);
root = api_add_uint8(root, buf, &info->pipes_enabled[i], false);
}
/* Status debug */
root = api_add_int(root, "Underruns", &info->tot_underruns, false);
for (i = 0; i < CTA_CORES; i++) {
sprintf(buf, "HWErrors%d", i);
root = api_add_uint16(root, buf, &info->tot_hw_errors[i], false);
}
ghs = info->tot_calc_hashes / dev_runtime;
root = api_add_uint64(root, "Calc hashrate", &ghs, true);
ghs = (info->tot_hashes - info->tot_reset_hashes) / dev_runtime;
root = api_add_uint64(root, "Hashrate", &ghs, true);
//root = api_add_uint64(root, "cgminer 15m Hashrate", &cgpu->rolling15,
true);
// get runtime in 30 second steps
runtime = runtime / 30;
// store the current hashes
info->old_hashes[runtime%32] = info->tot_calc_hashes;
// calc the 15 minute average hashrate
ghs = (info->old_hashes[(runtime+31)%32] - info-
>old_hashes[(runtime+1)%32])/(15*60);
root = api_add_uint64(root, "15m Hashrate", &ghs, true);
ghs = info->tot_share_hashes / dev_runtime;
root = api_add_uint64(root, "Share hashrate", &ghs, true);
root = api_add_uint64(root, "Total calc hashes", &info->tot_calc_hashes,
false);
ghs = info->tot_hashes - info->tot_reset_hashes;
root = api_add_uint64(root, "Total hashes", &ghs, true);
root = api_add_uint64(root, "Total raw hashes", &info->tot_hashes, false);
root = api_add_uint64(root, "Total share hashes", &info->tot_share_hashes,
false);
root = api_add_uint64(root, "Total flushed hashes", &info-
>tot_flushed_hashes, false);
val = cgpu->diff_accepted * 0x100000000ull;
root = api_add_uint64(root, "Accepted hashes", &val, true);
ghs = val / dev_runtime;
root = api_add_uint64(root, "Accepted hashrate", &ghs, true);
val = cgpu->diff_rejected * 0x100000000ull;
root = api_add_uint64(root, "Rejected hashes", &val, true);
ghs = val / dev_runtime;
root = api_add_uint64(root, "Rejected hashrate", &ghs, true);
cgtime(&now);
dev_runtime = tdiff(&now, &info->core_hash_start);
if (dev_runtime < 1)
dev_runtime = 1;
for (i = 0; i < CTA_CORES; i++) {
sprintf(buf, "Core%d hashrate", i);
ghs = info->tot_core_hashes[i] / dev_runtime;
root = api_add_uint64(root, buf, &ghs, true);
}
root = api_add_uint32(root, "Uptime",&info->uptime,false);
for (asic = 0; asic < 2; asic++) {
for (core = 0; core < 4; core++) {
char bitmapcount[40], asiccore[12];
int count = 0;
value *= (info->power_voltage/100.0);
root = api_add_double(root, "Power Used", &value, true);
}
root = api_add_uint16(root, "IOUT", &info->power_used, false);
root = api_add_uint16(root, "VOUT", &info->power_voltage, false);
root = api_add_uint16(root, "IIN", &info->ipower_used, false);
root = api_add_uint16(root, "VIN", &info->ipower_voltage, false);
root = api_add_uint16(root, "PSTemp1", &info->power_temps[0], false);
root = api_add_uint16(root, "PSTemp2", &info->power_temps[1], false);
//}
sprintf(name,"IRVIN%d",core+1);
value = info->irstat_vin[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRIIN%d",core+1);
value = info->irstat_iin[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRVOUT%d",core+1);
value = info->irstat_vout[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRIOUT%d",core+1);
value = info->irstat_iout[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRTEMP1_%d",core+1);
value = info->irstat_temp1[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRTEMP2_%d",core+1);
value = info->irstat_temp2[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRPOUT%d",core+1);
value = info->irstat_pout[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRPIN%d",core+1);
value = info->irstat_pin[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IREFFICIENCY%d",core+1);
value = info->irstat_efficiency[core]/100.0;
root = api_add_double(root,name,&value,true);
sprintf(name,"IRSTATUS%d",core+1);
//root = api_add_uint16(root,name,&info->irstat_status[core],false);
sprintf(str,"0x%04X",info->irstat_status[core]);
root = api_add_string(root, name, str, true);
}
return root;
}