-
Notifications
You must be signed in to change notification settings - Fork 30
[FIX] Improve D4XX streaming resilience and I2C failure handling #368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -194,6 +194,7 @@ enum ds5_mux_pad { | |||||||||||||
| /* I2C retry configuration */ | ||||||||||||||
| #define DS5_I2C_RETRY_COUNT 5 | ||||||||||||||
| #define DS5_I2C_RETRY_DELAY_US 5000 | ||||||||||||||
| #define DS5_I2C_DEAD_THRESHOLD 50 /* consecutive errors before declaring dead */ | ||||||||||||||
|
|
||||||||||||||
| /* DFU definition section */ | ||||||||||||||
| #define DFU_MAGIC_NUMBER "/0x01/0x02/0x03/0x04" | ||||||||||||||
|
|
@@ -481,6 +482,9 @@ struct ds5 { | |||||||||||||
| struct i2c_client *dser_i2c; | ||||||||||||||
| const struct dser_interface *dser_ops; | ||||||||||||||
| #endif | ||||||||||||||
| /* I2C health tracking */ | ||||||||||||||
| atomic_t i2c_consec_errors; | ||||||||||||||
| bool camera_dead; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| struct ds5_counters { | ||||||||||||||
|
|
@@ -545,6 +549,9 @@ static int ds5_write(struct ds5 *state, u16 reg, u16 val) | |||||||||||||
| int retry; | ||||||||||||||
| u8 value[2]; | ||||||||||||||
|
|
||||||||||||||
| if (state->camera_dead) | ||||||||||||||
| return -ENODEV; | ||||||||||||||
|
|
||||||||||||||
| value[1] = val >> 8; | ||||||||||||||
| value[0] = val & 0x00FF; | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -557,20 +564,31 @@ static int ds5_write(struct ds5 *state, u16 reg, u16 val) | |||||||||||||
| if (ret == 0) | ||||||||||||||
| break; | ||||||||||||||
| if (retry < DS5_I2C_RETRY_COUNT - 1) { | ||||||||||||||
| dev_warn(&state->client->dev, | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c write retry %d, 0x%04x = 0x%x, err %d\n", | ||||||||||||||
| __func__, retry + 1, reg, val, ret); | ||||||||||||||
| usleep_range(DS5_I2C_RETRY_DELAY_US, | ||||||||||||||
| DS5_I2C_RETRY_DELAY_US + 500); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (ret < 0) | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| if (ret < 0) { | ||||||||||||||
| dev_warn_ratelimited(&state->client->dev, | ||||||||||||||
| "%s(): i2c write failed after %d retries, 0x%04x = 0x%x, err %d\n", | ||||||||||||||
| __func__, DS5_I2C_RETRY_COUNT, reg, val, ret); | ||||||||||||||
| else if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) | ||||||||||||||
| dev_dbg(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", | ||||||||||||||
| __func__, reg, val); | ||||||||||||||
| if (atomic_inc_return(&state->i2c_consec_errors) >= | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD) { | ||||||||||||||
| state->camera_dead = true; | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| "camera unreachable after %d consecutive I2C errors\n", | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD); | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+578
to
+584
|
||||||||||||||
| } else { | ||||||||||||||
| atomic_set(&state->i2c_consec_errors, 0); | ||||||||||||||
| if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c write 0x%04x: 0x%x\n", | ||||||||||||||
| __func__, reg, val); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return ret; | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -581,26 +599,39 @@ static int ds5_raw_write(struct ds5 *state, u16 reg, | |||||||||||||
| int ret; | ||||||||||||||
| int retry; | ||||||||||||||
|
|
||||||||||||||
| if (state->camera_dead) | ||||||||||||||
| return -ENODEV; | ||||||||||||||
|
|
||||||||||||||
| for (retry = 0; retry < DS5_I2C_RETRY_COUNT; retry++) { | ||||||||||||||
| ret = regmap_raw_write(state->regmap, reg, val, val_len); | ||||||||||||||
| if (ret == 0) | ||||||||||||||
| break; | ||||||||||||||
| if (retry < DS5_I2C_RETRY_COUNT - 1) { | ||||||||||||||
| dev_warn(&state->client->dev, | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c raw write retry %d, 0x%04x size(%d), err %d\n", | ||||||||||||||
| __func__, retry + 1, reg, (int)val_len, ret); | ||||||||||||||
| usleep_range(DS5_I2C_RETRY_DELAY_US, | ||||||||||||||
| DS5_I2C_RETRY_DELAY_US + 500); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (ret < 0) | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| if (ret < 0) { | ||||||||||||||
| dev_warn_ratelimited(&state->client->dev, | ||||||||||||||
| "%s(): i2c raw write failed after %d retries, 0x%04x size(%d), err %d\n", | ||||||||||||||
| __func__, DS5_I2C_RETRY_COUNT, reg, (int)val_len, ret); | ||||||||||||||
| else if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c raw write 0x%04x: %d bytes\n", | ||||||||||||||
| __func__, reg, (int)val_len); | ||||||||||||||
| if (atomic_inc_return(&state->i2c_consec_errors) >= | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD) { | ||||||||||||||
| state->camera_dead = true; | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| "camera unreachable after %d consecutive I2C errors\n", | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD); | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| atomic_set(&state->i2c_consec_errors, 0); | ||||||||||||||
| if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c raw write 0x%04x: %d bytes\n", | ||||||||||||||
| __func__, reg, (int)val_len); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return ret; | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -610,25 +641,39 @@ static int ds5_read(struct ds5 *state, u16 reg, u16 *val) | |||||||||||||
| int ret; | ||||||||||||||
| int retry; | ||||||||||||||
|
|
||||||||||||||
| if (state->camera_dead) | ||||||||||||||
| return -ENODEV; | ||||||||||||||
|
|
||||||||||||||
| for (retry = 0; retry < DS5_I2C_RETRY_COUNT; retry++) { | ||||||||||||||
| ret = regmap_raw_read(state->regmap, reg, val, 2); | ||||||||||||||
| if (ret == 0) | ||||||||||||||
| break; | ||||||||||||||
| if (retry < DS5_I2C_RETRY_COUNT - 1) { | ||||||||||||||
| dev_warn(&state->client->dev, | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c read retry %d, 0x%04x, err %d\n", | ||||||||||||||
| __func__, retry + 1, reg, ret); | ||||||||||||||
| usleep_range(DS5_I2C_RETRY_DELAY_US, | ||||||||||||||
| DS5_I2C_RETRY_DELAY_US + 500); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (ret < 0) | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| if (ret < 0) { | ||||||||||||||
| dev_warn_ratelimited(&state->client->dev, | ||||||||||||||
| "%s(): i2c read failed after %d retries, 0x%04x, err %d\n", | ||||||||||||||
| __func__, DS5_I2C_RETRY_COUNT, reg, ret); | ||||||||||||||
| else if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) | ||||||||||||||
| dev_dbg(&state->client->dev, "%s(): i2c read 0x%04x: 0x%x\n", | ||||||||||||||
| __func__, reg, *val); | ||||||||||||||
| if (atomic_inc_return(&state->i2c_consec_errors) >= | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD) { | ||||||||||||||
| state->camera_dead = true; | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| "camera unreachable after %d consecutive I2C errors\n", | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD); | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| atomic_set(&state->i2c_consec_errors, 0); | ||||||||||||||
| if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c read 0x%04x: 0x%x\n", | ||||||||||||||
| __func__, reg, *val); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return ret; | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -638,22 +683,35 @@ static int ds5_raw_read(struct ds5 *state, u16 reg, void *val, size_t val_len) | |||||||||||||
| int ret; | ||||||||||||||
| int retry; | ||||||||||||||
|
|
||||||||||||||
| if (state->camera_dead) | ||||||||||||||
| return -ENODEV; | ||||||||||||||
|
|
||||||||||||||
| for (retry = 0; retry < DS5_I2C_RETRY_COUNT; retry++) { | ||||||||||||||
| ret = regmap_raw_read(state->regmap, reg, val, val_len); | ||||||||||||||
| if (ret == 0) | ||||||||||||||
| break; | ||||||||||||||
| if (retry < DS5_I2C_RETRY_COUNT - 1) { | ||||||||||||||
| dev_warn(&state->client->dev, | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): i2c raw read retry %d, 0x%04x size(%d), err %d\n", | ||||||||||||||
| __func__, retry + 1, reg, (int)val_len, ret); | ||||||||||||||
| usleep_range(DS5_I2C_RETRY_DELAY_US, | ||||||||||||||
| DS5_I2C_RETRY_DELAY_US + 500); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (ret < 0) | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| if (ret < 0) { | ||||||||||||||
| dev_warn_ratelimited(&state->client->dev, | ||||||||||||||
| "%s(): i2c raw read failed after %d retries, 0x%04x size(%d), err %d\n", | ||||||||||||||
| __func__, DS5_I2C_RETRY_COUNT, reg, (int)val_len, ret); | ||||||||||||||
| if (atomic_inc_return(&state->i2c_consec_errors) >= | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD) { | ||||||||||||||
| state->camera_dead = true; | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| "camera unreachable after %d consecutive I2C errors\n", | ||||||||||||||
| DS5_I2C_DEAD_THRESHOLD); | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| atomic_set(&state->i2c_consec_errors, 0); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return ret; | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2384,12 +2442,43 @@ static int ds5_hw_reset_with_recovery(struct ds5 *state) | |||||||||||||
| sensor->pipe_configured = false; | ||||||||||||||
| sensor->pipe_id = -1; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /* Ensure all sensor streaming state is fully reset so | ||||||||||||||
| * ds5_configure() does a fresh setup on next stream-on */ | ||||||||||||||
| for (i = 0; i < ARRAY_SIZE(sensors); i++) | ||||||||||||||
| sensors[i]->streaming = false; | ||||||||||||||
|
|
||||||||||||||
| mutex_unlock(&serdes_lock__); | ||||||||||||||
|
|
||||||||||||||
| dev_info(&state->client->dev, | ||||||||||||||
| "%s(): Re-initializing SERDES link\n", __func__); | ||||||||||||||
| state->dser_ops->reset_oneshot(state->dser_dev); | ||||||||||||||
| msleep(300); | ||||||||||||||
|
|
||||||||||||||
| /* | ||||||||||||||
| * Verify GMSL link is re-established by polling I2C. | ||||||||||||||
| * reset_oneshot itself sleeps 100ms internally; we add | ||||||||||||||
| * settling time and verify the link is actually alive | ||||||||||||||
| * rather than blindly sleeping a fixed 300ms. | ||||||||||||||
| */ | ||||||||||||||
| msleep(200); | ||||||||||||||
| for (i = 0; i < 10; i++) { | ||||||||||||||
| ret = ds5_read(state, DS5_FW_VERSION, &status); | ||||||||||||||
|
||||||||||||||
| if (ret == 0) | ||||||||||||||
| break; | ||||||||||||||
| dev_dbg(&state->client->dev, | ||||||||||||||
| "%s(): GMSL link not ready, retry %d\n", | ||||||||||||||
| __func__, i); | ||||||||||||||
| msleep(100); | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+2464
to
+2472
|
||||||||||||||
| if (ret < 0) { | ||||||||||||||
| dev_err(&state->client->dev, | ||||||||||||||
| "%s(): GMSL link failed to recover after reset_oneshot (%d)\n", | ||||||||||||||
| __func__, ret); | ||||||||||||||
| return -EIO; | ||||||||||||||
| } | ||||||||||||||
| dev_info(&state->client->dev, | ||||||||||||||
| "%s(): GMSL link verified after %d ms\n", | ||||||||||||||
|
||||||||||||||
| "%s(): GMSL link verified after %d ms\n", | |
| "%s(): GMSL link verified after %d ms (including initial 200 ms settling delay)\n", |
Copilot
AI
Feb 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The circuit breaker has no recovery mechanism. Once camera_dead is set to true, it remains permanently true for the lifetime of the driver instance. Even if hardware reset recovery (ds5_hw_reset_with_recovery) succeeds and verifies the GMSL link is working (lines 2464-2481), the camera_dead flag is never cleared. This means the camera will remain unusable until the driver is unloaded and reloaded. Consider resetting both camera_dead and i2c_consec_errors after successful hardware reset recovery to allow the driver to recover from transient failures.
Copilot
AI
Feb 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retry loop at lines 5683-5687 will fail prematurely if the circuit breaker trips during retries. If the first 2 reads fail (decrementing retry to 1), and those failures cause i2c_consec_errors to hit the threshold, camera_dead will be set to true. The third retry will then return -ENODEV immediately at line 644 due to the camera_dead check, without actually attempting I2C communication. This means the actual retry count becomes unpredictable once the threshold is approached. Consider resetting or pausing the circuit breaker during DFU release retries, or document that the circuit breaker can cause early termination of retries.
Copilot
AI
Feb 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function now unconditionally returns 0 even after the final ds5_read for FW_BUILD (line 5694) regardless of whether it succeeds or fails. While this prevents FD leaks (which is good), silently ignoring errors from reading FW_BUILD means the firmware version information could be incomplete or stale. If consistency is important, either document why FW_BUILD read failures are acceptable, or at least log a debug message when line 5694 fails so there's a record in the logs.
| ret = ds5_read(state, DS5_FW_BUILD, &state->fw_build); | |
| ret = ds5_read(state, DS5_FW_BUILD, &state->fw_build); | |
| if (ret) | |
| dev_dbg(&state->client->dev, | |
| "%s(): failed to read FW_BUILD, firmware build info may be stale (err=%d)\n", | |
| __func__, ret); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The circuit breaker threshold message is duplicated across all four I2C functions (ds5_write, ds5_raw_write, ds5_read, ds5_raw_read) with identical wording. If multiple I2C functions hit the threshold simultaneously or sequentially, the same "camera unreachable after 50 consecutive I2C errors" message will be logged multiple times. Since the goal is to reduce log noise (as mentioned in the PR description about 3,430+ identical messages), consider using a flag to ensure this critical message is only logged once, or at least using
dev_err_ratelimitedinstead ofdev_err.