static int mrvlqspi_flash_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { struct target *target = bank->target; struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv; int retval = ERROR_OK; uint32_t page_size, fifo_size; struct working_area *fifo; struct reg_param reg_params[6]; struct armv7m_algorithm armv7m_info; struct working_area *write_algorithm; int sector; LOG_DEBUG("offset=0x%08" PRIx32 " count=0x%08" PRIx32, offset, count); if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if (offset + count > mrvlqspi_info->dev->size_in_bytes) { LOG_WARNING("Writes past end of flash. Extra data discarded."); count = mrvlqspi_info->dev->size_in_bytes - offset; } /* Check sector protection */ for (sector = 0; sector < bank->num_sectors; sector++) { /* Start offset in or before this sector? */ /* End offset in or behind this sector? */ if ((offset < (bank->sectors[sector].offset + bank->sectors[sector].size)) && ((offset + count - 1) >= bank->sectors[sector].offset) && bank->sectors[sector].is_protected) { LOG_ERROR("Flash sector %d protected", sector); return ERROR_FAIL; } } page_size = mrvlqspi_info->dev->pagesize; /* See contrib/loaders/flash/mrvlqspi.S for src */ static const uint8_t mrvlqspi_flash_write_code[] = { 0x4f, 0xf0, 0x00, 0x0a, 0xa2, 0x44, 0x92, 0x45, 0x7f, 0xf6, 0xfc, 0xaf, 0x00, 0xf0, 0x6b, 0xf8, 0x5f, 0xf0, 0x01, 0x08, 0xc5, 0xf8, 0x1c, 0x80, 0x5f, 0xf0, 0x06, 0x08, 0xc5, 0xf8, 0x10, 0x80, 0x5f, 0xf0, 0x01, 0x09, 0x00, 0xf0, 0x6b, 0xf8, 0x00, 0xf0, 0x7d, 0xf8, 0x5f, 0xf0, 0x31, 0x08, 0xc5, 0xf8, 0x1c, 0x80, 0x90, 0x46, 0xc5, 0xf8, 0x14, 0x80, 0x5f, 0xf0, 0x02, 0x08, 0xc5, 0xf8, 0x10, 0x80, 0x5f, 0xf0, 0x01, 0x09, 0x00, 0xf0, 0x5a, 0xf8, 0xd0, 0xf8, 0x00, 0x80, 0xb8, 0xf1, 0x00, 0x0f, 0x00, 0xf0, 0x8b, 0x80, 0x47, 0x68, 0x47, 0x45, 0x3f, 0xf4, 0xf6, 0xaf, 0x17, 0xf8, 0x01, 0x9b, 0x00, 0xf0, 0x30, 0xf8, 0x8f, 0x42, 0x28, 0xbf, 0x00, 0xf1, 0x08, 0x07, 0x47, 0x60, 0x01, 0x3b, 0x00, 0x2b, 0x00, 0xf0, 0x05, 0x80, 0x02, 0xf1, 0x01, 0x02, 0x92, 0x45, 0x7f, 0xf4, 0xe4, 0xaf, 0x00, 0xf0, 0x50, 0xf8, 0xa2, 0x44, 0x00, 0xf0, 0x2d, 0xf8, 0x5f, 0xf0, 0x01, 0x08, 0xc5, 0xf8, 0x1c, 0x80, 0x5f, 0xf0, 0x00, 0x08, 0xc5, 0xf8, 0x20, 0x80, 0x5f, 0xf0, 0x05, 0x08, 0xc5, 0xf8, 0x10, 0x80, 0x5f, 0xf0, 0x00, 0x09, 0x00, 0xf0, 0x29, 0xf8, 0x00, 0xf0, 0x13, 0xf8, 0x09, 0xf0, 0x01, 0x09, 0xb9, 0xf1, 0x00, 0x0f, 0xf8, 0xd1, 0x00, 0xf0, 0x34, 0xf8, 0x00, 0x2b, 0xa4, 0xd1, 0x00, 0xf0, 0x53, 0xb8, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0x08, 0x68, 0xfa, 0xd4, 0xc5, 0xf8, 0x08, 0x90, 0x70, 0x47, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0xc8, 0x68, 0xfa, 0xd4, 0xd5, 0xf8, 0x0c, 0x90, 0x70, 0x47, 0xd5, 0xf8, 0x04, 0x80, 0x48, 0xf4, 0x00, 0x78, 0xc5, 0xf8, 0x04, 0x80, 0xd5, 0xf8, 0x04, 0x80, 0x5f, 0xea, 0x88, 0x58, 0xfa, 0xd4, 0x70, 0x47, 0xd5, 0xf8, 0x00, 0x80, 0x48, 0xf0, 0x01, 0x08, 0xc5, 0xf8, 0x00, 0x80, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0x88, 0x78, 0xfa, 0xd5, 0xd5, 0xf8, 0x04, 0x80, 0x69, 0xf3, 0x4d, 0x38, 0x48, 0xf4, 0x00, 0x48, 0xc5, 0xf8, 0x04, 0x80, 0x70, 0x47, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0x88, 0x78, 0xfa, 0xd5, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0x48, 0x68, 0xfa, 0xd5, 0xd5, 0xf8, 0x04, 0x80, 0x48, 0xf4, 0x80, 0x48, 0xc5, 0xf8, 0x04, 0x80, 0xd5, 0xf8, 0x04, 0x80, 0x5f, 0xea, 0x08, 0x48, 0xfa, 0xd4, 0xd5, 0xf8, 0x00, 0x80, 0x28, 0xf0, 0x01, 0x08, 0xc5, 0xf8, 0x00, 0x80, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0x88, 0x78, 0xfa, 0xd5, 0x70, 0x47, 0x00, 0x20, 0x50, 0x60, 0x30, 0x46, 0x00, 0xbe }; if (target_alloc_working_area(target, sizeof(mrvlqspi_flash_write_code), &write_algorithm) != ERROR_OK) { LOG_ERROR("Insufficient working area. You must configure"\ " a working area > %zdB in order to write to SPIFI flash.", sizeof(mrvlqspi_flash_write_code)); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; } retval = target_write_buffer(target, write_algorithm->address, sizeof(mrvlqspi_flash_write_code), mrvlqspi_flash_write_code); if (retval != ERROR_OK) { target_free_working_area(target, write_algorithm); return retval; } /* FIFO allocation */ fifo_size = target_get_working_area_avail(target); if (fifo_size == 0) { /* if we already allocated the writing code but failed to get fifo * space, free the algorithm */ target_free_working_area(target, write_algorithm); LOG_ERROR("Insufficient working area. Please allocate at least"\ " %zdB of working area to enable flash writes.", sizeof(mrvlqspi_flash_write_code) + 1 ); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; } else if (fifo_size < page_size) LOG_WARNING("Working area size is limited; flash writes may be"\ " slow. Increase working area size to at least %zdB"\ " to reduce write times.", (size_t)(sizeof(mrvlqspi_flash_write_code) + page_size) ); if (target_alloc_working_area(target, fifo_size, &fifo) != ERROR_OK) { target_free_working_area(target, write_algorithm); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; } armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; armv7m_info.core_mode = ARM_MODE_THREAD; init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* buffer start, status (out) */ init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* buffer end */ init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* target address */ init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* count (halfword-16bit) */ init_reg_param(®_params[4], "r4", 32, PARAM_OUT); /* page size */ init_reg_param(®_params[5], "r5", 32, PARAM_OUT); /* qspi base address */ buf_set_u32(reg_params[0].value, 0, 32, fifo->address); buf_set_u32(reg_params[1].value, 0, 32, fifo->address + fifo->size); buf_set_u32(reg_params[2].value, 0, 32, offset); buf_set_u32(reg_params[3].value, 0, 32, count); buf_set_u32(reg_params[4].value, 0, 32, page_size); buf_set_u32(reg_params[5].value, 0, 32, (uint32_t) mrvlqspi_info->reg_base); retval = target_run_flash_async_algorithm(target, buffer, count, 1, 0, NULL, 6, reg_params, fifo->address, fifo->size, write_algorithm->address, 0, &armv7m_info ); if (retval != ERROR_OK) LOG_ERROR("Error executing flash write algorithm"); target_free_working_area(target, fifo); target_free_working_area(target, write_algorithm); destroy_reg_param(®_params[0]); destroy_reg_param(®_params[1]); destroy_reg_param(®_params[2]); destroy_reg_param(®_params[3]); destroy_reg_param(®_params[4]); destroy_reg_param(®_params[5]); return retval; }
static int lpcspifi_write(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, uint32_t count) { struct target *target = bank->target; struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; uint32_t page_size, fifo_size; struct working_area *fifo; struct reg_param reg_params[5]; struct armv7m_algorithm armv7m_info; struct working_area *write_algorithm; int sector; int retval = ERROR_OK; LOG_DEBUG("offset=0x%08" PRIx32 " count=0x%08" PRIx32, offset, count); if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if (offset + count > lpcspifi_info->dev->size_in_bytes) { LOG_WARNING("Writes past end of flash. Extra data discarded."); count = lpcspifi_info->dev->size_in_bytes - offset; } /* Check sector protection */ for (sector = 0; sector < bank->num_sectors; sector++) { /* Start offset in or before this sector? */ /* End offset in or behind this sector? */ if ((offset < (bank->sectors[sector].offset + bank->sectors[sector].size)) && ((offset + count - 1) >= bank->sectors[sector].offset) && bank->sectors[sector].is_protected) { LOG_ERROR("Flash sector %d protected", sector); return ERROR_FAIL; } } page_size = lpcspifi_info->dev->pagesize; retval = lpcspifi_set_hw_mode(bank); if (retval != ERROR_OK) return retval; /* see contrib/loaders/flash/lpcspifi_write.S for src */ static const uint8_t lpcspifi_flash_write_code[] = { 0x4f, 0xf4, 0xc0, 0x4a, 0xc4, 0xf2, 0x08, 0x0a, 0x4f, 0xf0, 0xea, 0x08, 0xca, 0xf8, 0x8c, 0x81, 0x4f, 0xf0, 0x40, 0x08, 0xca, 0xf8, 0x90, 0x81, 0x4f, 0xf0, 0x40, 0x08, 0xca, 0xf8, 0x94, 0x81, 0x4f, 0xf0, 0xed, 0x08, 0xca, 0xf8, 0x98, 0x81, 0x4f, 0xf0, 0xed, 0x08, 0xca, 0xf8, 0x9c, 0x81, 0x4f, 0xf0, 0x44, 0x08, 0xca, 0xf8, 0xa0, 0x81, 0x4f, 0xf4, 0xc0, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, 0x4f, 0xf4, 0x00, 0x68, 0xca, 0xf8, 0x14, 0x80, 0x4f, 0xf4, 0x80, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, 0x4f, 0xf0, 0xff, 0x08, 0xca, 0xf8, 0xab, 0x80, 0x4f, 0xf0, 0x00, 0x0a, 0xc4, 0xf2, 0x05, 0x0a, 0x4f, 0xf0, 0x00, 0x08, 0xc0, 0xf2, 0x00, 0x18, 0xca, 0xf8, 0x94, 0x80, 0x4f, 0xf4, 0x00, 0x5a, 0xc4, 0xf2, 0x05, 0x0a, 0x4f, 0xf0, 0x01, 0x08, 0xca, 0xf8, 0x00, 0x87, 0x4f, 0xf4, 0x40, 0x5a, 0xc4, 0xf2, 0x08, 0x0a, 0x4f, 0xf0, 0x07, 0x08, 0xca, 0xf8, 0x00, 0x80, 0x4f, 0xf0, 0x02, 0x08, 0xca, 0xf8, 0x10, 0x80, 0xca, 0xf8, 0x04, 0x80, 0x4f, 0xf0, 0x00, 0x0b, 0xa3, 0x44, 0x93, 0x45, 0x7f, 0xf6, 0xfc, 0xaf, 0x00, 0xf0, 0x6a, 0xf8, 0x4f, 0xf0, 0x06, 0x09, 0x00, 0xf0, 0x53, 0xf8, 0x00, 0xf0, 0x60, 0xf8, 0x00, 0xf0, 0x62, 0xf8, 0x4f, 0xf0, 0x05, 0x09, 0x00, 0xf0, 0x4b, 0xf8, 0x4f, 0xf0, 0x00, 0x09, 0x00, 0xf0, 0x47, 0xf8, 0x00, 0xf0, 0x54, 0xf8, 0x19, 0xf0, 0x02, 0x0f, 0x00, 0xf0, 0x5d, 0x80, 0x00, 0xf0, 0x52, 0xf8, 0x4f, 0xf0, 0x02, 0x09, 0x00, 0xf0, 0x3b, 0xf8, 0x4f, 0xea, 0x12, 0x49, 0x00, 0xf0, 0x37, 0xf8, 0x4f, 0xea, 0x12, 0x29, 0x00, 0xf0, 0x33, 0xf8, 0x4f, 0xea, 0x02, 0x09, 0x00, 0xf0, 0x2f, 0xf8, 0xd0, 0xf8, 0x00, 0x80, 0xb8, 0xf1, 0x00, 0x0f, 0x00, 0xf0, 0x47, 0x80, 0x47, 0x68, 0x47, 0x45, 0x3f, 0xf4, 0xf6, 0xaf, 0x17, 0xf8, 0x01, 0x9b, 0x00, 0xf0, 0x21, 0xf8, 0x8f, 0x42, 0x28, 0xbf, 0x00, 0xf1, 0x08, 0x07, 0x47, 0x60, 0x01, 0x3b, 0xbb, 0xb3, 0x02, 0xf1, 0x01, 0x02, 0x93, 0x45, 0x7f, 0xf4, 0xe6, 0xaf, 0x00, 0xf0, 0x22, 0xf8, 0xa3, 0x44, 0x00, 0xf0, 0x23, 0xf8, 0x4f, 0xf0, 0x05, 0x09, 0x00, 0xf0, 0x0c, 0xf8, 0x4f, 0xf0, 0x00, 0x09, 0x00, 0xf0, 0x08, 0xf8, 0x00, 0xf0, 0x15, 0xf8, 0x19, 0xf0, 0x01, 0x0f, 0x7f, 0xf4, 0xf0, 0xaf, 0xff, 0xf7, 0xa7, 0xbf, 0x4f, 0xf4, 0x40, 0x5a, 0xc4, 0xf2, 0x08, 0x0a, 0xca, 0xf8, 0x08, 0x90, 0xda, 0xf8, 0x0c, 0x90, 0x19, 0xf0, 0x10, 0x0f, 0x7f, 0xf4, 0xfa, 0xaf, 0xda, 0xf8, 0x08, 0x90, 0x70, 0x47, 0x4f, 0xf0, 0xff, 0x08, 0x00, 0xf0, 0x02, 0xb8, 0x4f, 0xf0, 0x00, 0x08, 0x4f, 0xf4, 0x80, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, 0xca, 0xf8, 0xab, 0x80, 0x70, 0x47, 0x00, 0x20, 0x50, 0x60, 0x30, 0x46, 0x00, 0xbe, 0xff, 0xff }; if (target_alloc_working_area(target, sizeof(lpcspifi_flash_write_code), &write_algorithm) != ERROR_OK) { LOG_ERROR("Insufficient working area. You must configure"\ " a working area > %zdB in order to write to SPIFI flash.", sizeof(lpcspifi_flash_write_code)); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; }; retval = target_write_buffer(target, write_algorithm->address, sizeof(lpcspifi_flash_write_code), lpcspifi_flash_write_code); if (retval != ERROR_OK) { target_free_working_area(target, write_algorithm); return retval; } /* FIFO allocation */ fifo_size = target_get_working_area_avail(target); if (fifo_size == 0) { /* if we already allocated the writing code but failed to get fifo * space, free the algorithm */ target_free_working_area(target, write_algorithm); LOG_ERROR("Insufficient working area. Please allocate at least"\ " %zdB of working area to enable flash writes.", sizeof(lpcspifi_flash_write_code) + 1 ); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; } else if (fifo_size < page_size) LOG_WARNING("Working area size is limited; flash writes may be"\ " slow. Increase working area size to at least %zdB"\ " to reduce write times.", (size_t)(sizeof(lpcspifi_flash_write_code) + page_size) ); else if (fifo_size > 0x2000) /* Beyond this point, we start to get diminishing returns */ fifo_size = 0x2000; if (target_alloc_working_area(target, fifo_size, &fifo) != ERROR_OK) { target_free_working_area(target, write_algorithm); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; }; armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; armv7m_info.core_mode = ARM_MODE_THREAD; init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* buffer start, status (out) */ init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* buffer end */ init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* target address */ init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* count (halfword-16bit) */ init_reg_param(®_params[4], "r4", 32, PARAM_OUT); /* page size */ buf_set_u32(reg_params[0].value, 0, 32, fifo->address); buf_set_u32(reg_params[1].value, 0, 32, fifo->address + fifo->size); buf_set_u32(reg_params[2].value, 0, 32, offset); buf_set_u32(reg_params[3].value, 0, 32, count); buf_set_u32(reg_params[4].value, 0, 32, page_size); retval = target_run_flash_async_algorithm(target, buffer, count, 1, 0, NULL, 5, reg_params, fifo->address, fifo->size, write_algorithm->address, 0, &armv7m_info ); if (retval != ERROR_OK) LOG_ERROR("Error executing flash write algorithm"); target_free_working_area(target, fifo); target_free_working_area(target, write_algorithm); destroy_reg_param(®_params[0]); destroy_reg_param(®_params[1]); destroy_reg_param(®_params[2]); destroy_reg_param(®_params[3]); destroy_reg_param(®_params[4]); /* Switch to HW mode before return to prompt */ retval = lpcspifi_set_hw_mode(bank); return retval; }
static int fm4_flash_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t byte_count) { struct target *target = bank->target; struct working_area *code_workarea, *data_workarea; struct reg_param reg_params[6]; struct armv7m_algorithm armv7m_algo; uint32_t halfword_count = DIV_ROUND_UP(byte_count, 2); uint32_t result; unsigned i; int retval; const uint8_t write_block_code[] = { #include "../../../contrib/loaders/flash/fm4/write.inc" }; LOG_DEBUG("Spansion FM4 write at 0x%08" PRIx32 " (%" PRId32 " bytes)", offset, byte_count); if (offset & 0x1) { LOG_ERROR("offset 0x%" PRIx32 " breaks required 2-byte alignment", offset); return ERROR_FLASH_DST_BREAKS_ALIGNMENT; } if (byte_count & 0x1) { LOG_WARNING("length %" PRId32 " is not 2-byte aligned, rounding up", byte_count); } if (target->state != TARGET_HALTED) { LOG_WARNING("Cannot communicate... target not halted."); return ERROR_TARGET_NOT_HALTED; } retval = fm4_disable_hw_watchdog(target); if (retval != ERROR_OK) return retval; retval = target_alloc_working_area(target, sizeof(write_block_code), &code_workarea); if (retval != ERROR_OK) { LOG_ERROR("No working area available for write code."); return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; } retval = target_write_buffer(target, code_workarea->address, sizeof(write_block_code), write_block_code); if (retval != ERROR_OK) goto err_write_code; retval = target_alloc_working_area(target, MIN(halfword_count * 2, target_get_working_area_avail(target)), &data_workarea); if (retval != ERROR_OK) { LOG_ERROR("No working area available for write data."); retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; goto err_alloc_data; } armv7m_algo.common_magic = ARMV7M_COMMON_MAGIC; armv7m_algo.core_mode = ARM_MODE_THREAD; init_reg_param(®_params[0], "r0", 32, PARAM_OUT); init_reg_param(®_params[1], "r1", 32, PARAM_OUT); init_reg_param(®_params[2], "r2", 32, PARAM_OUT); init_reg_param(®_params[3], "r3", 32, PARAM_OUT); init_reg_param(®_params[4], "r4", 32, PARAM_OUT); init_reg_param(®_params[5], "r5", 32, PARAM_IN); retval = fm4_enter_flash_cpu_programming_mode(target); if (retval != ERROR_OK) goto err_flash_mode; while (byte_count > 0) { uint32_t halfwords = MIN(halfword_count, data_workarea->size / 2); uint32_t addr = bank->base + offset; LOG_DEBUG("copying %" PRId32 " bytes to SRAM 0x%08" PRIx32, MIN(halfwords * 2, byte_count), data_workarea->address); retval = target_write_buffer(target, data_workarea->address, MIN(halfwords * 2, byte_count), buffer); if (retval != ERROR_OK) { LOG_ERROR("Error writing data buffer"); retval = ERROR_FLASH_OPERATION_FAILED; goto err_write_data; } LOG_DEBUG("writing 0x%08" PRIx32 "-0x%08" PRIx32 " (%" PRId32 "x)", addr, addr + halfwords * 2 - 1, halfwords); buf_set_u32(reg_params[0].value, 0, 32, (addr & ~0xffff) | 0xAA8); buf_set_u32(reg_params[1].value, 0, 32, (addr & ~0xffff) | 0x554); buf_set_u32(reg_params[2].value, 0, 32, addr); buf_set_u32(reg_params[3].value, 0, 32, data_workarea->address); buf_set_u32(reg_params[4].value, 0, 32, halfwords); retval = target_run_algorithm(target, 0, NULL, ARRAY_SIZE(reg_params), reg_params, code_workarea->address, 0, 5 * 60 * 1000, &armv7m_algo); if (retval != ERROR_OK) { LOG_ERROR("Error executing flash sector erase " "programming algorithm"); retval = ERROR_FLASH_OPERATION_FAILED; goto err_run; } result = buf_get_u32(reg_params[5].value, 0, 32); if (result == 2) { LOG_ERROR("Timeout error from flash write " "programming algorithm"); retval = ERROR_FLASH_OPERATION_FAILED; goto err_run_ret; } else if (result != 0) { LOG_ERROR("Unexpected error %d from flash write " "programming algorithm", result); retval = ERROR_FLASH_OPERATION_FAILED; goto err_run_ret; } else retval = ERROR_OK; halfword_count -= halfwords; offset += halfwords * 2; buffer += halfwords * 2; byte_count -= MIN(halfwords * 2, byte_count); } err_run_ret: err_run: err_write_data: retval = fm4_enter_flash_cpu_rom_mode(target); err_flash_mode: for (i = 0; i < ARRAY_SIZE(reg_params); i++) destroy_reg_param(®_params[i]); target_free_working_area(target, data_workarea); err_alloc_data: err_write_code: target_free_working_area(target, code_workarea); return retval; }