blob: 951937a147f916ea0ad7855971027dd3ef6dbb6d [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "EvsUltrasonicsArray.h"
#include <android-base/logging.h>
#include <hidlmemory/mapping.h>
#include <log/log.h>
#include <time.h>
#include <utils/SystemClock.h>
#include <utils/Timers.h>
namespace android {
namespace hardware {
namespace automotive {
namespace evs {
namespace V1_1 {
namespace implementation {
// Arbitrary limit on number of data frames allowed to be allocated
// Safeguards against unreasonable resource consumption and provides a testable limit
const unsigned int kMaximumDataFramesInFlight = 100;
const uint32_t kMaxReadingsPerSensor = 5;
const uint32_t kMaxReceiversCount = 3;
const unsigned int kSharedMemoryMaxSize =
kMaxReadingsPerSensor * kMaxReceiversCount * 2 * sizeof(float);
// Target frame rate in frames per second.
const int kTargetFrameRate = 10;
namespace {
void fillMockArrayDesc(UltrasonicsArrayDesc& arrayDesc) {
arrayDesc.maxReadingsPerSensorCount = kMaxReadingsPerSensor;
arrayDesc.maxReceiversCount = kMaxReceiversCount;
const int kSensorCount = 3;
const float kMaxRange = 4000; // 4 metres.
const float kAngleOfMeasurement = 0.261799; // 15 degrees.
std::vector<UltrasonicSensor> sensors(kSensorCount);
// Sensor pointing forward on left side of front bumper.
sensors[0].maxRange = kMaxRange;
sensors[0].angleOfMeasurement = kAngleOfMeasurement;
sensors[0].pose = {{1, 0, 0, 0}, {-1000, 2000, 200}};
// Sensor pointing forward on center of front bumper.
sensors[1].maxRange = kMaxRange;
sensors[1].angleOfMeasurement = kAngleOfMeasurement;
sensors[1].pose = {{1, 0, 0, 0}, {0, 2000, 200}};
// Sensor pointing forward on right side of front bumper.
sensors[2].maxRange = kMaxRange;
sensors[2].angleOfMeasurement = kAngleOfMeasurement;
sensors[2].pose = {{1, 0, 0, 0}, {1000, 2000, 200}};
arrayDesc.sensors = sensors;
}
// Struct used by SerializeWaveformData().
struct WaveformData {
uint8_t receiverId;
std::vector<std::pair<float, float>> readings;
};
// Serializes data provided in waveformDataList to a shared memory data pointer.
// TODO(b/149950362): Add a common library for serialiazing and deserializing waveform data.
void SerializeWaveformData(const std::vector<WaveformData>& waveformDataList, uint8_t* pData) {
for (auto& waveformData : waveformDataList) {
// Set Id
memcpy(pData, &waveformData.receiverId, sizeof(uint8_t));
pData += sizeof(uint8_t);
for (auto& reading : waveformData.readings) {
// Set the time of flight.
memcpy(pData, &reading.first, sizeof(float));
pData += sizeof(float);
// Set the resonance.
memcpy(pData, &reading.second, sizeof(float));
pData += sizeof(float);
}
}
}
// Fills dataFrameDesc with mock data.
bool fillMockDataFrame(UltrasonicsDataFrameDesc& dataFrameDesc, sp<IMemory> pIMemory) {
dataFrameDesc.timestampNs = elapsedRealtimeNano();
const std::vector<uint8_t> transmittersIdList = {0};
dataFrameDesc.transmittersIdList = transmittersIdList;
const std::vector<uint8_t> recvIdList = {0, 1, 2};
dataFrameDesc.receiversIdList = recvIdList;
const std::vector<uint32_t> receiversReadingsCountList = {2, 2, 4};
dataFrameDesc.receiversReadingsCountList = receiversReadingsCountList;
const std::vector<WaveformData> waveformDataList = {
{recvIdList[0], {{1000, 0.1f}, {2000, 0.8f}}},
{recvIdList[1], {{1000, 0.1f}, {2000, 1.0f}}},
{recvIdList[2], {{1000, 0.1f}, {2000, 0.2f}, {4000, 0.2f}, {5000, 0.1f}}}};
if (pIMemory.get() == nullptr) {
return false;
}
uint8_t* pData = (uint8_t*)((void*)pIMemory->getPointer());
pIMemory->update();
SerializeWaveformData(waveformDataList, pData);
pIMemory->commit();
return true;
}
} // namespace
EvsUltrasonicsArray::EvsUltrasonicsArray(const char* deviceName)
: mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED) {
LOG(DEBUG) << "EvsUltrasonicsArray instantiated";
// Set up mock data for description.
mArrayDesc.ultrasonicsArrayId = deviceName;
fillMockArrayDesc(mArrayDesc);
// Assign allocator.
mShmemAllocator = IAllocator::getService("ashmem");
if (mShmemAllocator.get() == nullptr) {
LOG(ERROR) << "SurroundViewHidlTest getService ashmem failed";
}
}
sp<EvsUltrasonicsArray> EvsUltrasonicsArray::Create(const char* deviceName) {
return sp<EvsUltrasonicsArray>(new EvsUltrasonicsArray(deviceName));
}
EvsUltrasonicsArray::~EvsUltrasonicsArray() {
LOG(DEBUG) << "EvsUltrasonicsArray being destroyed";
forceShutdown();
}
// This gets called if another caller "steals" ownership of the ultrasonic array.
void EvsUltrasonicsArray::forceShutdown() {
LOG(DEBUG) << "EvsUltrasonicsArray forceShutdown";
// Make sure our output stream is cleaned up
// (It really should be already)
stopStream();
// Claim the lock while we work on internal state
std::lock_guard<std::mutex> lock(mAccessLock);
// Drop all the data frames we've been using
for (auto&& dataFrame : mDataFrames) {
if (dataFrame.inUse) {
LOG(ERROR) << "Error - releasing data frame despite remote ownership";
}
dataFrame.sharedMemory.clear();
}
mDataFrames.clear();
// Put this object into an unrecoverable error state since somebody else
// is going to own the underlying ultrasonic array now
mStreamState = DEAD;
}
UltrasonicsArrayDesc EvsUltrasonicsArray::GetMockArrayDesc(const char* deviceName) {
UltrasonicsArrayDesc ultrasonicsArrayDesc;
ultrasonicsArrayDesc.ultrasonicsArrayId = deviceName;
fillMockArrayDesc(ultrasonicsArrayDesc);
return ultrasonicsArrayDesc;
}
Return<void> EvsUltrasonicsArray::getUltrasonicArrayInfo(getUltrasonicArrayInfo_cb _get_info_cb) {
LOG(DEBUG) << "EvsUltrasonicsArray getUltrasonicsArrayInfo";
// Return the description for the get info callback.
_get_info_cb(mArrayDesc);
return Void();
}
Return<EvsResult> EvsUltrasonicsArray::setMaxFramesInFlight(uint32_t bufferCount) {
LOG(DEBUG) << "EvsUltrasonicsArray setMaxFramesInFlight";
// Lock mutex for performing changes to available frames.
std::lock_guard<std::mutex> lock(mAccessLock);
// We cannot function without at least one buffer to send data.
if (bufferCount < 1) {
LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested";
return EvsResult::INVALID_ARG;
}
// Update our internal state of buffer count.
if (setAvailableFrames_Locked(bufferCount)) {
return EvsResult::OK;
} else {
return EvsResult::BUFFER_NOT_AVAILABLE;
}
return EvsResult::OK;
}
Return<void> EvsUltrasonicsArray::doneWithDataFrame(const UltrasonicsDataFrameDesc& dataFrameDesc) {
LOG(DEBUG) << "EvsUltrasonicsArray doneWithFrame";
std::lock_guard<std::mutex> lock(mAccessLock);
if (dataFrameDesc.dataFrameId >= mDataFrames.size()) {
LOG(ERROR) << "ignoring doneWithFrame called with invalid dataFrameId "
<< dataFrameDesc.dataFrameId << "(max is " << mDataFrames.size() - 1 << ")";
return Void();
}
if (!mDataFrames[dataFrameDesc.dataFrameId].inUse) {
LOG(ERROR) << "ignoring doneWithFrame called on frame " << dataFrameDesc.dataFrameId
<< "which is already free";
return Void();
}
// Mark the frame as available
mDataFrames[dataFrameDesc.dataFrameId].inUse = false;
mFramesInUse--;
// If this frame's index is high in the array, try to move it down
// to improve locality after mFramesAllowed has been reduced.
if (dataFrameDesc.dataFrameId >= mFramesAllowed) {
// Find an empty slot lower in the array (which should always exist in this case)
for (auto&& dataFrame : mDataFrames) {
if (!dataFrame.sharedMemory.IsValid()) {
dataFrame.sharedMemory = mDataFrames[dataFrameDesc.dataFrameId].sharedMemory;
mDataFrames[dataFrameDesc.dataFrameId].sharedMemory.clear();
return Void();
}
}
}
return Void();
}
Return<EvsResult> EvsUltrasonicsArray::startStream(
const ::android::sp<IEvsUltrasonicsArrayStream>& stream) {
LOG(DEBUG) << "EvsUltrasonicsArray startStream";
std::lock_guard<std::mutex> lock(mAccessLock);
if (mStreamState != STOPPED) {
LOG(ERROR) << "ignoring startStream call when a stream is already running.";
return EvsResult::STREAM_ALREADY_RUNNING;
}
// If the client never indicated otherwise, configure ourselves for a single streaming buffer
if (mFramesAllowed < 1) {
if (!setAvailableFrames_Locked(1)) {
LOG(ERROR)
<< "Failed to start stream because we couldn't get shared memory data buffer";
return EvsResult::BUFFER_NOT_AVAILABLE;
}
}
// Record the user's callback for use when we have a frame ready
mStream = stream;
// Start the frame generation thread
mStreamState = RUNNING;
mCaptureThread = std::thread([this]() { generateDataFrames(); });
return EvsResult::OK;
}
Return<void> EvsUltrasonicsArray::stopStream() {
LOG(DEBUG) << "EvsUltrasonicsArray stopStream";
bool streamStateStopping = false;
{
std::lock_guard<std::mutex> lock(mAccessLock);
if (mStreamState == RUNNING) {
// Tell the GenerateFrames loop we want it to stop
mStreamState = STOPPING;
streamStateStopping = true;
}
}
if (streamStateStopping) {
// Block outside the mutex until the "stop" flag has been acknowledged
// We won't send any more frames, but the client might still get some already in flight
LOG(DEBUG) << "Waiting for stream thread to end...";
mCaptureThread.join();
}
{
std::lock_guard<std::mutex> lock(mAccessLock);
mStreamState = STOPPED;
mStream = nullptr;
LOG(DEBUG) << "Stream marked STOPPED.";
}
return Void();
}
bool EvsUltrasonicsArray::setAvailableFrames_Locked(unsigned bufferCount) {
if (bufferCount < 1) {
LOG(ERROR) << "Ignoring request to set buffer count to zero";
return false;
}
if (bufferCount > kMaximumDataFramesInFlight) {
LOG(ERROR) << "Rejecting buffer request in excess of internal limit";
return false;
}
// Is an increase required?
if (mFramesAllowed < bufferCount) {
// An increase is required
unsigned needed = bufferCount - mFramesAllowed;
LOG(INFO) << "Number of data frame buffers to add: " << needed;
unsigned added = increaseAvailableFrames_Locked(needed);
if (added != needed) {
// If we didn't add all the frames we needed, then roll back to the previous state
LOG(ERROR) << "Rolling back to previous frame queue size";
decreaseAvailableFrames_Locked(added);
return false;
}
} else if (mFramesAllowed > bufferCount) {
// A decrease is required
unsigned framesToRelease = mFramesAllowed - bufferCount;
LOG(INFO) << "Number of data frame buffers to reduce: " << framesToRelease;
unsigned released = decreaseAvailableFrames_Locked(framesToRelease);
if (released != framesToRelease) {
// This shouldn't happen with a properly behaving client because the client
// should only make this call after returning sufficient outstanding buffers
// to allow a clean resize.
LOG(ERROR) << "Buffer queue shrink failed -- too many buffers currently in use?";
}
}
return true;
}
EvsUltrasonicsArray::SharedMemory EvsUltrasonicsArray::allocateAndMapSharedMemory() {
SharedMemory sharedMemory;
// Check shared memory allocator is valid.
if (mShmemAllocator.get() == nullptr) {
LOG(ERROR) << "Shared memory allocator not initialized.";
return SharedMemory();
}
// Allocate memory.
bool allocateSuccess = false;
Return<void> result = mShmemAllocator->allocate(kSharedMemoryMaxSize,
[&](bool success, const hidl_memory& hidlMem) {
if (!success) {
return;
}
allocateSuccess = success;
sharedMemory.hidlMemory = hidlMem;
});
// Check result of allocated memory.
if (!result.isOk() || !allocateSuccess) {
LOG(ERROR) << "Shared memory allocation failed.";
return SharedMemory();
}
// Map shared memory.
sharedMemory.pIMemory = mapMemory(sharedMemory.hidlMemory);
if (sharedMemory.pIMemory.get() == nullptr) {
LOG(ERROR) << "Shared memory mapping failed.";
return SharedMemory();
}
// Return success.
return sharedMemory;
}
unsigned EvsUltrasonicsArray::increaseAvailableFrames_Locked(unsigned numToAdd) {
unsigned added = 0;
while (added < numToAdd) {
SharedMemory sharedMemory = allocateAndMapSharedMemory();
// If allocate and map fails, break.
if (!sharedMemory.IsValid()) {
break;
}
// Find a place to store the new buffer
bool stored = false;
for (auto&& dataFrame : mDataFrames) {
if (!dataFrame.sharedMemory.IsValid()) {
// Use this existing entry
dataFrame.sharedMemory = sharedMemory;
dataFrame.inUse = false;
stored = true;
break;
}
}
if (!stored) {
// Add a BufferRecord wrapping this handle to our set of available buffers
mDataFrames.emplace_back(sharedMemory);
}
mFramesAllowed++;
added++;
}
return added;
}
unsigned EvsUltrasonicsArray::decreaseAvailableFrames_Locked(unsigned numToRemove) {
unsigned removed = 0;
for (auto&& dataFrame : mDataFrames) {
// Is this record not in use, but holding a buffer that we can free?
if (!dataFrame.inUse && dataFrame.sharedMemory.IsValid()) {
// Release buffer and update the record so we can recognize it as "empty"
dataFrame.sharedMemory.clear();
mFramesAllowed--;
removed++;
if (removed == numToRemove) {
break;
}
}
}
return removed;
}
// This is the asynchronous data frame generation thread that runs in parallel with the
// main serving thread. There is one for each active ultrasonic array instance.
void EvsUltrasonicsArray::generateDataFrames() {
LOG(DEBUG) << "Data frame generation loop started";
unsigned idx = 0;
while (true) {
bool timeForFrame = false;
nsecs_t startTime = elapsedRealtimeNano();
// Lock scope for updating shared state
{
std::lock_guard<std::mutex> lock(mAccessLock);
if (mStreamState != RUNNING) {
// Break out of our main thread loop
break;
}
// Are we allowed to issue another buffer?
if (mFramesInUse >= mFramesAllowed) {
// Can't do anything right now -- skip this frame
LOG(WARNING) << "Skipped a frame because too many are in flight";
} else {
// Identify an available buffer to fill
for (idx = 0; idx < mDataFrames.size(); idx++) {
if (!mDataFrames[idx].inUse && mDataFrames[idx].sharedMemory.IsValid()) {
// Found an available record, so stop looking
break;
}
}
if (idx >= mDataFrames.size()) {
// This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed
LOG(ERROR) << "Failed to find an available buffer slot";
} else {
// We're going to make the frame busy
mDataFrames[idx].inUse = true;
mFramesInUse++;
timeForFrame = true;
}
}
}
if (timeForFrame) {
// Assemble the buffer description we'll transmit below
UltrasonicsDataFrameDesc mockDataFrameDesc;
mockDataFrameDesc.dataFrameId = idx;
mockDataFrameDesc.waveformsData = mDataFrames[idx].sharedMemory.hidlMemory;
// Fill mock waveform data.
fillMockDataFrame(mockDataFrameDesc, mDataFrames[idx].sharedMemory.pIMemory);
// Issue the (asynchronous) callback to the client -- can't be holding the lock
auto result = mStream->deliverDataFrame(mockDataFrameDesc);
if (result.isOk()) {
LOG(DEBUG) << "Delivered data frame id: " << mockDataFrameDesc.dataFrameId;
} else {
// This can happen if the client dies and is likely unrecoverable.
// To avoid consuming resources generating failing calls, we stop sending
// frames. Note, however, that the stream remains in the "STREAMING" state
// until cleaned up on the main thread.
LOG(ERROR) << "Frame delivery call failed in the transport layer.";
// Since we didn't actually deliver it, mark the frame as available
std::lock_guard<std::mutex> lock(mAccessLock);
mDataFrames[idx].inUse = false;
mFramesInUse--;
break;
}
}
// Sleep to generate frames at kTargetFrameRate.
static const nsecs_t kTargetFrameTimeUs = 1000 * 1000 / kTargetFrameRate;
const nsecs_t now = elapsedRealtimeNano();
const nsecs_t workTimeUs = (now - startTime) / 1000;
const nsecs_t sleepDurationUs = kTargetFrameTimeUs - workTimeUs;
if (sleepDurationUs > 0) {
usleep(sleepDurationUs);
}
}
// If we've been asked to stop, send an event to signal the actual end of stream
EvsEventDesc event;
event.aType = EvsEventType::STREAM_STOPPED;
auto result = mStream->notify(event);
if (!result.isOk()) {
LOG(ERROR) << "Error delivering end of stream marker";
}
}
} // namespace implementation
} // namespace V1_1
} // namespace evs
} // namespace automotive
} // namespace hardware
} // namespace android