731 lines · c
1// SPDX-License-Identifier: MIT2/*3 * Copyright 2020 Noralf Trønnes4 */5 6#include <linux/backlight.h>7#include <linux/workqueue.h>8 9#include <drm/drm_atomic.h>10#include <drm/drm_atomic_state_helper.h>11#include <drm/drm_connector.h>12#include <drm/drm_drv.h>13#include <drm/drm_edid.h>14#include <drm/drm_encoder.h>15#include <drm/drm_file.h>16#include <drm/drm_modeset_helper_vtables.h>17#include <drm/drm_print.h>18#include <drm/drm_probe_helper.h>19#include <drm/drm_simple_kms_helper.h>20#include <drm/gud.h>21 22#include "gud_internal.h"23 24struct gud_connector {25 struct drm_connector connector;26 struct drm_encoder encoder;27 struct backlight_device *backlight;28 struct work_struct backlight_work;29 30 /* Supported properties */31 u16 *properties;32 unsigned int num_properties;33 34 /* Initial gadget tv state if applicable, applied on state reset */35 struct drm_tv_connector_state initial_tv_state;36 37 /*38 * Initial gadget backlight brightness if applicable, applied on state reset.39 * The value -ENODEV is used to signal no backlight.40 */41 int initial_brightness;42};43 44static inline struct gud_connector *to_gud_connector(struct drm_connector *connector)45{46 return container_of(connector, struct gud_connector, connector);47}48 49static void gud_conn_err(struct drm_connector *connector, const char *msg, int ret)50{51 dev_err(connector->dev->dev, "%s: %s (ret=%d)\n", connector->name, msg, ret);52}53 54/*55 * Use a worker to avoid taking kms locks inside the backlight lock.56 * Other display drivers use backlight within their kms locks.57 * This avoids inconsistent locking rules, which would upset lockdep.58 */59static void gud_connector_backlight_update_status_work(struct work_struct *work)60{61 struct gud_connector *gconn = container_of(work, struct gud_connector, backlight_work);62 struct drm_connector *connector = &gconn->connector;63 struct drm_connector_state *connector_state;64 struct drm_device *drm = connector->dev;65 struct drm_modeset_acquire_ctx ctx;66 struct drm_atomic_state *state;67 int idx, ret;68 69 if (!drm_dev_enter(drm, &idx))70 return;71 72 state = drm_atomic_state_alloc(drm);73 if (!state) {74 ret = -ENOMEM;75 goto exit;76 }77 78 drm_modeset_acquire_init(&ctx, 0);79 state->acquire_ctx = &ctx;80retry:81 connector_state = drm_atomic_get_connector_state(state, connector);82 if (IS_ERR(connector_state)) {83 ret = PTR_ERR(connector_state);84 goto out;85 }86 87 /* Reuse tv.brightness to avoid having to subclass */88 connector_state->tv.brightness = gconn->backlight->props.brightness;89 90 ret = drm_atomic_commit(state);91out:92 if (ret == -EDEADLK) {93 drm_atomic_state_clear(state);94 drm_modeset_backoff(&ctx);95 goto retry;96 }97 98 drm_atomic_state_put(state);99 100 drm_modeset_drop_locks(&ctx);101 drm_modeset_acquire_fini(&ctx);102exit:103 drm_dev_exit(idx);104 105 if (ret)106 dev_err(drm->dev, "Failed to update backlight, err=%d\n", ret);107}108 109static int gud_connector_backlight_update_status(struct backlight_device *bd)110{111 struct drm_connector *connector = bl_get_data(bd);112 struct gud_connector *gconn = to_gud_connector(connector);113 114 /* The USB timeout is 5 seconds so use system_long_wq for worst case scenario */115 queue_work(system_long_wq, &gconn->backlight_work);116 117 return 0;118}119 120static const struct backlight_ops gud_connector_backlight_ops = {121 .update_status = gud_connector_backlight_update_status,122};123 124static int gud_connector_backlight_register(struct gud_connector *gconn)125{126 struct drm_connector *connector = &gconn->connector;127 struct backlight_device *bd;128 const char *name;129 const struct backlight_properties props = {130 .type = BACKLIGHT_RAW,131 .scale = BACKLIGHT_SCALE_NON_LINEAR,132 .max_brightness = 100,133 .brightness = gconn->initial_brightness,134 };135 136 name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",137 connector->dev->primary->index, connector->name);138 if (!name)139 return -ENOMEM;140 141 bd = backlight_device_register(name, connector->kdev, connector,142 &gud_connector_backlight_ops, &props);143 kfree(name);144 if (IS_ERR(bd))145 return PTR_ERR(bd);146 147 gconn->backlight = bd;148 149 return 0;150}151 152static int gud_connector_detect(struct drm_connector *connector,153 struct drm_modeset_acquire_ctx *ctx, bool force)154{155 struct gud_device *gdrm = to_gud_device(connector->dev);156 int idx, ret;157 u8 status;158 159 if (!drm_dev_enter(connector->dev, &idx))160 return connector_status_disconnected;161 162 if (force) {163 ret = gud_usb_set(gdrm, GUD_REQ_SET_CONNECTOR_FORCE_DETECT,164 connector->index, NULL, 0);165 if (ret) {166 ret = connector_status_unknown;167 goto exit;168 }169 }170 171 ret = gud_usb_get_u8(gdrm, GUD_REQ_GET_CONNECTOR_STATUS, connector->index, &status);172 if (ret) {173 ret = connector_status_unknown;174 goto exit;175 }176 177 switch (status & GUD_CONNECTOR_STATUS_CONNECTED_MASK) {178 case GUD_CONNECTOR_STATUS_DISCONNECTED:179 ret = connector_status_disconnected;180 break;181 case GUD_CONNECTOR_STATUS_CONNECTED:182 ret = connector_status_connected;183 break;184 default:185 ret = connector_status_unknown;186 break;187 }188 189 if (status & GUD_CONNECTOR_STATUS_CHANGED)190 connector->epoch_counter += 1;191exit:192 drm_dev_exit(idx);193 194 return ret;195}196 197struct gud_connector_get_edid_ctx {198 void *buf;199 size_t len;200 bool edid_override;201};202 203static int gud_connector_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)204{205 struct gud_connector_get_edid_ctx *ctx = data;206 size_t start = block * EDID_LENGTH;207 208 ctx->edid_override = false;209 210 if (start + len > ctx->len)211 return -1;212 213 memcpy(buf, ctx->buf + start, len);214 215 return 0;216}217 218static int gud_connector_get_modes(struct drm_connector *connector)219{220 struct gud_device *gdrm = to_gud_device(connector->dev);221 struct gud_display_mode_req *reqmodes = NULL;222 struct gud_connector_get_edid_ctx edid_ctx;223 unsigned int i, num_modes = 0;224 const struct drm_edid *drm_edid = NULL;225 int idx, ret;226 227 if (!drm_dev_enter(connector->dev, &idx))228 return 0;229 230 edid_ctx.edid_override = true;231 edid_ctx.buf = kmalloc(GUD_CONNECTOR_MAX_EDID_LEN, GFP_KERNEL);232 if (!edid_ctx.buf)233 goto out;234 235 ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_EDID, connector->index,236 edid_ctx.buf, GUD_CONNECTOR_MAX_EDID_LEN);237 if (ret > 0 && ret % EDID_LENGTH) {238 gud_conn_err(connector, "Invalid EDID size", ret);239 } else if (ret > 0) {240 edid_ctx.len = ret;241 drm_edid = drm_edid_read_custom(connector, gud_connector_get_edid_block, &edid_ctx);242 }243 244 kfree(edid_ctx.buf);245 drm_edid_connector_update(connector, drm_edid);246 247 if (drm_edid && edid_ctx.edid_override)248 goto out;249 250 reqmodes = kmalloc_array(GUD_CONNECTOR_MAX_NUM_MODES, sizeof(*reqmodes), GFP_KERNEL);251 if (!reqmodes)252 goto out;253 254 ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_MODES, connector->index,255 reqmodes, GUD_CONNECTOR_MAX_NUM_MODES * sizeof(*reqmodes));256 if (ret <= 0)257 goto out;258 if (ret % sizeof(*reqmodes)) {259 gud_conn_err(connector, "Invalid display mode array size", ret);260 goto out;261 }262 263 num_modes = ret / sizeof(*reqmodes);264 265 for (i = 0; i < num_modes; i++) {266 struct drm_display_mode *mode;267 268 mode = drm_mode_create(connector->dev);269 if (!mode) {270 num_modes = i;271 goto out;272 }273 274 gud_to_display_mode(mode, &reqmodes[i]);275 drm_mode_probed_add(connector, mode);276 }277out:278 if (!num_modes)279 num_modes = drm_edid_connector_add_modes(connector);280 281 kfree(reqmodes);282 drm_edid_free(drm_edid);283 drm_dev_exit(idx);284 285 return num_modes;286}287 288static int gud_connector_atomic_check(struct drm_connector *connector,289 struct drm_atomic_state *state)290{291 struct drm_connector_state *new_state;292 struct drm_crtc_state *new_crtc_state;293 struct drm_connector_state *old_state;294 295 new_state = drm_atomic_get_new_connector_state(state, connector);296 if (!new_state->crtc)297 return 0;298 299 old_state = drm_atomic_get_old_connector_state(state, connector);300 new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);301 302 if (old_state->tv.margins.left != new_state->tv.margins.left ||303 old_state->tv.margins.right != new_state->tv.margins.right ||304 old_state->tv.margins.top != new_state->tv.margins.top ||305 old_state->tv.margins.bottom != new_state->tv.margins.bottom ||306 old_state->tv.legacy_mode != new_state->tv.legacy_mode ||307 old_state->tv.brightness != new_state->tv.brightness ||308 old_state->tv.contrast != new_state->tv.contrast ||309 old_state->tv.flicker_reduction != new_state->tv.flicker_reduction ||310 old_state->tv.overscan != new_state->tv.overscan ||311 old_state->tv.saturation != new_state->tv.saturation ||312 old_state->tv.hue != new_state->tv.hue)313 new_crtc_state->connectors_changed = true;314 315 return 0;316}317 318static const struct drm_connector_helper_funcs gud_connector_helper_funcs = {319 .detect_ctx = gud_connector_detect,320 .get_modes = gud_connector_get_modes,321 .atomic_check = gud_connector_atomic_check,322};323 324static int gud_connector_late_register(struct drm_connector *connector)325{326 struct gud_connector *gconn = to_gud_connector(connector);327 328 if (gconn->initial_brightness < 0)329 return 0;330 331 return gud_connector_backlight_register(gconn);332}333 334static void gud_connector_early_unregister(struct drm_connector *connector)335{336 struct gud_connector *gconn = to_gud_connector(connector);337 338 backlight_device_unregister(gconn->backlight);339 cancel_work_sync(&gconn->backlight_work);340}341 342static void gud_connector_destroy(struct drm_connector *connector)343{344 struct gud_connector *gconn = to_gud_connector(connector);345 346 drm_connector_cleanup(connector);347 kfree(gconn->properties);348 kfree(gconn);349}350 351static void gud_connector_reset(struct drm_connector *connector)352{353 struct gud_connector *gconn = to_gud_connector(connector);354 355 drm_atomic_helper_connector_reset(connector);356 connector->state->tv = gconn->initial_tv_state;357 /* Set margins from command line */358 drm_atomic_helper_connector_tv_margins_reset(connector);359 if (gconn->initial_brightness >= 0)360 connector->state->tv.brightness = gconn->initial_brightness;361}362 363static const struct drm_connector_funcs gud_connector_funcs = {364 .fill_modes = drm_helper_probe_single_connector_modes,365 .late_register = gud_connector_late_register,366 .early_unregister = gud_connector_early_unregister,367 .destroy = gud_connector_destroy,368 .reset = gud_connector_reset,369 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,370 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,371};372 373/*374 * The tv.mode property is shared among the connectors and its enum names are375 * driver specific. This means that if more than one connector uses tv.mode,376 * the enum names has to be the same.377 */378static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connector *connector)379{380 size_t buf_len = GUD_CONNECTOR_TV_MODE_MAX_NUM * GUD_CONNECTOR_TV_MODE_NAME_LEN;381 const char *modes[GUD_CONNECTOR_TV_MODE_MAX_NUM];382 unsigned int i, num_modes;383 char *buf;384 int ret;385 386 buf = kmalloc(buf_len, GFP_KERNEL);387 if (!buf)388 return -ENOMEM;389 390 ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES,391 connector->index, buf, buf_len);392 if (ret < 0)393 goto free;394 if (!ret || ret % GUD_CONNECTOR_TV_MODE_NAME_LEN) {395 ret = -EIO;396 goto free;397 }398 399 num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN;400 for (i = 0; i < num_modes; i++)401 modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN];402 403 ret = drm_mode_create_tv_properties_legacy(connector->dev, num_modes, modes);404free:405 kfree(buf);406 if (ret < 0)407 gud_conn_err(connector, "Failed to add TV modes", ret);408 409 return ret;410}411 412static struct drm_property *413gud_connector_property_lookup(struct drm_connector *connector, u16 prop)414{415 struct drm_mode_config *config = &connector->dev->mode_config;416 417 switch (prop) {418 case GUD_PROPERTY_TV_LEFT_MARGIN:419 return config->tv_left_margin_property;420 case GUD_PROPERTY_TV_RIGHT_MARGIN:421 return config->tv_right_margin_property;422 case GUD_PROPERTY_TV_TOP_MARGIN:423 return config->tv_top_margin_property;424 case GUD_PROPERTY_TV_BOTTOM_MARGIN:425 return config->tv_bottom_margin_property;426 case GUD_PROPERTY_TV_MODE:427 return config->legacy_tv_mode_property;428 case GUD_PROPERTY_TV_BRIGHTNESS:429 return config->tv_brightness_property;430 case GUD_PROPERTY_TV_CONTRAST:431 return config->tv_contrast_property;432 case GUD_PROPERTY_TV_FLICKER_REDUCTION:433 return config->tv_flicker_reduction_property;434 case GUD_PROPERTY_TV_OVERSCAN:435 return config->tv_overscan_property;436 case GUD_PROPERTY_TV_SATURATION:437 return config->tv_saturation_property;438 case GUD_PROPERTY_TV_HUE:439 return config->tv_hue_property;440 default:441 return ERR_PTR(-EINVAL);442 }443}444 445static unsigned int *gud_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state)446{447 switch (prop) {448 case GUD_PROPERTY_TV_LEFT_MARGIN:449 return &state->margins.left;450 case GUD_PROPERTY_TV_RIGHT_MARGIN:451 return &state->margins.right;452 case GUD_PROPERTY_TV_TOP_MARGIN:453 return &state->margins.top;454 case GUD_PROPERTY_TV_BOTTOM_MARGIN:455 return &state->margins.bottom;456 case GUD_PROPERTY_TV_MODE:457 return &state->legacy_mode;458 case GUD_PROPERTY_TV_BRIGHTNESS:459 return &state->brightness;460 case GUD_PROPERTY_TV_CONTRAST:461 return &state->contrast;462 case GUD_PROPERTY_TV_FLICKER_REDUCTION:463 return &state->flicker_reduction;464 case GUD_PROPERTY_TV_OVERSCAN:465 return &state->overscan;466 case GUD_PROPERTY_TV_SATURATION:467 return &state->saturation;468 case GUD_PROPERTY_TV_HUE:469 return &state->hue;470 default:471 return ERR_PTR(-EINVAL);472 }473}474 475static int gud_connector_add_properties(struct gud_device *gdrm, struct gud_connector *gconn)476{477 struct drm_connector *connector = &gconn->connector;478 struct drm_device *drm = &gdrm->drm;479 struct gud_property_req *properties;480 unsigned int i, num_properties;481 int ret;482 483 properties = kcalloc(GUD_CONNECTOR_PROPERTIES_MAX_NUM, sizeof(*properties), GFP_KERNEL);484 if (!properties)485 return -ENOMEM;486 487 ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_PROPERTIES, connector->index,488 properties, GUD_CONNECTOR_PROPERTIES_MAX_NUM * sizeof(*properties));489 if (ret <= 0)490 goto out;491 if (ret % sizeof(*properties)) {492 ret = -EIO;493 goto out;494 }495 496 num_properties = ret / sizeof(*properties);497 ret = 0;498 499 gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL);500 if (!gconn->properties) {501 ret = -ENOMEM;502 goto out;503 }504 505 for (i = 0; i < num_properties; i++) {506 u16 prop = le16_to_cpu(properties[i].prop);507 u64 val = le64_to_cpu(properties[i].val);508 struct drm_property *property;509 unsigned int *state_val;510 511 drm_dbg(drm, "property: %u = %llu(0x%llx)\n", prop, val, val);512 513 switch (prop) {514 case GUD_PROPERTY_TV_LEFT_MARGIN:515 fallthrough;516 case GUD_PROPERTY_TV_RIGHT_MARGIN:517 fallthrough;518 case GUD_PROPERTY_TV_TOP_MARGIN:519 fallthrough;520 case GUD_PROPERTY_TV_BOTTOM_MARGIN:521 ret = drm_mode_create_tv_margin_properties(drm);522 if (ret)523 goto out;524 break;525 case GUD_PROPERTY_TV_MODE:526 ret = gud_connector_add_tv_mode(gdrm, connector);527 if (ret)528 goto out;529 break;530 case GUD_PROPERTY_TV_BRIGHTNESS:531 fallthrough;532 case GUD_PROPERTY_TV_CONTRAST:533 fallthrough;534 case GUD_PROPERTY_TV_FLICKER_REDUCTION:535 fallthrough;536 case GUD_PROPERTY_TV_OVERSCAN:537 fallthrough;538 case GUD_PROPERTY_TV_SATURATION:539 fallthrough;540 case GUD_PROPERTY_TV_HUE:541 /* This is a no-op if already added. */542 ret = drm_mode_create_tv_properties_legacy(drm, 0, NULL);543 if (ret)544 goto out;545 break;546 case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS:547 if (val > 100) {548 ret = -EINVAL;549 goto out;550 }551 gconn->initial_brightness = val;552 break;553 default:554 /* New ones might show up in future devices, skip those we don't know. */555 drm_dbg(drm, "Ignoring unknown property: %u\n", prop);556 continue;557 }558 559 gconn->properties[gconn->num_properties++] = prop;560 561 if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS)562 continue; /* not a DRM property */563 564 property = gud_connector_property_lookup(connector, prop);565 if (WARN_ON(IS_ERR(property)))566 continue;567 568 state_val = gud_connector_tv_state_val(prop, &gconn->initial_tv_state);569 if (WARN_ON(IS_ERR(state_val)))570 continue;571 572 *state_val = val;573 drm_object_attach_property(&connector->base, property, 0);574 }575out:576 kfree(properties);577 578 return ret;579}580 581int gud_connector_fill_properties(struct drm_connector_state *connector_state,582 struct gud_property_req *properties)583{584 struct gud_connector *gconn = to_gud_connector(connector_state->connector);585 unsigned int i;586 587 for (i = 0; i < gconn->num_properties; i++) {588 u16 prop = gconn->properties[i];589 u64 val;590 591 if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) {592 val = connector_state->tv.brightness;593 } else {594 unsigned int *state_val;595 596 state_val = gud_connector_tv_state_val(prop, &connector_state->tv);597 if (WARN_ON_ONCE(IS_ERR(state_val)))598 return PTR_ERR(state_val);599 600 val = *state_val;601 }602 603 properties[i].prop = cpu_to_le16(prop);604 properties[i].val = cpu_to_le64(val);605 }606 607 return gconn->num_properties;608}609 610static int gud_connector_create(struct gud_device *gdrm, unsigned int index,611 struct gud_connector_descriptor_req *desc)612{613 struct drm_device *drm = &gdrm->drm;614 struct gud_connector *gconn;615 struct drm_connector *connector;616 struct drm_encoder *encoder;617 int ret, connector_type;618 u32 flags;619 620 gconn = kzalloc(sizeof(*gconn), GFP_KERNEL);621 if (!gconn)622 return -ENOMEM;623 624 INIT_WORK(&gconn->backlight_work, gud_connector_backlight_update_status_work);625 gconn->initial_brightness = -ENODEV;626 flags = le32_to_cpu(desc->flags);627 connector = &gconn->connector;628 629 drm_dbg(drm, "Connector: index=%u type=%u flags=0x%x\n", index, desc->connector_type, flags);630 631 switch (desc->connector_type) {632 case GUD_CONNECTOR_TYPE_PANEL:633 connector_type = DRM_MODE_CONNECTOR_USB;634 break;635 case GUD_CONNECTOR_TYPE_VGA:636 connector_type = DRM_MODE_CONNECTOR_VGA;637 break;638 case GUD_CONNECTOR_TYPE_DVI:639 connector_type = DRM_MODE_CONNECTOR_DVID;640 break;641 case GUD_CONNECTOR_TYPE_COMPOSITE:642 connector_type = DRM_MODE_CONNECTOR_Composite;643 break;644 case GUD_CONNECTOR_TYPE_SVIDEO:645 connector_type = DRM_MODE_CONNECTOR_SVIDEO;646 break;647 case GUD_CONNECTOR_TYPE_COMPONENT:648 connector_type = DRM_MODE_CONNECTOR_Component;649 break;650 case GUD_CONNECTOR_TYPE_DISPLAYPORT:651 connector_type = DRM_MODE_CONNECTOR_DisplayPort;652 break;653 case GUD_CONNECTOR_TYPE_HDMI:654 connector_type = DRM_MODE_CONNECTOR_HDMIA;655 break;656 default: /* future types */657 connector_type = DRM_MODE_CONNECTOR_USB;658 break;659 }660 661 drm_connector_helper_add(connector, &gud_connector_helper_funcs);662 ret = drm_connector_init(drm, connector, &gud_connector_funcs, connector_type);663 if (ret) {664 kfree(connector);665 return ret;666 }667 668 if (WARN_ON(connector->index != index))669 return -EINVAL;670 671 if (flags & GUD_CONNECTOR_FLAGS_POLL_STATUS)672 connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT);673 if (flags & GUD_CONNECTOR_FLAGS_INTERLACE)674 connector->interlace_allowed = true;675 if (flags & GUD_CONNECTOR_FLAGS_DOUBLESCAN)676 connector->doublescan_allowed = true;677 678 ret = gud_connector_add_properties(gdrm, gconn);679 if (ret) {680 gud_conn_err(connector, "Failed to add properties", ret);681 return ret;682 }683 684 /* The first connector is attached to the existing simple pipe encoder */685 if (!connector->index) {686 encoder = &gdrm->pipe.encoder;687 } else {688 encoder = &gconn->encoder;689 690 ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);691 if (ret)692 return ret;693 694 encoder->possible_crtcs = 1;695 }696 697 return drm_connector_attach_encoder(connector, encoder);698}699 700int gud_get_connectors(struct gud_device *gdrm)701{702 struct gud_connector_descriptor_req *descs;703 unsigned int i, num_connectors;704 int ret;705 706 descs = kmalloc_array(GUD_CONNECTORS_MAX_NUM, sizeof(*descs), GFP_KERNEL);707 if (!descs)708 return -ENOMEM;709 710 ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTORS, 0,711 descs, GUD_CONNECTORS_MAX_NUM * sizeof(*descs));712 if (ret < 0)713 goto free;714 if (!ret || ret % sizeof(*descs)) {715 ret = -EIO;716 goto free;717 }718 719 num_connectors = ret / sizeof(*descs);720 721 for (i = 0; i < num_connectors; i++) {722 ret = gud_connector_create(gdrm, i, &descs[i]);723 if (ret)724 goto free;725 }726free:727 kfree(descs);728 729 return ret;730}731