diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0773339a9..3901017d3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -209,6 +209,7 @@ set(HEADERS hle/service/cam/cam_q.h hle/service/cam/cam_s.h hle/service/cam/cam_u.h + hle/service/cam/impl.h hle/service/cecd/cecd.h hle/service/cecd/cecd_s.h hle/service/cecd/cecd_u.h @@ -281,6 +282,12 @@ set(HEADERS system.h ) +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(SRCS ${SRCS} hle/service/cam/impl_linux.cpp) +else() + set(SRCS ${SRCS} hle/service/cam/impl_dummy.cpp) +endif() + create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index 9df48a650..0bd18a02f 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -5,11 +5,13 @@ #include "common/logging/log.h" #include "core/hle/kernel/event.h" +#include "core/hle/result.h" #include "core/hle/service/cam/cam.h" #include "core/hle/service/cam/cam_c.h" #include "core/hle/service/cam/cam_q.h" #include "core/hle/service/cam/cam_s.h" #include "core/hle/service/cam/cam_u.h" +#include "core/hle/service/cam/impl.h" #include "core/hle/service/service.h" namespace Service { @@ -27,10 +29,12 @@ void StartCapture(Service::Interface* self) { u8 port = cmd_buff[1] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = StartCapture(port); - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, port=%d", port); } void StopCapture(Service::Interface* self) { @@ -38,10 +42,12 @@ void StopCapture(Service::Interface* self) { u8 port = cmd_buff[1] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = StopCapture(port); - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, port=%d", port); } void GetVsyncInterruptEvent(Service::Interface* self) { @@ -78,18 +84,20 @@ void SetReceiving(Service::Interface* self) { u32 image_size = cmd_buff[3]; u16 trans_unit = cmd_buff[4] & 0xFFFF; + ResultCode result = SetReceiving(dest, port, image_size, trans_unit); + Kernel::Event* completion_event = (Port)port == Port::Cam2 ? completion_event_cam2.get() : completion_event_cam1.get(); completion_event->Signal(); cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[1] = result.raw; cmd_buff[2] = IPC::MoveHandleDesc(); cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); - LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d", - dest, port, image_size, trans_unit); + LOG_DEBUG(Service_CAM, "called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d", + dest, port, image_size, trans_unit); } void SetTransferLines(Service::Interface* self) { @@ -166,11 +174,13 @@ void Activate(Service::Interface* self) { u8 cam_select = cmd_buff[1] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = Activate(cam_select); - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d", - cam_select); + cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, cam_select=%d", + cam_select); } void FlipImage(Service::Interface* self) { @@ -194,11 +204,13 @@ void SetSize(Service::Interface* self) { u8 size = cmd_buff[2] & 0xFF; u8 context = cmd_buff[3] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = SetSize(cam_select, size, context); - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, size=%d, context=%d", - cam_select, size, context); + cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, cam_select=%d, size=%d, context=%d", + cam_select, size, context); } void SetFrameRate(Service::Interface* self) { @@ -214,6 +226,22 @@ void SetFrameRate(Service::Interface* self) { cam_select, frame_rate); } +void SetOutputFormat(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u8 cam_select = cmd_buff[1] & 0xFF; + u8 output_format = cmd_buff[2] & 0xFF; + u8 context = cmd_buff[3] & 0xFF; + + ResultCode result = SetOutputFormat(cam_select, output_format, context); + + cmd_buff[0] = IPC::MakeHeader(0x25, 3, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, cam_select=%d, output_format=%d, context=%d", + cam_select, output_format, context); +} + void GetStereoCameraCalibrationData(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); @@ -238,7 +266,7 @@ void GetStereoCameraCalibrationData(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; memcpy(&cmd_buff[2], &data, sizeof(data)); - LOG_TRACE(Service_CAM, "called"); + LOG_DEBUG(Service_CAM, "called"); } void GetSuitableY2rStandardCoefficient(Service::Interface* self) { @@ -270,19 +298,23 @@ void DriverInitialize(Service::Interface* self) { interrupt_error_event->Clear(); vsync_interrupt_error_event->Clear(); - cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = DriverInitialize(); - LOG_WARNING(Service_CAM, "(STUBBED) called"); + cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called"); } void DriverFinalize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = DriverFinalize(); - LOG_WARNING(Service_CAM, "(STUBBED) called"); + cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called"); } void Init() { diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index 2f4923728..eed55dcde 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -373,6 +373,20 @@ void SetSize(Service::Interface* self); */ void SetFrameRate(Service::Interface* self); +/** + * Selects the output format for the listed cameras. + * + * Inputs: + * 0: 0x002500C0 + * 1: u8 Camera select (`CameraSelect` enum) + * 2: u8 Camera output format (`OutputFormat` enum) + * 3: u8 Context id (`Context` enum) + * Outputs: + * 0: 0x002500C0 + * 1: ResultCode + */ +void SetOutputFormat(Service::Interface* self); + /** * Returns calibration data relating the outside cameras to eachother, for use in AR applications. * diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp index a1070ebb2..3fce6fc50 100644 --- a/src/core/hle/service/cam/cam_u.cpp +++ b/src/core/hle/service/cam/cam_u.cpp @@ -45,7 +45,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x002200C0, nullptr, "SetEffect"}, {0x00230080, nullptr, "SetContrast"}, {0x00240080, nullptr, "SetLensCorrection"}, - {0x002500C0, nullptr, "SetOutputFormat"}, + {0x002500C0, SetOutputFormat, "SetOutputFormat"}, {0x00260140, nullptr, "SetAutoExposureWindow"}, {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00280080, nullptr, "SetNoiseFilter"}, diff --git a/src/core/hle/service/cam/impl.h b/src/core/hle/service/cam/impl.h new file mode 100644 index 000000000..363d8fc94 --- /dev/null +++ b/src/core/hle/service/cam/impl.h @@ -0,0 +1,26 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/hle/result.h" +#include "core/memory.h" + +namespace Service { +namespace CAM { + +ResultCode StartCapture(u8 port); +ResultCode StopCapture(u8 port); +ResultCode SetReceiving(VAddr dest, u8 port, u32 image_size, u16 trans_unit); +ResultCode Activate(u8 cam_select); +ResultCode SetSize(u8 cam_select, u8 size, u8 context); +ResultCode SetOutputFormat(u8 cam_select, u8 output_format, u8 context); +ResultCode DriverInitialize(); +ResultCode DriverFinalize(); + +} // namespace CAM + +} // namespace Service diff --git a/src/core/hle/service/cam/impl_dummy.cpp b/src/core/hle/service/cam/impl_dummy.cpp new file mode 100644 index 000000000..7ca798124 --- /dev/null +++ b/src/core/hle/service/cam/impl_dummy.cpp @@ -0,0 +1,58 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_types.h" +#include "common/logging/log.h" + +#include "core/memory.h" + +#include "core/hle/result.h" +#include "core/hle/service/cam/impl.h" + +namespace Service { +namespace CAM { + +ResultCode StartCapture(u8 port) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode StopCapture(u8 port) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode SetReceiving(VAddr dest, u8 port, u32 image_size, u16 trans_unit) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode Activate(u8 cam_select) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode SetSize(u8 cam_select, u8 size, u8 context) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode SetOutputFormat(u8 cam_select, u8 output_format, u8 context) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode DriverInitialize() { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode DriverFinalize() { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +} // namespace CAM + +} // namespace Service diff --git a/src/core/hle/service/cam/impl_linux.cpp b/src/core/hle/service/cam/impl_linux.cpp new file mode 100644 index 000000000..39139e7b0 --- /dev/null +++ b/src/core/hle/service/cam/impl_linux.cpp @@ -0,0 +1,362 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include + +#include "common/common_types.h" +#include "common/logging/log.h" + +#include "core/memory.h" + +#include "core/hle/result.h" +#include "core/hle/service/cam/cam.h" +#include "core/hle/service/cam/impl.h" + +namespace Service { +namespace CAM { + +// TODO(Link Mauve): make the video device configurable. +static const char *video_device = "/dev/video0"; +static const unsigned NUM_BUFFERS = 2; + +static int fd; +static enum v4l2_buf_type format_type; +static struct v4l2_format format; +static u32 width; +static u32 height; +static bool crop_image_horizontally; + +static struct { + char *memory; + size_t length; +} mapped_buffers[NUM_BUFFERS]; + +ResultCode StartCapture(u8 port) { + if (ioctl(fd, VIDIOC_STREAMON, &format_type) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_STREAMON: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +ResultCode StopCapture(u8 port) { + if (ioctl(fd, VIDIOC_STREAMOFF, &format_type) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_STREAMOFF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +ResultCode SetReceiving(VAddr dest, u8 port, u32 image_size, u16 trans_unit) { + // XXX: hack stereoscopy somehow. + if (!(port & 1)) + return ResultCode(ErrorDescription::NotImplemented, ErrorModule::CAM, + ErrorSummary::NotSupported, ErrorLevel::Info); + + struct v4l2_buffer buf; + + std::memset(&buf, '\0', sizeof(buf)); + buf.type = format_type; + buf.memory = V4L2_MEMORY_MMAP; + + // Dequeue a buffer, at this point we can safely read from it. + // TODO(Link Mauve): this ioctl is blocking until a buffer is ready, maybe we don’t want that? + if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_DQBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + ASSERT(buf.flags & V4L2_BUF_FLAG_DONE); + + unsigned index = buf.index; + ASSERT(index < NUM_BUFFERS); + const char *memory = mapped_buffers[index].memory; + + // Not every capture device supports the size requested. + if (crop_image_horizontally) { + // There are always two bytes per pixel, both for YUYV and for RGB565. + const unsigned bytes_per_pixel = 2; + for (int y = 0; y < height; ++y) + Memory::WriteBlock(dest + (width * bytes_per_pixel * y), + static_cast(memory) + (format.fmt.pix.bytesperline * y), + width * bytes_per_pixel); + } else { + Memory::WriteBlock(dest, memory, image_size); + } + + // Enqueue back the buffer we just copied, so it will be filled again. + if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +ResultCode Activate(u8 cam_select) { + struct v4l2_requestbuffers req; + struct v4l2_buffer buf; + + // TODO(Link Mauve): apparently used to deactivate them, according to 3ds-examples. + if (cam_select == 0) { + return ResultCode(ErrorDescription::NotImplemented, ErrorModule::CAM, + ErrorSummary::NothingHappened, ErrorLevel::Info); + } + + // We are using the mmap interface instead of the user-pointer one because the user could + // change the target address at any frame. + std::memset(&req, '\0', sizeof(req)); + req.type = format_type; + req.memory = V4L2_MEMORY_MMAP; + req.count = NUM_BUFFERS; + + // Allocate our NUM_BUFFERS buffers. + if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_REQBUFS: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + if (req.count < NUM_BUFFERS) { + LOG_CRITICAL(Service_CAM, "wrong number of buffers!"); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + for (unsigned i = 0; i < NUM_BUFFERS; ++i) { + std::memset(&buf, '\0', sizeof(buf)); + buf.type = format_type; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + // Query the characteristics of the newly-allocated buffer. + if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QUERYBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // Map the buffer so we can copy from this address whenever we get more data. + mapped_buffers[i].memory = static_cast(mmap(nullptr, buf.length, PROT_READ, + MAP_SHARED, fd, buf.m.offset)); + mapped_buffers[i].length = buf.length; + + // Enqueue the buffer to let the driver fill it whenever it gets an image. + if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + } + + return RESULT_SUCCESS; +} + +ResultCode SetSize(u8 cam_select, u8 size, u8 context) { + switch (static_cast(size)) { + case Size::VGA: + width = 640; + height = 480; + break; + case Size::QVGA: + width = 320; + height = 240; + break; + case Size::QQVGA: + width = 160; + height = 120; + break; + case Size::CIF: + width = 352; + height = 288; + break; + case Size::QCIF: + width = 176; + height = 144; + break; + case Size::DS_LCD: + width = 256; + height = 192; + break; + case Size::DS_LCDx4: + width = 512; + height = 384; + break; + case Size::CTR_TOP_LCD: + width = 400; + height = 240; + break; + default: + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidSize, ErrorModule::CAM, + ErrorSummary::WrongArgument, ErrorLevel::Fatal); + } + + format.fmt.pix.width = width; + format.fmt.pix.height = height; + + if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_S_FMT: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + if (format.fmt.pix.width > width) { + LOG_WARNING(Service_CAM, "Got a larger format than requested (%d > %d), will crop", + format.fmt.pix.width, width); + crop_image_horizontally = true; + } else { + crop_image_horizontally = false; + } + + if (format.fmt.pix.height > height) + LOG_WARNING(Service_CAM, "Got a higher format than requested (%d > %d), will crop", + format.fmt.pix.height, height); + + return RESULT_SUCCESS; +} + +static inline const char * dump_format(uint32_t format, char out[4]) +{ +#if BYTE_ORDER == BIG_ENDIAN + format = __builtin_bswap32(format); +#endif + memcpy(out, &format, 4); + return out; +} + +ResultCode SetOutputFormat(u8 cam_select, u8 output_format, u8 context) { + u32 v4l2_format; + + switch (static_cast(output_format)) { + case OutputFormat::YUV422: + v4l2_format = V4L2_PIX_FMT_YUYV; + break; + case OutputFormat::RGB565: + v4l2_format = V4L2_PIX_FMT_RGB565; + break; + default: + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, + ErrorSummary::WrongArgument, ErrorLevel::Fatal); + } + + format.fmt.pix.pixelformat = v4l2_format; + + if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_S_FMT: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // TODO(Link Mauve): use y2r:u instead when the user asks for RGB565, since the vast majority + // of capture devices won’t give us that kind of format. + if (format.fmt.pix.pixelformat != v4l2_format) { + char buf1[4], buf2[4]; + LOG_ERROR(Service_CAM, "Wrong pixel format, asked %.4s got %.4s!", + dump_format(v4l2_format, buf1), dump_format(format.fmt.pix.pixelformat, buf2)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::NotImplemented, ErrorModule::CAM, + ErrorSummary::NotSupported, ErrorLevel::Permanent); + } + + return RESULT_SUCCESS; +} + +ResultCode DriverInitialize() { + struct v4l2_capability cap; + + // Open the chosen v4l2 device. + fd = open(video_device, O_RDWR); + if (fd < 0) { + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::NotFound, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // Query the capabilities of that device. + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QUERYCAP: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // We only support single-planar video capture devices currently, which represent the vast + // majority of camera devices. + if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { + format_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } else { + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // We are using the streaming interface. + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + LOG_CRITICAL(Service_CAM, "Not a streaming device!"); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // Retrieve the current format, which will be modified afterwards. + std::memset(&format, '\0', sizeof(format)); + format.type = format_type; + if (ioctl(fd, VIDIOC_G_FMT, &format) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_G_FMT: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // TODO(Link Mauve): find the default format on a 3DS and set it here. + + return RESULT_SUCCESS; +} + +ResultCode DriverFinalize() { + // TODO(Link Mauve): maybe this should be done in Activate(0) instead. + for (unsigned i = 0; i < NUM_BUFFERS; ++i) { + if (mapped_buffers[i].memory) + munmap(mapped_buffers[i].memory, mapped_buffers[i].length); + mapped_buffers[i].memory = nullptr; + } + + // Exits the API. + if (close(fd) == -1) { + LOG_ERROR(Service_CAM, "close: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +} // namespace CAM + +} // namespace Service