Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/surface: aggregator_registry: Give devices time to set up when connecting

Sometimes, the "base connected" event that we rely on to (re-)attach the
device connected to the base is sent a bit too early. When this happens,
some devices may not be completely ready yet.

Specifically, the battery has been observed to report zero-values for
things like full charge capacity, which, however, is only loaded once
when the driver for that device probes. This can thus result in battery
readings being unavailable.

As we cannot easily and reliably discern between devices that are not
ready yet and devices that are not connected (i.e. will never be ready),
delay adding these devices. This should give them enough time to set up.

The delay is set to 2.5 seconds, which should give us a good safety
margin based on testing and still be fairly responsive for users.

To achieve that delay switch to updating via a delayed work struct,
which means that we can also get rid of some locking.

Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com
Signed-off-by: Hans de Goede <hdegoede@redhat.com>

authored by

Maximilian Luz and committed by
Hans de Goede
8145476f 1ea602e4

+40 -58
+40 -58
drivers/platform/surface/surface_aggregator_registry.c
··· 13 13 #include <linux/kernel.h> 14 14 #include <linux/limits.h> 15 15 #include <linux/module.h> 16 - #include <linux/mutex.h> 17 16 #include <linux/platform_device.h> 18 17 #include <linux/property.h> 19 18 #include <linux/types.h> 19 + #include <linux/workqueue.h> 20 20 21 21 #include <linux/surface_aggregator/controller.h> 22 22 #include <linux/surface_aggregator/device.h> ··· 287 287 288 288 /* -- SSAM base-hub driver. ------------------------------------------------- */ 289 289 290 + /* 291 + * Some devices (especially battery) may need a bit of time to be fully usable 292 + * after being (re-)connected. This delay has been determined via 293 + * experimentation. 294 + */ 295 + #define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) 296 + 290 297 enum ssam_base_hub_state { 291 298 SSAM_BASE_HUB_UNINITIALIZED, 292 299 SSAM_BASE_HUB_CONNECTED, ··· 303 296 struct ssam_base_hub { 304 297 struct ssam_device *sdev; 305 298 306 - struct mutex lock; /* Guards state update checks and transitions. */ 307 299 enum ssam_base_hub_state state; 300 + struct delayed_work update_work; 308 301 309 302 struct ssam_event_notifier notif; 310 303 }; ··· 342 335 char *buf) 343 336 { 344 337 struct ssam_base_hub *hub = dev_get_drvdata(dev); 345 - bool connected; 346 - 347 - mutex_lock(&hub->lock); 348 - connected = hub->state == SSAM_BASE_HUB_CONNECTED; 349 - mutex_unlock(&hub->lock); 338 + bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; 350 339 351 340 return sysfs_emit(buf, "%d\n", connected); 352 341 } ··· 359 356 .attrs = ssam_base_hub_attrs, 360 357 }; 361 358 362 - static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) 359 + static void ssam_base_hub_update_workfn(struct work_struct *work) 363 360 { 361 + struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); 364 362 struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); 363 + enum ssam_base_hub_state state; 365 364 int status = 0; 366 365 367 - lockdep_assert_held(&hub->lock); 366 + status = ssam_base_hub_query_state(hub, &state); 367 + if (status) 368 + return; 368 369 369 - if (hub->state == new) 370 - return 0; 371 - hub->state = new; 370 + if (hub->state == state) 371 + return; 372 + hub->state = state; 372 373 373 374 if (hub->state == SSAM_BASE_HUB_CONNECTED) 374 375 status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); ··· 381 374 382 375 if (status) 383 376 dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); 384 - 385 - return status; 386 - } 387 - 388 - static int ssam_base_hub_update(struct ssam_base_hub *hub) 389 - { 390 - enum ssam_base_hub_state state; 391 - int status; 392 - 393 - mutex_lock(&hub->lock); 394 - 395 - status = ssam_base_hub_query_state(hub, &state); 396 - if (!status) 397 - status = __ssam_base_hub_update(hub, state); 398 - 399 - mutex_unlock(&hub->lock); 400 - return status; 401 377 } 402 378 403 379 static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 404 380 { 405 - struct ssam_base_hub *hub; 406 - struct ssam_device *sdev; 407 - enum ssam_base_hub_state new; 408 - 409 - hub = container_of(nf, struct ssam_base_hub, notif); 410 - sdev = hub->sdev; 381 + struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); 382 + unsigned long delay; 411 383 412 384 if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) 413 385 return 0; 414 386 415 387 if (event->length < 1) { 416 - dev_err(&sdev->dev, "unexpected payload size: %u\n", 417 - event->length); 388 + dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); 418 389 return 0; 419 390 } 420 391 421 - if (event->data[0]) 422 - new = SSAM_BASE_HUB_CONNECTED; 423 - else 424 - new = SSAM_BASE_HUB_DISCONNECTED; 392 + /* 393 + * Delay update when the base is being connected to give devices/EC 394 + * some time to set up. 395 + */ 396 + delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; 425 397 426 - mutex_lock(&hub->lock); 427 - __ssam_base_hub_update(hub, new); 428 - mutex_unlock(&hub->lock); 398 + schedule_delayed_work(&hub->update_work, delay); 429 399 430 400 /* 431 401 * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and ··· 414 430 415 431 static int __maybe_unused ssam_base_hub_resume(struct device *dev) 416 432 { 417 - return ssam_base_hub_update(dev_get_drvdata(dev)); 433 + struct ssam_base_hub *hub = dev_get_drvdata(dev); 434 + 435 + schedule_delayed_work(&hub->update_work, 0); 436 + return 0; 418 437 } 419 438 static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); 420 439 ··· 430 443 if (!hub) 431 444 return -ENOMEM; 432 445 433 - mutex_init(&hub->lock); 434 - 435 446 hub->sdev = sdev; 436 447 hub->state = SSAM_BASE_HUB_UNINITIALIZED; 437 448 ··· 441 456 hub->notif.event.mask = SSAM_EVENT_MASK_NONE; 442 457 hub->notif.event.flags = SSAM_EVENT_SEQUENCED; 443 458 459 + INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); 460 + 444 461 ssam_device_set_drvdata(sdev, hub); 445 462 446 463 status = ssam_notifier_register(sdev->ctrl, &hub->notif); 447 464 if (status) 448 - goto err_register; 449 - 450 - status = ssam_base_hub_update(hub); 451 - if (status) 452 - goto err_update; 465 + return status; 453 466 454 467 status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); 455 468 if (status) 456 - goto err_update; 469 + goto err; 457 470 471 + schedule_delayed_work(&hub->update_work, 0); 458 472 return 0; 459 473 460 - err_update: 474 + err: 461 475 ssam_notifier_unregister(sdev->ctrl, &hub->notif); 476 + cancel_delayed_work_sync(&hub->update_work); 462 477 ssam_hub_remove_devices(&sdev->dev); 463 - err_register: 464 - mutex_destroy(&hub->lock); 465 478 return status; 466 479 } 467 480 ··· 470 487 sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); 471 488 472 489 ssam_notifier_unregister(sdev->ctrl, &hub->notif); 490 + cancel_delayed_work_sync(&hub->update_work); 473 491 ssam_hub_remove_devices(&sdev->dev); 474 - 475 - mutex_destroy(&hub->lock); 476 492 } 477 493 478 494 static const struct ssam_device_id ssam_base_hub_match[] = {