/* * 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 #include #include #include #include #include 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 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> 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& 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 pIMemory) { dataFrameDesc.timestampNs = elapsedRealtimeNano(); const std::vector transmittersIdList = {0}; dataFrameDesc.transmittersIdList = transmittersIdList; const std::vector recvIdList = {0, 1, 2}; dataFrameDesc.receiversIdList = recvIdList; const std::vector receiversReadingsCountList = {2, 2, 4}; dataFrameDesc.receiversReadingsCountList = receiversReadingsCountList; const std::vector 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::Create(const char* deviceName) { return sp(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 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 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 EvsUltrasonicsArray::setMaxFramesInFlight(uint32_t bufferCount) { LOG(DEBUG) << "EvsUltrasonicsArray setMaxFramesInFlight"; // Lock mutex for performing changes to available frames. std::lock_guard 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 EvsUltrasonicsArray::doneWithDataFrame(const UltrasonicsDataFrameDesc& dataFrameDesc) { LOG(DEBUG) << "EvsUltrasonicsArray doneWithFrame"; std::lock_guard 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 EvsUltrasonicsArray::startStream( const ::android::sp& stream) { LOG(DEBUG) << "EvsUltrasonicsArray startStream"; std::lock_guard 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 EvsUltrasonicsArray::stopStream() { LOG(DEBUG) << "EvsUltrasonicsArray stopStream"; bool streamStateStopping = false; { std::lock_guard 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 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 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 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 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