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