Files
ardupilot/libraries/AP_Terrain/TerrainUtil.cpp
T
Thomas Watson 002e8da59a AP_Terrain: allocate cache as block linked list instead of array
It is difficult to find enough contiguous free heap space for a large
terrain cache when it is allocated as one large array. This is
especially problematic on STM32 with the variety of heaps across
different RAM regions.

The cache is only accessed by iterating through its blocks in order.
Therefore, using a singly-linked list of blocks is a natural fit as
there is no random access to penalize. The blocks are then allocated
independently so they can be allocated in different areas or regions as
heap availability dictates. There is an order-1% space overhead for the
`next` pointer and extra heap block headers.

This also fixes memory corruption if the user gives a zero or negative
cache size parameter then enables terrain.
2026-09-22 12:03:24 +10:00

188 lines
6.2 KiB
C++

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that 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/>.
*/
/*
handle disk IO for terrain code
*/
#include "AP_Terrain.h"
#if AP_TERRAIN_AVAILABLE
#include <AP_HAL/AP_HAL.h>
#include <AP_Common/AP_Common.h>
#include <AP_Math/AP_Math.h>
extern const AP_HAL::HAL& hal;
/*
calculate bit number in grid_block bitmap. This corresponds to a
bit representing a 4x4 mavlink transmitted block
*/
uint8_t AP_Terrain::grid_bitnum(uint8_t idx_x, uint8_t idx_y)
{
ASSERT_RANGE(idx_x,0,27);
ASSERT_RANGE(idx_y,0,31);
uint8_t subgrid_x = idx_x / TERRAIN_GRID_MAVLINK_SIZE;
uint8_t subgrid_y = idx_y / TERRAIN_GRID_MAVLINK_SIZE;
ASSERT_RANGE(subgrid_x,0,TERRAIN_GRID_BLOCK_MUL_X-1);
ASSERT_RANGE(subgrid_y,0,TERRAIN_GRID_BLOCK_MUL_Y-1);
return subgrid_y + TERRAIN_GRID_BLOCK_MUL_Y*subgrid_x;
}
/*
given a grid_info check that a given idx_x/idx_y is available (set
in the bitmap)
*/
bool AP_Terrain::check_bitmap(const struct grid_block &grid, uint8_t idx_x, uint8_t idx_y)
{
uint8_t bitnum = grid_bitnum(idx_x, idx_y);
return (grid.bitmap & (((uint64_t)1U)<<bitnum)) != 0;
}
/*
given a location, calculate the 32x28 grid SW corner, plus the
grid indices
*/
void AP_Terrain::calculate_grid_info(const Location &loc, struct grid_info &info) const
{
// grids start on integer degrees. This makes storing terrain data
// on the SD card a bit easier
info.lat_degrees = (loc.lat<0?(loc.lat-9999999L):loc.lat) / (10*1000*1000L);
info.lon_degrees = (loc.lng<0?(loc.lng-9999999L):loc.lng) / (10*1000*1000L);
// create reference position for this rounded degree position
Location ref;
ref.lat = info.lat_degrees*10*1000*1000L;
ref.lng = info.lon_degrees*10*1000*1000L;
// find offset from reference
const Vector2f offset = ref.get_distance_NE(loc);
// get indices in terms of grid_spacing elements
uint32_t idx_x = offset.x / grid_spacing;
uint32_t idx_y = offset.y / grid_spacing;
// find indexes into 32*28 grids for this degree reference. Note
// the use of TERRAIN_GRID_BLOCK_SPACING_{X,Y} which gives a one square
// overlap between grids
info.grid_idx_x = idx_x / TERRAIN_GRID_BLOCK_SPACING_X;
info.grid_idx_y = idx_y / TERRAIN_GRID_BLOCK_SPACING_Y;
// find the indices within the 32*28 grid
info.idx_x = idx_x % TERRAIN_GRID_BLOCK_SPACING_X;
info.idx_y = idx_y % TERRAIN_GRID_BLOCK_SPACING_Y;
// find the fraction (0..1) within the square
info.frac_x = (offset.x - idx_x * grid_spacing) / grid_spacing;
info.frac_y = (offset.y - idx_y * grid_spacing) / grid_spacing;
// calculate lat/lon of SW corner of 32*28 grid_block
ref.offset(info.grid_idx_x * TERRAIN_GRID_BLOCK_SPACING_X * (float)grid_spacing,
info.grid_idx_y * TERRAIN_GRID_BLOCK_SPACING_Y * (float)grid_spacing);
info.grid_lat = ref.lat;
info.grid_lon = ref.lng;
ASSERT_RANGE(info.idx_x,0,TERRAIN_GRID_BLOCK_SPACING_X-1);
ASSERT_RANGE(info.idx_y,0,TERRAIN_GRID_BLOCK_SPACING_Y-1);
ASSERT_RANGE(info.frac_x,0,1);
ASSERT_RANGE(info.frac_y,0,1);
}
/*
find a grid structure given a grid_info
*/
AP_Terrain::grid_cache &AP_Terrain::find_grid_cache(const struct grid_info &info)
{
struct grid_cache *oldest = cache;
// see if we have that grid
const auto now_ms = AP_HAL::millis();
for (struct grid_cache *curr = cache; curr != nullptr; curr = curr->next) {
if (TERRAIN_LATLON_EQUAL(curr->grid.lat,info.grid_lat) &&
TERRAIN_LATLON_EQUAL(curr->grid.lon,info.grid_lon) &&
curr->grid.spacing == grid_spacing) {
curr->last_access_ms = now_ms;
return *curr;
}
if (curr->last_access_ms < oldest->last_access_ms) {
oldest = curr;
}
}
// Not found. Use the oldest grid and make it this grid,
// initially unpopulated
struct grid_cache &grid = *oldest;
memset(&grid, 0, offsetof(struct grid_cache, next));
grid.grid.lat = info.grid_lat;
grid.grid.lon = info.grid_lon;
grid.grid.spacing = grid_spacing;
grid.grid.grid_idx_x = info.grid_idx_x;
grid.grid.grid_idx_y = info.grid_idx_y;
grid.grid.lat_degrees = info.lat_degrees;
grid.grid.lon_degrees = info.lon_degrees;
grid.grid.version = TERRAIN_GRID_FORMAT_VERSION;
grid.grid.version_minor = TERRAIN_VERSION_MINOR_MIN;
grid.last_access_ms = now_ms;
// mark as waiting for disk read
grid.state = GRID_CACHE_DISKWAIT;
return grid;
}
/*
find cache entry of disk_block
*/
AP_Terrain::grid_cache *AP_Terrain::find_io_cache(enum GridCacheState state)
{
// try first with given state
for (struct grid_cache *curr = cache; curr != nullptr; curr = curr->next) {
if (TERRAIN_LATLON_EQUAL(disk_block.block.lat,curr->grid.lat) &&
TERRAIN_LATLON_EQUAL(disk_block.block.lon,curr->grid.lon) &&
curr->state == state) {
return curr;
}
}
// then any state
for (struct grid_cache *curr = cache; curr != nullptr; curr = curr->next) {
if (TERRAIN_LATLON_EQUAL(disk_block.block.lat,curr->grid.lat) &&
TERRAIN_LATLON_EQUAL(disk_block.block.lon,curr->grid.lon)) {
return curr;
}
}
return nullptr;
}
/*
get CRC for a block
*/
uint16_t AP_Terrain::get_block_crc(struct grid_block &block)
{
uint16_t saved_crc = block.crc;
block.crc = 0;
/*
note that we exclude version_minor and any later bytes from the
CRC to maintain backwards compatibility so old versions of
ArduPilot accept new terrain blocks
*/
uint16_t ret = crc16_ccitt((const uint8_t *)&block, offsetof(struct grid_block, version_minor), 0);
block.crc = saved_crc;
return ret;
}
#endif // AP_TERRAIN_AVAILABLE