1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <lib/sysmem/sysmem.h>
6
7 #include <fbl/algorithm.h>
8 #include <fuchsia/sysmem/c/fidl.h>
9 #include <lib/fidl-async/bind.h>
10 #include <lib/syslog/global.h>
11 #include <lib/zx/vmo.h>
12 #include <string.h>
13 #include <zircon/assert.h>
14 #include <zircon/syscalls.h>
15
16 constexpr char kTag[] = "sysmem";
17
18 namespace {
19
20 // A helper function to set the plane info for the most common YUV planar formats.
21 // The width and height fields of |format| must be valid before calling
22 // this function.
23 // |format|->layers and |format|->planes will be set.
24 // The intensity (Y in YUV) is assumed to be present at full resolution, in the
25 // first plane, with |y_bits_per_pixel| representing each pixel.
26 // The U and V planes follow. |uv_together| indicates that U and V are both located on
27 // the second plane; otherwise U and V are located on planes 2 and 3.
28 // This function assumes U and V are represented equally.
29 // |uv_horizontal_bits_per_pixel| indicates how many bits each pixel is represented by
30 // for a horizontal line only - the vertical subsampling is indicated by |uv_vertical_subsample|,
31 // so a UV plane that is subsampled 2x2 and U and V are 8 bit interleaved (i.e. NV12)
32 // (This means for every 2 Y pixels, there is one U byte and one V byte)
33 // would give uv_horizontal_bits_per_pixel = 4 (8 bits for U and 8 for V for every 2 pixels), and
34 // a uv_vertical_subsample = 2, to indicate that those 8 bits actually correspond to a set
35 // of 4 pixels.
36 // |buffer_size| is set to the total (maximum) image size, rounded up to the nearest page boundary.
SetImagePlaneInfoPlanarYuv(fuchsia_sysmem_ImageFormat * format,size_t * buffer_size,uint32_t y_bits_per_pixel,uint32_t uv_horizontal_bits_per_pixel,uint32_t uv_vertical_subsample,bool uv_together,bool page_align_layers=false)37 void SetImagePlaneInfoPlanarYuv(fuchsia_sysmem_ImageFormat* format,
38 size_t* buffer_size, uint32_t y_bits_per_pixel,
39 uint32_t uv_horizontal_bits_per_pixel,
40 uint32_t uv_vertical_subsample,
41 bool uv_together, bool page_align_layers = false) {
42 uint32_t offset;
43 format->planes[0].byte_offset = 0;
44 format->planes[0].bytes_per_row = (format->width * y_bits_per_pixel) / 8;
45 offset = format->planes[0].bytes_per_row * format->height;
46 offset = page_align_layers ? fbl::round_up(offset, ZX_PAGE_SIZE) : offset;
47 format->planes[1].bytes_per_row = (format->width * uv_horizontal_bits_per_pixel * (uv_together ? 2 : 1)) / 8;
48 format->planes[1].byte_offset = offset;
49 offset += format->planes[1].bytes_per_row * format->height / uv_vertical_subsample;
50 offset = page_align_layers ? fbl::round_up(offset, ZX_PAGE_SIZE) : offset;
51 format->layers = 2;
52 if (!uv_together) {
53 format->layers = 3;
54 format->planes[2].bytes_per_row = format->planes[1].bytes_per_row;
55 format->planes[2].byte_offset = offset;
56 offset += format->planes[2].bytes_per_row * format->height / uv_vertical_subsample;
57 }
58 *buffer_size = fbl::round_up(offset, ZX_PAGE_SIZE);
59 }
60
61 // A helper function to set the plane info for the most common packed formats.
62 // The width and height fields of |format| must be valid before calling
63 // this function.
64 // |format|->layers and |format|->planes will be set.
65 // |buffer_size| is set to the total (maximum) image buffer size, rounded up to the nearest page boundary.
SetImagePlaneInfoPacked(fuchsia_sysmem_ImageFormat * format,size_t * buffer_size,uint32_t bits_per_pixel)66 void SetImagePlaneInfoPacked(fuchsia_sysmem_ImageFormat* format,
67 size_t* buffer_size, uint32_t bits_per_pixel) {
68 format->planes[0].bytes_per_row = (format->width * bits_per_pixel) / 8;
69 format->layers = 1;
70 *buffer_size = fbl::round_up(format->height * format->planes[0].bytes_per_row, ZX_PAGE_SIZE);
71 }
72
PickImageFormat(const fuchsia_sysmem_BufferSpec & spec,fuchsia_sysmem_ImageFormat * format,size_t * buffer_size)73 zx_status_t PickImageFormat(const fuchsia_sysmem_BufferSpec& spec,
74 fuchsia_sysmem_ImageFormat* format,
75 size_t* buffer_size) {
76 // If hardware compatibility needs to be checked, do so here!
77 // For the simple case, just use whatever format was specified.
78 format->width = spec.image.min_width;
79 format->height = spec.image.min_height;
80 format->pixel_format = spec.image.pixel_format;
81 format->color_space = spec.image.color_space;
82 // Need to fill out the plane info, which depends on pixel_format:
83 // (More generally, it also depends on color space and BufferUsage,
84 // but this is a simplified version.)
85 switch (format->pixel_format.type) {
86 case fuchsia_sysmem_PixelFormatType_R8G8B8A8:
87 case fuchsia_sysmem_PixelFormatType_BGRA32:
88 SetImagePlaneInfoPacked(format, buffer_size, 32);
89 break;
90 case fuchsia_sysmem_PixelFormatType_YUY2:
91 SetImagePlaneInfoPacked(format, buffer_size, 16);
92 break;
93 // NV12 has an NxN Y plane and an interlaced (N/2)x(N/2) U and V plane.
94 case fuchsia_sysmem_PixelFormatType_NV12:
95 SetImagePlaneInfoPlanarYuv(format, buffer_size, 8, 4, 2, true, false);
96 break;
97 // I420 has an NxN Y plane and separate (N/2)x(N/2) U and V planes.
98 case fuchsia_sysmem_PixelFormatType_I420:
99 SetImagePlaneInfoPlanarYuv(format, buffer_size, 8, 4, 2, false, false);
100 break;
101 // M420 is interleaved version of I420, with 2 rows of Y and one row of equal size with
102 // 2x2 subsampled U and V. It results in 12 bits per pixel, but since it is organized
103 // as height * 1.5 rows, SetImagePlaneInfoPacked will not work if line padding is != 0.
104 case fuchsia_sysmem_PixelFormatType_M420:
105 SetImagePlaneInfoPacked(format, buffer_size, 12);
106 break;
107 default:
108 FX_LOGF(ERROR, kTag, "Unsupported pixel format %u\n", format->pixel_format.type);
109 // static_cast<const uint32_t>(spec.image.pixel_format));
110 return ZX_ERR_NOT_SUPPORTED;
111 }
112 return ZX_OK;
113 }
114
115 } // namespace
116
Allocator_AllocateCollection(void * ctx,uint32_t buffer_count,const fuchsia_sysmem_BufferSpec * spec,const fuchsia_sysmem_BufferUsage * usage,fidl_txn_t * txn)117 static zx_status_t Allocator_AllocateCollection(void* ctx,
118 uint32_t buffer_count,
119 const fuchsia_sysmem_BufferSpec* spec,
120 const fuchsia_sysmem_BufferUsage* usage,
121 fidl_txn_t* txn) {
122 fuchsia_sysmem_BufferCollectionInfo info;
123 memset(&info, 0, sizeof(info));
124 // Most basic usage of the allocator: create vmos with no special vendor format:
125 // 1) Pick which format gets used. For the simple case, just use whatever format was given.
126 // We also assume here that the format is an ImageFormat
127 ZX_ASSERT(info.format.tag == fuchsia_sysmem_BufferSpecTag_image);
128 zx_status_t status = PickImageFormat(*spec, &info.format.image, &info.vmo_size);
129 if (status != ZX_OK) {
130 FX_LOG(ERROR, kTag, "Failed to pick format for Buffer Collection\n");
131 return fuchsia_sysmem_AllocatorAllocateCollection_reply(txn, status, &info);
132 }
133
134 // 3) Allocate the buffers. This will be specialized for different formats.
135 info.buffer_count = buffer_count;
136 for (uint32_t i = 0; i < buffer_count; ++i) {
137 status = zx_vmo_create(info.vmo_size, 0, &info.vmos[i]);
138 if (status != ZX_OK) {
139 // Close the handles we created already. We do not support partial allocations.
140 for (uint32_t j = 0; j < i; ++j) {
141 zx_handle_close(info.vmos[j]);
142 info.vmos[j] = ZX_HANDLE_INVALID;
143 }
144 info.buffer_count = 0;
145 FX_LOG(ERROR, kTag, "Failed to allocate Buffer Collection\n");
146 return fuchsia_sysmem_AllocatorAllocateCollection_reply(txn, ZX_ERR_NO_MEMORY, &info);
147 }
148 }
149 // If everything is happy and allocated, can give ZX_OK:
150 return fuchsia_sysmem_AllocatorAllocateCollection_reply(txn, ZX_OK, &info);
151 }
152
Allocator_AllocateSharedCollection(void * ctx,uint32_t buffer_count,const fuchsia_sysmem_BufferSpec * spec,zx_handle_t token_peer,fidl_txn_t * txn)153 static zx_status_t Allocator_AllocateSharedCollection(void* ctx,
154 uint32_t buffer_count,
155 const fuchsia_sysmem_BufferSpec* spec,
156 zx_handle_t token_peer,
157 fidl_txn_t* txn) {
158 return fuchsia_sysmem_AllocatorAllocateSharedCollection_reply(txn, ZX_ERR_NOT_SUPPORTED);
159 }
160
Allocator_BindSharedCollection(void * ctx,const fuchsia_sysmem_BufferUsage * usage,zx_handle_t token,fidl_txn_t * txn)161 static zx_status_t Allocator_BindSharedCollection(void* ctx,
162 const fuchsia_sysmem_BufferUsage* usage,
163 zx_handle_t token,
164 fidl_txn_t* txn) {
165 fuchsia_sysmem_BufferCollectionInfo info;
166 memset(&info, 0, sizeof(info));
167 return fuchsia_sysmem_AllocatorBindSharedCollection_reply(txn, ZX_ERR_NOT_SUPPORTED, &info);
168 }
169
170 static constexpr const fuchsia_sysmem_Allocator_ops_t allocator_ops = {
171 .AllocateCollection = Allocator_AllocateCollection,
172 .AllocateSharedCollection = Allocator_AllocateSharedCollection,
173 .BindSharedCollection = Allocator_BindSharedCollection,
174 };
175
connect(void * ctx,async_dispatcher_t * dispatcher,const char * service_name,zx_handle_t request)176 static zx_status_t connect(void* ctx, async_dispatcher_t* dispatcher,
177 const char* service_name, zx_handle_t request) {
178 if (!strcmp(service_name, fuchsia_sysmem_Allocator_Name)) {
179 return fidl_bind(dispatcher, request,
180 (fidl_dispatch_t*)fuchsia_sysmem_Allocator_dispatch,
181 ctx, &allocator_ops);
182 }
183
184 zx_handle_close(request);
185 return ZX_ERR_NOT_SUPPORTED;
186 }
187
188 static constexpr const char* sysmem_services[] = {
189 fuchsia_sysmem_Allocator_Name,
190 nullptr,
191 };
192
193 static constexpr zx_service_ops_t sysmem_ops = {
194 .init = nullptr,
195 .connect = connect,
196 .release = nullptr,
197 };
198
199 static constexpr zx_service_provider_t sysmem_service_provider = {
200 .version = SERVICE_PROVIDER_VERSION,
201 .services = sysmem_services,
202 .ops = &sysmem_ops,
203 };
204
sysmem_get_service_provider()205 const zx_service_provider_t* sysmem_get_service_provider() {
206 return &sysmem_service_provider;
207 }
208