blob: 3802a69d70027a2da99f4423aa2eb8da96c58a30 [file]
/*
* Copyright 2024 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 <aidl/android/crosvm/BnCrosvmAndroidDisplayService.h>
#include <aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.h>
#include <android-base/result.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <system/graphics.h> // for HAL_PIXEL_FORMAT_*
#include <condition_variable>
#include <memory>
#include <mutex>
using aidl::android::crosvm::BnCrosvmAndroidDisplayService;
using aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal;
using aidl::android::view::Surface;
using android::base::Error;
using android::base::Result;
namespace {
class SinkANativeWindow_Buffer {
public:
Result<void> configure(uint32_t width, uint32_t height, int format) {
if (format != HAL_PIXEL_FORMAT_BGRA_8888) {
return Error() << "Pixel format " << format << " is not BGRA_8888.";
}
mBufferBits.resize(width * height * 4);
mBuffer = ANativeWindow_Buffer{
.width = static_cast<int32_t>(width),
.height = static_cast<int32_t>(height),
.stride = static_cast<int32_t>(width),
.format = format,
.bits = mBufferBits.data(),
};
return {};
}
operator ANativeWindow_Buffer&() { return mBuffer; }
private:
ANativeWindow_Buffer mBuffer;
std::vector<uint8_t> mBufferBits;
};
static Result<void> copyBuffer(ANativeWindow_Buffer& from, ANativeWindow_Buffer& to) {
if (from.width != to.width || from.height != to.height) {
return Error() << "dimension mismatch. from=(" << from.width << ", " << from.height << ") "
<< "to=(" << to.width << ", " << to.height << ")";
}
uint32_t* dst = reinterpret_cast<uint32_t*>(to.bits);
uint32_t* src = reinterpret_cast<uint32_t*>(from.bits);
size_t bytes_on_line = to.width * 4; // 4 bytes per pixel
for (int32_t h = 0; h < to.height; h++) {
memcpy(dst + (h * to.stride), src + (h * from.stride), bytes_on_line);
}
return {};
}
// Wrapper which contains the latest available Surface/ANativeWindow from the DisplayService, if
// available. A Surface/ANativeWindow may not always be available if, for example, the VmLauncherApp
// on the other end of the DisplayService is not in the foreground / is paused.
class AndroidDisplaySurface {
public:
AndroidDisplaySurface(const std::string& name) : mName(name) {}
void setNativeSurface(Surface* surface) {
{
std::lock_guard lk(mSurfaceMutex);
mNativeSurface = std::make_unique<Surface>(surface->release());
mNativeSurfaceNeedsConfiguring = true;
}
mNativeSurfaceReady.notify_one();
}
void removeSurface() {
{
std::lock_guard lk(mSurfaceMutex);
mNativeSurface = nullptr;
}
mNativeSurfaceReady.notify_one();
}
Surface* getSurface() {
std::unique_lock lk(mSurfaceMutex);
return mNativeSurface.get();
}
Result<void> configure(uint32_t width, uint32_t height) {
std::unique_lock lk(mSurfaceMutex);
mRequestedSurfaceDimensions = Rect{
.width = width,
.height = height,
};
if (auto ret = mSinkBuffer.configure(width, height, kFormat); !ret.ok()) {
return Error() << "Failed to configure sink buffer: " << ret.error();
}
if (auto ret = mSavedFrameBuffer.configure(width, height, kFormat); !ret.ok()) {
return Error() << "Failed to configure saved frame buffer: " << ret.error();
}
return {};
}
void waitForNativeSurface() {
std::unique_lock lk(mSurfaceMutex);
mNativeSurfaceReady.wait(lk, [this] { return mNativeSurface != nullptr; });
}
Result<void> lock(ANativeWindow_Buffer* out_buffer) {
std::unique_lock lk(mSurfaceMutex);
Surface* surface = mNativeSurface.get();
if (surface == nullptr) {
// Surface not currently available but not necessarily an error
// if, for example, the VmLauncherApp is not in the foreground.
*out_buffer = mSinkBuffer;
return {};
}
ANativeWindow* anw = surface->get();
if (anw == nullptr) {
return Error() << "Failed to get ANativeWindow";
}
if (mNativeSurfaceNeedsConfiguring) {
if (!mRequestedSurfaceDimensions) {
return Error() << "Surface dimension is not configured yet!";
}
const auto& dims = *mRequestedSurfaceDimensions;
// Ensure locked buffers have our desired format.
if (ANativeWindow_setBuffersGeometry(anw, dims.width, dims.height, kFormat) != 0) {
return Error() << "Failed to set buffer geometry.";
}
mNativeSurfaceNeedsConfiguring = false;
}
if (ANativeWindow_lock(anw, out_buffer, nullptr) != 0) {
return Error() << "Failed to lock window";
}
mLastBuffer = *out_buffer;
return {};
}
Result<void> unlockAndPost() {
std::unique_lock lk(mSurfaceMutex);
Surface* surface = mNativeSurface.get();
if (surface == nullptr) {
// Surface not currently available but not necessarily an error
// if, for example, the VmLauncherApp is not in the foreground.
return {};
}
ANativeWindow* anw = surface->get();
if (anw == nullptr) {
return Error() << "Failed to get ANativeWindow";
}
if (ANativeWindow_unlockAndPost(anw) != 0) {
return Error() << "Failed to unlock and post window";
}
return {};
}
// Saves the last frame drawn
Result<void> saveFrame() {
std::unique_lock lk(mSurfaceMutex);
if (auto ret = copyBuffer(mLastBuffer, mSavedFrameBuffer); !ret.ok()) {
return Error() << "Failed to copy frame: " << ret.error();
}
return {};
}
// Draws the saved frame
Result<void> drawSavedFrame() {
std::unique_lock lk(mSurfaceMutex);
Surface* surface = mNativeSurface.get();
if (surface == nullptr) {
return Error() << "Surface not ready";
}
ANativeWindow* anw = surface->get();
if (anw == nullptr) {
return Error() << "Failed to get ANativeWindow";
}
// TODO: dedup this and the one in lock(...)
if (mNativeSurfaceNeedsConfiguring) {
if (!mRequestedSurfaceDimensions) {
return Error() << "Surface dimension is not configured yet!";
}
const auto& dims = *mRequestedSurfaceDimensions;
// Ensure locked buffers have our desired format.
if (ANativeWindow_setBuffersGeometry(anw, dims.width, dims.height, kFormat) != 0) {
return Error() << "Failed to set buffer geometry.";
}
mNativeSurfaceNeedsConfiguring = false;
}
ANativeWindow_Buffer buf;
if (ANativeWindow_lock(anw, &buf, nullptr) != 0) {
return Error() << "Failed to lock window";
}
if (auto ret = copyBuffer(mSavedFrameBuffer, buf); !ret.ok()) {
return Error() << "Failed to copy frame: " << ret.error();
}
if (ANativeWindow_unlockAndPost(anw) != 0) {
return Error() << "Failed to unlock and post window";
}
return {};
}
const std::string& name() const { return mName; }
private:
// Note: crosvm always uses BGRA8888 or BGRX8888. See devices/src/virtio/gpu/mod.rs in
// crosvm where the SetScanoutBlob command is handled. Let's use BGRA not BGRX with a hope
// that we will need alpha blending for the cursor surface.
static constexpr const int kFormat = HAL_PIXEL_FORMAT_BGRA_8888;
std::string mName;
std::mutex mSurfaceMutex;
std::unique_ptr<Surface> mNativeSurface;
std::condition_variable mNativeSurfaceReady;
bool mNativeSurfaceNeedsConfiguring = true;
// Buffer which crosvm uses when in background. This is just to not fail crosvm even when
// Android-side Surface doesn't exist. The content drawn here is never displayed on the physical
// screen.
SinkANativeWindow_Buffer mSinkBuffer;
// Buffer which is currently allocated for crosvm to draw onto. This holds the last frame. This
// is what gets displayed on the physical screen.
ANativeWindow_Buffer mLastBuffer;
// Copy of mLastBuffer made by the call saveFrameForSurface. This holds the last good (i.e.
// non-blank) frame before the VM goes background. When the VM is brought up to foreground,
// this is drawn to the physical screen until the VM starts to emit actual frames.
SinkANativeWindow_Buffer mSavedFrameBuffer;
struct Rect {
uint32_t width = 0;
uint32_t height = 0;
};
std::optional<Rect> mRequestedSurfaceDimensions;
};
class DisplayService : public BnCrosvmAndroidDisplayService {
public:
DisplayService() = default;
virtual ~DisplayService() = default;
ndk::ScopedAStatus setSurface(Surface* surface, bool forCursor) override {
getSurface(forCursor).setNativeSurface(surface);
return ::ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus removeSurface(bool forCursor) override {
getSurface(forCursor).removeSurface();
return ::ndk::ScopedAStatus::ok();
}
ndk::ScopedFileDescriptor& getCursorStream() { return mCursorStream; }
ndk::ScopedAStatus setCursorStream(const ndk::ScopedFileDescriptor& in_stream) {
mCursorStream = ndk::ScopedFileDescriptor(dup(in_stream.get()));
return ::ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus saveFrameForSurface(bool forCursor) override {
if (auto ret = getSurface(forCursor).saveFrame(); !ret.ok()) {
std::string msg = std::format("Failed to save frame: {}", ret.error().message());
return ::ndk::ScopedAStatus(
AStatus_fromServiceSpecificErrorWithMessage(-1, msg.c_str()));
}
return ::ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus drawSavedFrameForSurface(bool forCursor) override {
if (auto ret = getSurface(forCursor).drawSavedFrame(); !ret.ok()) {
std::string msg = std::format("Failed to draw saved frame: {}", ret.error().message());
return ::ndk::ScopedAStatus(
AStatus_fromServiceSpecificErrorWithMessage(-1, msg.c_str()));
}
return ::ndk::ScopedAStatus::ok();
}
AndroidDisplaySurface& getSurface(bool forCursor) {
if (forCursor) {
return mCursor;
} else {
return mScanout;
}
}
private:
AndroidDisplaySurface mScanout{"scanout"};
AndroidDisplaySurface mCursor{"cursor"};
ndk::ScopedFileDescriptor mCursorStream;
};
} // namespace
typedef void (*ErrorCallback)(const char* message);
struct AndroidDisplayContext {
std::shared_ptr<IVirtualizationServiceInternal> virt_service;
std::shared_ptr<DisplayService> disp_service;
ErrorCallback error_callback;
AndroidDisplayContext(ErrorCallback cb) : error_callback(cb) {
auto disp_service = ::ndk::SharedRefBase::make<DisplayService>();
// Creates DisplayService and register it to the virtualizationservice. This is needed
// because this code is executed inside of crosvm which runs as an app. Apps are not allowed
// to register a service to the service manager.
auto virt_service = IVirtualizationServiceInternal::fromBinder(ndk::SpAIBinder(
AServiceManager_waitForService("android.system.virtualizationservice")));
if (virt_service == nullptr) {
errorf("Failed to find virtualization service");
return;
}
auto status = virt_service->setDisplayService(disp_service->asBinder());
if (!status.isOk()) {
errorf("Failed to register display service");
return;
}
this->virt_service = virt_service;
this->disp_service = disp_service;
ABinderProcess_startThreadPool();
}
~AndroidDisplayContext() {
if (virt_service == nullptr) {
errorf("Not connected to virtualization service");
return;
}
auto status = this->virt_service->clearDisplayService();
if (!status.isOk()) {
errorf("Failed to clear display service");
}
}
void errorf(const char* format, ...) {
char buffer[1024];
va_list vararg;
va_start(vararg, format);
vsnprintf(buffer, sizeof(buffer), format, vararg);
va_end(vararg);
error_callback(buffer);
}
};
extern "C" struct AndroidDisplayContext* create_android_display_context(
const char*, ErrorCallback error_callback) {
return new AndroidDisplayContext(error_callback);
}
extern "C" void destroy_android_display_context(struct AndroidDisplayContext* ctx) {
delete ctx;
}
extern "C" AndroidDisplaySurface* create_android_surface(struct AndroidDisplayContext* ctx,
uint32_t width, uint32_t height,
bool forCursor) {
if (ctx->disp_service == nullptr) {
ctx->errorf("Display service was not created");
return nullptr;
}
AndroidDisplaySurface& surface = ctx->disp_service->getSurface(forCursor);
if (auto ret = surface.configure(width, height); !ret.ok()) {
ctx->errorf("Failed to configure surface %s: %s", surface.name().c_str(),
ret.error().message().c_str());
}
surface.waitForNativeSurface(); // this can block
// TODO(b/332785161): if we know that surface can get destroyed dynamically while VM is running,
// consider calling ANativeWindow_acquire here and _release in destroy_android_surface, so that
// crosvm doesn't hold a dangling pointer.
return &surface;
}
extern "C" void destroy_android_surface(struct AndroidDisplayContext*, ANativeWindow*) {
// NOT IMPLEMENTED
}
extern "C" bool get_android_surface_buffer(struct AndroidDisplayContext* ctx,
AndroidDisplaySurface* surface,
ANativeWindow_Buffer* out_buffer) {
if (out_buffer == nullptr) {
ctx->errorf("out_buffer is null");
return false;
}
if (surface == nullptr) {
ctx->errorf("Invalid AndroidDisplaySurface provided");
return false;
}
auto ret = surface->lock(out_buffer);
if (!ret.ok()) {
ctx->errorf("Failed to lock surface %s: %s", surface->name().c_str(),
ret.error().message().c_str());
return false;
}
return true;
}
extern "C" void set_android_surface_position(struct AndroidDisplayContext* ctx, uint32_t x,
uint32_t y) {
if (ctx->disp_service == nullptr) {
ctx->errorf("Display service was not created");
return;
}
auto fd = ctx->disp_service->getCursorStream().get();
if (fd == -1) {
ctx->errorf("Invalid fd");
return;
}
uint32_t pos[] = {x, y};
write(fd, pos, sizeof(pos));
}
extern "C" void post_android_surface_buffer(struct AndroidDisplayContext* ctx,
AndroidDisplaySurface* surface) {
if (surface == nullptr) {
ctx->errorf("Invalid AndroidDisplaySurface provided");
return;
}
auto ret = surface->unlockAndPost();
if (!ret.ok()) {
ctx->errorf("Failed to unlock and post for surface %s: %s", surface->name().c_str(),
ret.error().message().c_str());
}
return;
}