1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // Versions of hwcomposer we implement:
18 // JB: 0.3
19 // JB-MR1 to N : 1.1
20 // N-MR1 to ... : We report 1.1 but SurfaceFlinger has the option to use an
21 // adapter to treat our 1.1 hwcomposer as a 2.0. If SF stops using that adapter
22 // to support 1.1 implementations it can be copied into cuttlefish from
23 // frameworks/native/services/surfaceflinger/DisplayHardware/HWC2On1Adapter.*
24
25 #define LOG_TAG "hwc.cf_x86"
26 #define HWC_REMOVE_DEPRECATED_VERSIONS 1
27
28 #include "guest/hals/hwcomposer/common/hwcomposer.h"
29
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <math.h>
33 #include <poll.h>
34 #include <pthread.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <sync/sync.h>
38 #include <sys/resource.h>
39 #include <sys/time.h>
40
41 #include <sstream>
42 #include <string>
43 #include <vector>
44
45 #include <cutils/compiler.h>
46 #include <cutils/properties.h>
47 #include <hardware/gralloc.h>
48 #include <hardware/hardware.h>
49 #include <hardware/hwcomposer.h>
50 #include <hardware/hwcomposer_defs.h>
51 #include <log/log.h>
52 #include <utils/String8.h>
53 #include <utils/Vector.h>
54
55 #include "guest/hals/hwcomposer/common/base_composer.h"
56 #include "guest/hals/hwcomposer/common/cpu_composer.h"
57 #include "guest/hals/hwcomposer/common/geometry_utils.h"
58 #include "guest/hals/hwcomposer/common/hwcomposer.h"
59
60 #ifdef USE_OLD_HWCOMPOSER
61 typedef cvd::BaseComposer ComposerType;
62 #else
63 typedef cvd::CpuComposer ComposerType;
64 #endif
65
66 struct hwc_composer_device_data_t {
67 const hwc_procs_t* procs;
68 pthread_t vsync_thread;
69 int64_t vsync_base_timestamp;
70 int32_t vsync_period_ns;
71 };
72
73 struct cvd_hwc_composer_device_1_t {
74 hwc_composer_device_1_t base;
75 hwc_composer_device_data_t vsync_data;
76 cvd::BaseComposer* composer;
77 };
78
79 struct external_display_config_t {
80 uint64_t physicalId;
81 uint32_t width;
82 uint32_t height;
83 uint32_t dpi;
84 uint32_t flags;
85 };
86
87 namespace {
88
hwc_vsync_thread(void * data)89 void* hwc_vsync_thread(void* data) {
90 struct hwc_composer_device_data_t* pdev =
91 (struct hwc_composer_device_data_t*)data;
92 setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
93
94 int64_t base_timestamp = pdev->vsync_base_timestamp;
95 int64_t last_logged = base_timestamp / 1e9;
96 int sent = 0;
97 int last_sent = 0;
98 static const int log_interval = 60;
99 void (*vsync_proc)(const struct hwc_procs*, int, int64_t) = nullptr;
100 bool log_no_procs = true, log_no_vsync = true;
101 while (true) {
102 struct timespec rt;
103 if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
104 LOG_ALWAYS_FATAL("%s:%d error in vsync thread clock_gettime: %s",
105 __FILE__, __LINE__, strerror(errno));
106 }
107
108 int64_t timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
109 // Given now's timestamp calculate the time of the next timestamp.
110 timestamp += pdev->vsync_period_ns -
111 (timestamp - base_timestamp) % pdev->vsync_period_ns;
112
113 rt.tv_sec = timestamp / 1e9;
114 rt.tv_nsec = timestamp % static_cast<int32_t>(1e9);
115 int err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &rt, NULL);
116 if (err == -1) {
117 ALOGE("error in vsync thread: %s", strerror(errno));
118 if (errno == EINTR) {
119 continue;
120 }
121 }
122
123 // The vsync thread is started on device open, it may run before the
124 // registerProcs callback has a chance to be called, so we need to make sure
125 // procs is not NULL before dereferencing it.
126 if (pdev && pdev->procs) {
127 vsync_proc = pdev->procs->vsync;
128 } else if (log_no_procs) {
129 log_no_procs = false;
130 ALOGI("procs is not set yet, unable to deliver vsync event");
131 }
132 if (vsync_proc) {
133 vsync_proc(const_cast<hwc_procs_t*>(pdev->procs), 0, timestamp);
134 ++sent;
135 } else if (log_no_vsync) {
136 log_no_vsync = false;
137 ALOGE("vsync callback is null (but procs was already set)");
138 }
139 if (rt.tv_sec - last_logged > log_interval) {
140 ALOGI("Sent %d syncs in %ds", sent - last_sent, log_interval);
141 last_logged = rt.tv_sec;
142 last_sent = sent;
143 }
144 }
145
146 return NULL;
147 }
148
CompositionString(int type)149 std::string CompositionString(int type) {
150 switch (type) {
151 case HWC_FRAMEBUFFER:
152 return "Framebuffer";
153 case HWC_OVERLAY:
154 return "Overlay";
155 case HWC_BACKGROUND:
156 return "Background";
157 case HWC_FRAMEBUFFER_TARGET:
158 return "FramebufferTarget";
159 case HWC_SIDEBAND:
160 return "Sideband";
161 case HWC_CURSOR_OVERLAY:
162 return "CursorOverlay";
163 default:
164 return std::string("Unknown (") + std::to_string(type) + ")";
165 }
166 }
167
LogLayers(int num_layers,hwc_layer_1_t * layers,int invalid)168 void LogLayers(int num_layers, hwc_layer_1_t* layers, int invalid) {
169 ALOGE("Layers:");
170 for (int idx = 0; idx < num_layers; ++idx) {
171 std::string log_line;
172 if (idx == invalid) {
173 log_line = "Invalid layer: ";
174 }
175 log_line +=
176 "Composition Type: " + CompositionString(layers[idx].compositionType);
177 ALOGE("%s", log_line.c_str());
178 }
179 }
180
181 // Ensures that the layer does not include any inconsistencies
IsValidLayer(hwc_composer_device_1_t * dev,const hwc_layer_1_t & layer)182 bool IsValidLayer(hwc_composer_device_1_t* dev, const hwc_layer_1_t& layer) {
183 if (layer.flags & HWC_SKIP_LAYER) {
184 // A layer we are asked to skip validate should not be marked as skip
185 ALOGE("%s: Layer is marked as skip", __FUNCTION__);
186 return false;
187 }
188 // Check displayFrame
189 if (layer.displayFrame.left > layer.displayFrame.right ||
190 layer.displayFrame.top > layer.displayFrame.bottom) {
191 ALOGE(
192 "%s: Malformed rectangle (displayFrame): [left = %d, right = %d, top = "
193 "%d, bottom = %d]",
194 __FUNCTION__, layer.displayFrame.left, layer.displayFrame.right,
195 layer.displayFrame.top, layer.displayFrame.bottom);
196 return false;
197 }
198 // Check sourceCrop
199 if (layer.sourceCrop.left > layer.sourceCrop.right ||
200 layer.sourceCrop.top > layer.sourceCrop.bottom) {
201 ALOGE(
202 "%s: Malformed rectangle (sourceCrop): [left = %d, right = %d, top = "
203 "%d, bottom = %d]",
204 __FUNCTION__, layer.sourceCrop.left, layer.sourceCrop.right,
205 layer.sourceCrop.top, layer.sourceCrop.bottom);
206 return false;
207 }
208
209 auto* cvd_hwc_dev = reinterpret_cast<cvd_hwc_composer_device_1_t*>(dev);
210 return cvd_hwc_dev->composer->IsValidLayer(layer);
211 }
212
IsValidComposition(hwc_composer_device_1_t * dev,int num_layers,hwc_layer_1_t * layers,bool on_set)213 bool IsValidComposition(hwc_composer_device_1_t* dev, int num_layers,
214 hwc_layer_1_t* layers, bool on_set) {
215 if (num_layers == 0) {
216 ALOGE("Composition requested with 0 layers");
217 return false;
218 }
219 // Sometimes the hwcomposer receives a prepare and set calls with no other
220 // layer than the FRAMEBUFFER_TARGET with a null handler. We treat this case
221 // independently as a valid composition, but issue a warning about it.
222 if (num_layers == 1 && layers[0].compositionType == HWC_FRAMEBUFFER_TARGET &&
223 layers[0].handle == NULL) {
224 ALOGW("Received request for empty composition, treating as valid noop");
225 return true;
226 }
227 // The FRAMEBUFFER_TARGET layer needs to be sane only if
228 // there is at least one layer marked HWC_FRAMEBUFFER or if there is no layer
229 // marked HWC_OVERLAY (i.e some layers where composed with OpenGL, no layer
230 // marked overlay or framebuffer means that surfaceflinger decided to go for
231 // OpenGL without asking the hwcomposer first)
232 bool check_fb_target = true;
233 for (int idx = 0; idx < num_layers; ++idx) {
234 if (layers[idx].compositionType == HWC_FRAMEBUFFER) {
235 // There is at least one, so it needs to be checked.
236 // It may have been set to false before, so ensure it's set to true.
237 check_fb_target = true;
238 break;
239 }
240 if (layers[idx].compositionType == HWC_OVERLAY) {
241 // At least one overlay, we may not need to.
242 check_fb_target = false;
243 }
244 }
245
246 for (int idx = 0; idx < num_layers; ++idx) {
247 switch (layers[idx].compositionType) {
248 case HWC_FRAMEBUFFER_TARGET:
249 // In the call to prepare() the framebuffer target does not have a valid
250 // buffer_handle, so we don't validate it yet.
251 if (on_set && check_fb_target && !IsValidLayer(dev, layers[idx])) {
252 ALOGE("%s: Invalid layer found", __FUNCTION__);
253 LogLayers(num_layers, layers, idx);
254 return false;
255 }
256 break;
257 case HWC_OVERLAY:
258 if (!(layers[idx].flags & HWC_SKIP_LAYER) &&
259 !IsValidLayer(dev, layers[idx])) {
260 ALOGE("%s: Invalid layer found", __FUNCTION__);
261 LogLayers(num_layers, layers, idx);
262 return false;
263 }
264 break;
265 }
266 }
267 return true;
268 }
269
270 // Note predefined "hwservicemanager." is used to avoid adding new selinux rules
271 #define EXTERANL_DISPLAY_PROP "hwservicemanager.external.displays"
272
273 // return 0 for successful
274 // return < 0 if failed
GetExternalDisplayConfigs(std::vector<struct external_display_config_t> * configs)275 int GetExternalDisplayConfigs(std::vector<struct external_display_config_t>* configs) {
276 // this guest property, hwservicemanager.external.displays,
277 // specifies multi-display info, with comma (,) as separator
278 // each display has the following info:
279 // physicalId,width,height,dpi,flags
280 // several displays can be provided, e.g., following has 2 displays:
281 // setprop hwservicemanager.external.displays 1,1200,800,120,0,2,1200,800,120,0
282 std::vector<uint64_t> values;
283 char displays_value[PROPERTY_VALUE_MAX] = "";
284 property_get(EXTERANL_DISPLAY_PROP, displays_value, "");
285 bool valid = displays_value[0] != '\0';
286 if (valid) {
287 char *p = displays_value;
288 while (*p) {
289 if (!isdigit(*p) && *p != ',' && *p != ' ') {
290 valid = false;
291 break;
292 }
293 p++;
294 }
295 }
296 if (!valid) {
297 // no external displays are specified
298 ALOGE("%s: Invalid syntax for the value of system prop: %s, value: %s",
299 __FUNCTION__, EXTERANL_DISPLAY_PROP, displays_value);
300 return 0;
301 }
302 // parse all int values to a vector
303 std::istringstream stream(displays_value);
304 for (uint64_t id; stream >> id;) {
305 values.push_back(id);
306 if (stream.peek() == ',')
307 stream.ignore();
308 }
309 // each display has 5 values
310 if ((values.size() % 5) != 0) {
311 ALOGE("%s: Invalid value for system property: %s", __FUNCTION__, EXTERANL_DISPLAY_PROP);
312 return -1;
313 }
314 while (!values.empty()) {
315 struct external_display_config_t config;
316 config.physicalId = values[0];
317 config.width = values[1];
318 config.height = values[2];
319 config.dpi = values[3];
320 config.flags = values[4];
321 values.erase(values.begin(), values.begin() + 5);
322 configs->push_back(config);
323 }
324 return 0;
325 }
326
327 } // namespace
328
cvd_hwc_prepare(hwc_composer_device_1_t * dev,size_t numDisplays,hwc_display_contents_1_t ** displays)329 static int cvd_hwc_prepare(hwc_composer_device_1_t* dev, size_t numDisplays,
330 hwc_display_contents_1_t** displays) {
331 if (!numDisplays || !displays) return 0;
332
333 for (int disp = 0; disp < numDisplays; ++disp) {
334 hwc_display_contents_1_t* list = displays[disp];
335
336 if (!list) return 0;
337 if (!IsValidComposition(dev, list->numHwLayers, &list->hwLayers[0], false)) {
338 LOG_ALWAYS_FATAL("%s: Invalid composition requested", __FUNCTION__);
339 return -1;
340 }
341 reinterpret_cast<cvd_hwc_composer_device_1_t*>(dev)->composer->PrepareLayers(
342 list->numHwLayers, &list->hwLayers[0]);
343 }
344 return 0;
345 }
346
cvd_hwc_set(hwc_composer_device_1_t * dev,size_t numDisplays,hwc_display_contents_1_t ** displays)347 static int cvd_hwc_set(hwc_composer_device_1_t* dev, size_t numDisplays,
348 hwc_display_contents_1_t** displays) {
349 if (!numDisplays || !displays) return 0;
350
351 int retval = -1;
352 for (int disp = 0; disp < numDisplays; ++disp) {
353 hwc_display_contents_1_t* contents = displays[disp];
354 if (!contents) return 0;
355
356 hwc_layer_1_t* layers = &contents->hwLayers[0];
357 if (contents->numHwLayers == 1 &&
358 layers[0].compositionType == HWC_FRAMEBUFFER_TARGET) {
359 ALOGW("Received request for empty composition, treating as valid noop");
360 return 0;
361 }
362 if (!IsValidComposition(dev, contents->numHwLayers, layers, true)) {
363 LOG_ALWAYS_FATAL("%s: Invalid composition requested", __FUNCTION__);
364 return -1;
365 }
366 retval =
367 reinterpret_cast<cvd_hwc_composer_device_1_t*>(dev)->composer->SetLayers(
368 contents->numHwLayers, layers);
369 if (retval != 0) break;
370
371 int closedFds = 0;
372 for (size_t index = 0; index < contents->numHwLayers; ++index) {
373 if (layers[index].acquireFenceFd != -1) {
374 close(layers[index].acquireFenceFd);
375 layers[index].acquireFenceFd = -1;
376 ++closedFds;
377 }
378 }
379 if (closedFds) {
380 ALOGI("Saw %zu layers, closed=%d", contents->numHwLayers, closedFds);
381 }
382
383 // TODO(ghartman): This should be set before returning. On the next set it
384 // should be signalled when we load the new frame.
385 contents->retireFenceFd = -1;
386 }
387
388 return retval;
389 }
390
cvd_hwc_register_procs(hwc_composer_device_1_t * dev,const hwc_procs_t * procs)391 static void cvd_hwc_register_procs(hwc_composer_device_1_t* dev,
392 const hwc_procs_t* procs) {
393 struct cvd_hwc_composer_device_1_t* pdev =
394 (struct cvd_hwc_composer_device_1_t*)dev;
395 pdev->vsync_data.procs = procs;
396 if (procs) {
397 std::vector<struct external_display_config_t> configs;
398 int res = GetExternalDisplayConfigs(&configs);
399 if (res == 0 && !configs.empty()) {
400 // configs will be used in the future
401 procs->hotplug(procs, HWC_DISPLAY_EXTERNAL, 1);
402 }
403 }
404 }
405
cvd_hwc_query(hwc_composer_device_1_t * dev,int what,int * value)406 static int cvd_hwc_query(hwc_composer_device_1_t* dev, int what, int* value) {
407 struct cvd_hwc_composer_device_1_t* pdev =
408 (struct cvd_hwc_composer_device_1_t*)dev;
409
410 switch (what) {
411 case HWC_BACKGROUND_LAYER_SUPPORTED:
412 // we support the background layer
413 value[0] = 0;
414 break;
415 case HWC_VSYNC_PERIOD:
416 value[0] = pdev->vsync_data.vsync_period_ns;
417 break;
418 default:
419 // unsupported query
420 ALOGE("%s badness unsupported query what=%d", __FUNCTION__, what);
421 return -EINVAL;
422 }
423 return 0;
424 }
425
cvd_hwc_event_control(hwc_composer_device_1_t *,int,int event,int)426 static int cvd_hwc_event_control(hwc_composer_device_1_t* /*dev*/, int /*dpy*/,
427 int event, int /*enabled*/) {
428 if (event == HWC_EVENT_VSYNC) {
429 return 0;
430 }
431 return -EINVAL;
432 }
433
cvd_hwc_blank(hwc_composer_device_1_t *,int disp,int)434 static int cvd_hwc_blank(hwc_composer_device_1_t* /*dev*/, int disp, int /*blank*/) {
435 if (!IS_PRIMARY_DISPLAY(disp) && !IS_EXTERNAL_DISPLAY(disp)) return -EINVAL;
436 return 0;
437 }
438
cvd_hwc_dump(hwc_composer_device_1_t * dev,char * buff,int buff_len)439 static void cvd_hwc_dump(hwc_composer_device_1_t* dev, char* buff, int buff_len) {
440 reinterpret_cast<cvd_hwc_composer_device_1_t*>(dev)->composer->Dump(buff,
441 buff_len);
442 }
443
cvd_hwc_get_display_configs(hwc_composer_device_1_t *,int disp,uint32_t * configs,size_t * numConfigs)444 static int cvd_hwc_get_display_configs(hwc_composer_device_1_t* /*dev*/, int disp,
445 uint32_t* configs, size_t* numConfigs) {
446 if (*numConfigs == 0) return 0;
447
448 if (IS_PRIMARY_DISPLAY(disp) || IS_EXTERNAL_DISPLAY(disp)) {
449 configs[0] = 0;
450 *numConfigs = 1;
451 return 0;
452 }
453
454 return -EINVAL;
455 }
456
cvd_hwc_attribute(struct cvd_hwc_composer_device_1_t * pdev,const uint32_t attribute)457 static int32_t cvd_hwc_attribute(struct cvd_hwc_composer_device_1_t* pdev,
458 const uint32_t attribute) {
459 switch (attribute) {
460 case HWC_DISPLAY_VSYNC_PERIOD:
461 return pdev->vsync_data.vsync_period_ns;
462 case HWC_DISPLAY_WIDTH:
463 return pdev->composer->x_res();
464 case HWC_DISPLAY_HEIGHT:
465 return pdev->composer->y_res();
466 case HWC_DISPLAY_DPI_X:
467 ALOGI("Reporting DPI_X of %d", pdev->composer->dpi());
468 // The number of pixels per thousand inches
469 return pdev->composer->dpi() * 1000;
470 case HWC_DISPLAY_DPI_Y:
471 ALOGI("Reporting DPI_Y of %d", pdev->composer->dpi());
472 // The number of pixels per thousand inches
473 return pdev->composer->dpi() * 1000;
474 default:
475 ALOGE("unknown display attribute %u", attribute);
476 return -EINVAL;
477 }
478 }
479
cvd_hwc_get_display_attributes(hwc_composer_device_1_t * dev,int disp,uint32_t config __unused,const uint32_t * attributes,int32_t * values)480 static int cvd_hwc_get_display_attributes(hwc_composer_device_1_t* dev, int disp,
481 uint32_t config __unused,
482 const uint32_t* attributes,
483 int32_t* values) {
484 struct cvd_hwc_composer_device_1_t* pdev =
485 (struct cvd_hwc_composer_device_1_t*)dev;
486 if (!IS_PRIMARY_DISPLAY(disp) && !IS_EXTERNAL_DISPLAY(disp)) {
487 ALOGE("unknown display type %u", disp);
488 return -EINVAL;
489 }
490
491 for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; i++) {
492 values[i] = cvd_hwc_attribute(pdev, attributes[i]);
493 }
494
495 return 0;
496 }
497
cvd_hwc_close(hw_device_t * device)498 static int cvd_hwc_close(hw_device_t* device) {
499 struct cvd_hwc_composer_device_1_t* dev =
500 (struct cvd_hwc_composer_device_1_t*)device;
501 ALOGE("cvd_hwc_close");
502 pthread_kill(dev->vsync_data.vsync_thread, SIGTERM);
503 pthread_join(dev->vsync_data.vsync_thread, NULL);
504 delete dev->composer;
505 delete dev;
506 return 0;
507 }
508
509 namespace cvd {
510
cvd_hwc_open(std::unique_ptr<ScreenView> screen_view,const struct hw_module_t * module,const char * name,struct hw_device_t ** device)511 int cvd_hwc_open(std::unique_ptr<ScreenView> screen_view,
512 const struct hw_module_t* module, const char* name,
513 struct hw_device_t** device) {
514 ALOGI("%s", __FUNCTION__);
515 if (strcmp(name, HWC_HARDWARE_COMPOSER)) {
516 ALOGE("%s called with bad name %s", __FUNCTION__, name);
517 return -EINVAL;
518 }
519
520 cvd_hwc_composer_device_1_t* dev = new cvd_hwc_composer_device_1_t();
521 if (!dev) {
522 ALOGE("%s failed to allocate dev", __FUNCTION__);
523 return -ENOMEM;
524 }
525
526 struct timespec rt;
527 if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
528 ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
529 strerror(errno));
530 }
531 dev->vsync_data.vsync_base_timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
532 dev->vsync_data.vsync_period_ns = 1e9 / screen_view->refresh_rate();
533
534 dev->base.common.tag = HARDWARE_DEVICE_TAG;
535 dev->base.common.version = HWC_DEVICE_API_VERSION_1_1;
536 dev->base.common.module = const_cast<hw_module_t*>(module);
537 dev->base.common.close = cvd_hwc_close;
538
539 dev->base.prepare = cvd_hwc_prepare;
540 dev->base.set = cvd_hwc_set;
541 dev->base.query = cvd_hwc_query;
542 dev->base.registerProcs = cvd_hwc_register_procs;
543 dev->base.dump = cvd_hwc_dump;
544 dev->base.blank = cvd_hwc_blank;
545 dev->base.eventControl = cvd_hwc_event_control;
546 dev->base.getDisplayConfigs = cvd_hwc_get_display_configs;
547 dev->base.getDisplayAttributes = cvd_hwc_get_display_attributes;
548 #ifdef GATHER_STATS
549 dev->composer = new cvd::StatsKeepingComposer<ComposerType>(
550 dev->vsync_data.vsync_base_timestamp, std::move(screen_view));
551 #else
552 dev->composer = new ComposerType(std::move(screen_view));
553 #endif
554
555 if (!dev->composer) {
556 ALOGE("Failed to instantiate the composer object");
557 delete dev;
558 return -1;
559 }
560 int ret = pthread_create(&dev->vsync_data.vsync_thread, NULL,
561 hwc_vsync_thread, &dev->vsync_data);
562 if (ret) {
563 ALOGE("failed to start vsync thread: %s", strerror(ret));
564 ret = -ret;
565 delete dev->composer;
566 delete dev;
567 } else {
568 *device = &dev->base.common;
569 }
570
571 return ret;
572 }
573
574 } // namespace cvd
575