diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index aa630b95dafe9..3ceb77e3578ac 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -519,6 +519,10 @@ static bool tps6598x_read_data_status(struct tps6598x *tps) return true; } +bool tipd_event_trace; +module_param_named(dptx_event_trace, tipd_event_trace, bool, 0400); +MODULE_PARM_DESC(dptx_event_trace, "Record existing PD/display events in bounded RAM; default off"); + static bool cd321x_read_data_status(struct tps6598x *tps) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); @@ -526,7 +530,7 @@ static bool cd321x_read_data_status(struct tps6598x *tps) ret = tps6598x_read_data_status(tps); if (!ret) - return false; + { TIPD_EVENT(tps, TIPD_EVENT_DATA, ret, 0, tps->status, tps->pwr_status, tps->data_status, le32_to_cpu(cd321x->dp_sid_status.status_rx), le32_to_cpu(cd321x->dp_sid_status.status_tx), le32_to_cpu(cd321x->dp_sid_status.configure), le32_to_cpu(cd321x->dp_sid_status.mode_data), 0, 0); return false; } if (tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) { ret = tps6598x_block_read(tps, TPS_REG_DP_SID_STATUS, @@ -534,7 +538,7 @@ static bool cd321x_read_data_status(struct tps6598x *tps) if (ret) { dev_err(tps->dev, "Failed to read DP SID Status: %d\n", ret); - return false; + { TIPD_EVENT(tps, TIPD_EVENT_DATA, ret, 0, tps->status, tps->pwr_status, tps->data_status, le32_to_cpu(cd321x->dp_sid_status.status_rx), le32_to_cpu(cd321x->dp_sid_status.status_tx), le32_to_cpu(cd321x->dp_sid_status.configure), le32_to_cpu(cd321x->dp_sid_status.mode_data), 0, 0); return false; } } } @@ -543,7 +547,7 @@ static bool cd321x_read_data_status(struct tps6598x *tps) &cd321x->intel_vid_status, sizeof(cd321x->intel_vid_status)); if (ret) { dev_err(tps->dev, "Failed to read Intel VID Status: %d\n", ret); - return false; + { TIPD_EVENT(tps, TIPD_EVENT_DATA, ret, 0, tps->status, tps->pwr_status, tps->data_status, le32_to_cpu(cd321x->dp_sid_status.status_rx), le32_to_cpu(cd321x->dp_sid_status.status_tx), le32_to_cpu(cd321x->dp_sid_status.configure), le32_to_cpu(cd321x->dp_sid_status.mode_data), 0, 0); return false; } } } @@ -553,10 +557,11 @@ static bool cd321x_read_data_status(struct tps6598x *tps) if (ret) { dev_err(tps->dev, "Failed to read USB4 Status: %d\n", ret); - return false; + { TIPD_EVENT(tps, TIPD_EVENT_DATA, ret, 0, tps->status, tps->pwr_status, tps->data_status, le32_to_cpu(cd321x->dp_sid_status.status_rx), le32_to_cpu(cd321x->dp_sid_status.status_tx), le32_to_cpu(cd321x->dp_sid_status.configure), le32_to_cpu(cd321x->dp_sid_status.mode_data), 0, 0); return false; } } } + TIPD_EVENT(tps, TIPD_EVENT_DATA, 0, 1 | ((tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) ? 2 : 0), tps->status, tps->pwr_status, tps->data_status, le32_to_cpu(cd321x->dp_sid_status.status_rx), le32_to_cpu(cd321x->dp_sid_status.status_tx), le32_to_cpu(cd321x->dp_sid_status.configure), le32_to_cpu(cd321x->dp_sid_status.mode_data), 0, 0); return true; } @@ -710,20 +715,21 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, *held = false; if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { if (cd321x->state_valid && cd321x->state.mode == TYPEC_STATE_SAFE) - return 0; + { TIPD_EVENT(tps, TIPD_EVENT_MUX_DUPLICATE, 0, 0, st->status, st->data_status, cd321x->state.mode, cd321x->dp_status, cd321x->dp_conf, cd321x->state_valid, 0, 0, 0); return 0; } cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; ret = typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); if (ret) goto out; + TIPD_EVENT(tps, TIPD_EVENT_MUX, 0, 0, st->status, st->data_status, cd321x->state.mode, le32_to_cpu(st->dp_sid_status.status_rx), le32_to_cpu(st->dp_sid_status.configure), cd321x->state_valid, 0, 0, 0); ret = cd321x_mux_set(cd321x, held); } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { struct enter_usb_data eusb_data; if (cd321x->state_valid && !cd321x->state.alt && cd321x->state.mode == TYPEC_MODE_USB4) - return 0; + { TIPD_EVENT(tps, TIPD_EVENT_MUX_DUPLICATE, 0, 0, st->status, st->data_status, cd321x->state.mode, cd321x->dp_status, cd321x->dp_conf, cd321x->state_valid, 0, 0, 0); return 0; } eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); eusb_data.active_link_training = @@ -732,6 +738,7 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, cd321x->state.alt = NULL; cd321x->state.data = &eusb_data; cd321x->state.mode = TYPEC_MODE_USB4; + TIPD_EVENT(tps, TIPD_EVENT_MUX, 0, 0, st->status, st->data_status, cd321x->state.mode, le32_to_cpu(st->dp_sid_status.status_rx), le32_to_cpu(st->dp_sid_status.configure), cd321x->state_valid, 0, 0, 0); ret = cd321x_mux_set(cd321x, held); if (ret) goto out; @@ -747,7 +754,7 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, if (cd321x->state_valid && cd321x->state.alt == cd321x->port_altmode_tbt && cd321x->state.mode == TYPEC_TBT_MODE) - return 0; + { TIPD_EVENT(tps, TIPD_EVENT_MUX_DUPLICATE, 0, 0, st->status, st->data_status, cd321x->state.mode, cd321x->dp_status, cd321x->dp_conf, cd321x->state_valid, 0, 0, 0); return 0; } tbt_data.cable_mode = TBT_MODE | TBT_SET_CABLE_SPEED(TPS_DATA_STATUS_TBT_CABLE_SPEED(st->data_status)) | @@ -765,6 +772,7 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, cd321x->state.alt = cd321x->port_altmode_tbt; cd321x->state.mode = TYPEC_TBT_MODE; cd321x->state.data = &tbt_data; + TIPD_EVENT(tps, TIPD_EVENT_MUX, 0, 0, st->status, st->data_status, cd321x->state.mode, le32_to_cpu(st->dp_sid_status.status_rx), le32_to_cpu(st->dp_sid_status.configure), cd321x->state_valid, 0, 0, 0); ret = cd321x_mux_set(cd321x, held); if (ret) goto out; @@ -802,7 +810,7 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, break; default: dev_err(tps->dev, "Invalid DP pin assignment\n"); - return -EINVAL; + { TIPD_EVENT(tps, TIPD_EVENT_MUX, -EINVAL, 0, st->data_status, 0, 0, 0, 0, 0, 0, 0, 0); return -EINVAL; } } dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); @@ -842,6 +850,7 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, ret = typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); if (ret) goto out; + TIPD_EVENT(tps, TIPD_EVENT_MUX, 0, 0, st->status, st->data_status, cd321x->state.mode, le32_to_cpu(st->dp_sid_status.status_rx), le32_to_cpu(st->dp_sid_status.configure), cd321x->state_valid, 0, 0, 0); if (send == CD321X_DP_SEND_EDGE) { struct typec_displayport_data edge = { .status = dp_data.status & ~DP_STATUS_IRQ_HPD, @@ -866,13 +875,14 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, } else { if (cd321x->state_valid && !cd321x->state.alt && cd321x->state.mode == TYPEC_STATE_USB) - return 0; + { TIPD_EVENT(tps, TIPD_EVENT_MUX_DUPLICATE, 0, 0, st->status, st->data_status, cd321x->state.mode, cd321x->dp_status, cd321x->dp_conf, cd321x->state_valid, 0, 0, 0); return 0; } cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_USB; cd321x->state.data = NULL; ret = typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); if (ret) goto out; + TIPD_EVENT(tps, TIPD_EVENT_MUX, 0, 0, st->status, st->data_status, cd321x->state.mode, le32_to_cpu(st->dp_sid_status.status_rx), le32_to_cpu(st->dp_sid_status.configure), cd321x->state_valid, 0, 0, 0); ret = cd321x_mux_set(cd321x, held); } @@ -895,6 +905,7 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, cd321x->dp_conf = 0; } + TIPD_EVENT(tps, TIPD_EVENT_APPLY, ret, 0, cd321x->state.mode, cd321x->state_valid, cd321x->dp_status, cd321x->dp_conf, *display_busy, 0, 0, 0, 0); return ret; } @@ -1034,10 +1045,13 @@ static void cd321x_update_work(struct work_struct *work) int ret; guard(mutex)(&tps->lock); - if (!cd321x_pm_can_update(&cd321x->pm)) + if (!cd321x_pm_can_update(&cd321x->pm)) { + TIPD_EVENT(tps, TIPD_EVENT_CONSUME, 0, 0, cd321x->pm.phase, 0, 0, 0, 0, 0, 0, 0, 0); return; + } st = cd321x->update_status; + TIPD_EVENT(tps, TIPD_EVENT_CONSUME, 0, 1, st.status, st.data_status, st.status_changed, st.data_status_changed, st.pwr_status, le32_to_cpu(st.dp_sid_status.status_rx), le32_to_cpu(st.dp_sid_status.configure), cd321x->pm.phase, 0); cd321x->update_status.status_changed = 0; cd321x->update_status.data_status_changed = 0; cd321x->update_status.dp_sid_event = false; @@ -1253,6 +1267,7 @@ static void cd321x_update_work(struct work_struct *work) goto retry; } ret = cd321x_typec_update_mode(tps, &st, &display_busy, &held); + TIPD_EVENT(tps, TIPD_EVENT_APPLY, ret, 2, st.status, st.data_status, display_busy, cd321x->display_route_active, 0, 0, 0, 0, 0); if (ret && !display_busy) goto retry; /* A shared display route can be busy while the dock's USB path is usable. */ @@ -1296,6 +1311,7 @@ static void cd321x_update_work(struct work_struct *work) return; retry: + TIPD_EVENT(tps, TIPD_EVENT_RETRY, 0, 0, cd321x->pm.phase, cd321x->state_valid, cd321x->display_route_active, 0, 0, 0, 0, 0, 0); /* * The Thunderbolt provider disabled the port for good after a failed * resume recovery (see apple_cio_tbt_switch_set()). Other errors, @@ -1365,6 +1381,7 @@ static void cd321x_queue_status(struct cd321x *cd321x) cd321x->update_status.dp_sid_status = cd321x->dp_sid_status; cd321x->update_status.intel_vid_status = cd321x->intel_vid_status; cd321x->update_status.usb4_status = cd321x->usb4_status; + TIPD_EVENT(&cd321x->tps, TIPD_EVENT_QUEUE, 0, 0, cd321x->update_status.status, cd321x->update_status.data_status, cd321x->update_status.status_changed, cd321x->update_status.data_status_changed, cd321x->update_status.pwr_status, le32_to_cpu(cd321x->update_status.dp_sid_status.status_rx), le32_to_cpu(cd321x->update_status.dp_sid_status.configure), cd321x->pm.phase, cd321x->display_route_active); } static int cd321x_connect(struct tps6598x *tps, u32 status) @@ -1599,15 +1616,18 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) struct cd321x *cd321x = container_of(tps, struct cd321x, tps); u64 event = 0; u32 status; + u32 diag_valid = 0; int ret; mutex_lock(&tps->lock); ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event); + TIPD_EVENT(tps, TIPD_EVENT_IRQ, ret, !ret, event, 0, 0, 0, 0, 0, 0, 0, 0); if (ret) { dev_err(tps->dev, "%s: failed to read events\n", __func__); goto err_lost; } + diag_valid |= BIT(0); trace_cd321x_irq(event); if (!event) @@ -1624,9 +1644,11 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) if (!tps6598x_read_status(tps, &status)) goto err_lost; + diag_valid |= BIT(1); if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { if (!tps->data->read_power_status(tps)) goto err_lost; + diag_valid |= BIT(2); if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { if (tps6598x_read_partner_identity(tps)) { dev_err(tps->dev, "failed to read partner identity\n"); @@ -1641,6 +1663,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) goto err_lost; if (event & cd321x_irq_mask(tps) & SN201202X_INT_DP_SID_UPDATE) cd321x_dp_sid_event(container_of(tps, struct cd321x, tps)); + diag_valid |= BIT(3); /* Can be called uncondtionally since it will check for any changes itself */ cd321x_connect(tps, status); @@ -1649,6 +1672,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) err_lost: cd321x_event_lost(cd321x); err_unlock: + TIPD_EVENT(tps, TIPD_EVENT_IRQ_EXIT, ret, diag_valid, event, diag_valid & BIT(1) ? status : 0, tps->status, tps->pwr_status, tps->data_status, 0, 0, 0, 0); mutex_unlock(&tps->lock); if (event) @@ -2111,10 +2135,19 @@ EXPORT_SYMBOL_IF_KUNIT(cd321x_init_work); static int cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { + struct fwnode_handle *connector_fwnode; struct cd321x *cd321x = container_of(tps, struct cd321x, tps); int ret; cd321x_init_work(cd321x); + if (tipd_event_trace) { + tps->event_log = devm_kzalloc(tps->dev, sizeof(*tps->event_log), GFP_KERNEL); + if (tps->event_log) + apple_display_event_log_init(tps->event_log, dev_name(tps->dev)); + else + dev_warn(tps->dev, "PD event recorder allocation failed\n"); + } + ret = tps6598x_register_port(tps, fwnode); if (ret) @@ -2130,9 +2163,13 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) goto err_unregister_altmodes; } - cd321x->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); - if (IS_ERR(cd321x->connector_fwnode)) - cd321x->connector_fwnode = NULL; + if (fwnode_property_present(fwnode, "displayport")) + connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); + else + /* Graph-described DRM ports use the physical connector identity. */ + connector_fwnode = fwnode_handle_get(fwnode); + if (!IS_ERR_OR_NULL(connector_fwnode)) + cd321x->connector_fwnode = connector_fwnode; cd321x->tbt_switch = cd321x_tbt_switch_get(tps->dev, fwnode); if (IS_ERR(cd321x->tbt_switch)) { diff --git a/drivers/usb/typec/tipd/debugfs.c b/drivers/usb/typec/tipd/debugfs.c index 77252b3ed3c90..f3c1d5e5a1ee0 100644 --- a/drivers/usb/typec/tipd/debugfs.c +++ b/drivers/usb/typec/tipd/debugfs.c @@ -21,6 +21,7 @@ * whoever set it up before Linux (e.g. a boot loader arming Debug USB). */ +#include #include #include #include @@ -270,6 +271,10 @@ void tipd_debugfs_register(struct tps6598x *tps) dbg->dentry = debugfs_create_dir(dev_name(tps->dev), tipd_debugfs_root); tps->debugfs = dbg; + if (tps->event_log && IS_ERR_OR_NULL(debugfs_create_file("dptx_events", 0400, + dbg->dentry, tps->event_log, &apple_display_events_fops))) + dev_warn(tps->dev, "PD event recorder file unavailable\n"); + if (IS_ENABLED(CONFIG_TYPEC_TPS6598X_DEBUG_COMMANDS)) debugfs_create_file("command", 0600, dbg->dentry, tps, &tipd_debugfs_command_fops); diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index ba89b4b57a255..d4d5f49608485 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -318,9 +318,16 @@ static int sn201202x_probe(struct spmi_device *device) return ret; ret = tipd_init(tps); - if (ret) + if (ret) { spmi_command_sleep(device); - return ret; + return ret; + } + + /* SPMI event messages can precede registration of the event IRQ. + * Reconcile the latched state after the port and its work are ready. + */ + tps->data->irq_handler(tps->irq, tps); + return 0; } static void sn201202x_remove(struct spmi_device *device) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index caac3cb771bd4..e4737ca07aef6 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -6,6 +6,7 @@ * Author: Heikki Krogerus */ +#include #include #include #include @@ -371,6 +372,8 @@ struct tps6598x { const struct tipd_data *data; struct tipd_debugfs *debugfs; + /* Diagnostic tail: preserve all pre-existing transport field offsets. */ + struct apple_display_event_log *event_log; }; /* TPS_REG_USB4_STATUS */ @@ -495,5 +498,16 @@ cd321x_tbt_switch_get(struct device *dev, struct fwnode_handle *fwnode); int sn201202x_suspend(struct device *dev); int sn201202x_resume(struct device *dev); #endif +/* Guard flag before evaluating payload expressions or accessing owner fields. */ +extern bool tipd_event_trace; +#define TIPD_EVENT(tps, type, result, flags, ...) do { \ + if (unlikely(READ_ONCE(tipd_event_trace))) \ + APPLE_DISPLAY_EVENT_RECORD((tps)->event_log, type, result, flags, __VA_ARGS__); \ +} while (0) +enum tipd_event_type { + TIPD_EVENT_IRQ = 1, TIPD_EVENT_IRQ_EXIT, TIPD_EVENT_DATA, + TIPD_EVENT_QUEUE, TIPD_EVENT_CONSUME, TIPD_EVENT_APPLY, + TIPD_EVENT_MUX, TIPD_EVENT_MUX_DUPLICATE, TIPD_EVENT_OOB, TIPD_EVENT_RETRY, +}; #endif /* __TPS6598X_H__ */ diff --git a/tools/testing/selftests/aurora-apif/typec.py b/tools/testing/selftests/aurora-apif/typec.py new file mode 100755 index 0000000000000..8ee4987a0ea3c --- /dev/null +++ b/tools/testing/selftests/aurora-apif/typec.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Exercise actual T6050 combo coordination with hardware dependency doubles.""" +from pathlib import Path +import subprocess,re,tempfile,shutil +ROOT=Path(__file__).resolve().parents[4] +s=(ROOT/'drivers/phy/apple/atc-t6050-combo.h').read_text() +def fn(name): + m=re.search(r'(?m)^static (?:int|void) '+name+r'\(',s);assert m,name;i=s.index('{',m.start());depth=1;j=i+1 + while depth:depth+=(s[j]=='{')-(s[j]=='}');j+=1 + return s[m.start():j] +names=['combo_quiesce','combo_stop','combo_start','combo_orientation','combo_mux','combo_usb2_mode','combo_usb2_reset','combo_usb3_mode','combo_usb3_poweroff','combo_reset_assert','combo_reset_deassert'] +preamble=r''' +#include +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +#define guard(x) fake_guard +#define lockdep_assert_held(x) ((void)0) +#define container_of(ptr,type,member) ((type*)((char*)(ptr)-offsetof(type,member))) +#define USB_TYPEC_DP_SID 0xff01 +#define TYPEC_STATE_SAFE 0 +#define TYPEC_STATE_USB 1 +#define TYPEC_DP_STATE_C 2 +#define TYPEC_DP_STATE_E 3 +#define TYPEC_DP_STATE_D 4 +static void fake_guard(void*p){} +enum typec_orientation{TYPEC_ORIENTATION_NONE,TYPEC_ORIENTATION_NORMAL,TYPEC_ORIENTATION_REVERSE}; +enum phy_mode{PHY_MODE_USB_HOST,PHY_MODE_USB_DEVICE}; +enum combo_mode{COMBO_SAFE,COMBO_USB,COMBO_DP4,COMBO_DP2}; +struct typec_altmode{unsigned svid;};struct typec_mux_state{unsigned mode;struct typec_altmode*alt;}; +struct phy{void *data;};static void*phy_get_drvdata(struct phy*p){return p->data;} +struct reset_controller_dev{int dummy;};struct atc_domain{bool active;}; +struct apple_t6050_usb_io{void *context;};struct apple_t6050_usb_platform{void*context;}; +struct t6050_dp{bool faulted,combo,controller_active,pipe_active,usb_prepared,usb2_active,enabled,reverse,multifunction;enum combo_mode combo_mode;enum typec_orientation orientation;struct atc_domain domain[3];int lock;struct apple_t6050_usb_io usb_io;struct apple_t6050_usb_platform usb_platform;struct reset_controller_dev reset;void *banks[45];void*pipe_default;}; +static char logbuf[2048];static int offset,fail_code;static const char*fail_at; +static int logstep(const char*s){offset+=snprintf(logbuf+offset,sizeof(logbuf)-offset,"%s ",s);return fail_at && !strcmp(fail_at,s)?fail_code:0;} +static int atc_domain_set(struct atc_domain*d,bool active){if(d->active==active)return 0;int r=logstep(active?"domain-on":"domain-off");if(!r)d->active=active;return r;} +static int atc_start(struct t6050_dp*d){int r=logstep(d->multifunction?"dp2-start":"dp4-start");if(!r)d->enabled=true;return r;} +static int atc_stop(struct t6050_dp*d){if(!d->enabled)return 0;int r=logstep(d->multifunction?"dp2-stop":"dp4-stop");if(!r)d->enabled=false;return r;} +static const char apple_t6050_usb3_shutdown[]="usb3-stop",apple_t6050_usb2_host[]="usb2-start",apple_t6050_usb3_init_reverse[]="usb3-reverse",apple_t6050_usb3_init_normal[]="usb3-normal"; +#define APPLE_T6050_USB_RUN(p,seq) logstep(seq) +static int apple_t6050_usb2_stop(void*p){return logstep("usb2-stop");} +static int apple_t6050_usb3_pipe_stop(void*p,int(*cb)(void*),void*ctx){int r=logstep("pipe-lock");if(!r)r=cb(ctx);if(!r)r=logstep("pipe-dummy");return r;} +static int apple_t6050_usb3_pipe_switch(void*p){return logstep("pipe-usb3");} +static int apple_t6050_usb3_pipe_cold_dummy(void*p){return logstep("reset-dummy");} +static void apple_t6050_usb_mask(void*p,int b,int o,int m,int v){logstep(v?"reset-bit-on":"reset-bit-off");} +static int apple_t6050_usb2_port_reset(void*io,void*ctx,void(*sleep)(void*,unsigned)){return logstep("usb2-reset");} +static void combo_sleep(void*p,unsigned n){} +static void apple_tunable_apply(void*b,void*t){logstep("pipe-tunable");} +static unsigned readl(void*p){return 0x10;} +''' +tests=r''' +static struct t6050_dp setup(void){struct t6050_dp d={.combo=true};return d;} +static void clearlog(void){offset=0;logbuf[0]=0;fail_at=NULL;fail_code=-EIO;} +static struct typec_altmode alt={USB_TYPEC_DP_SID}; +static int mode(struct t6050_dp*d,unsigned mode){struct typec_mux_state s={mode,mode>=2?&alt:NULL};return combo_mux(d,&s);} +static void attach_usb(struct t6050_dp*d){assert(combo_orientation(d,TYPEC_ORIENTATION_NORMAL)==0);assert(mode(d,TYPEC_STATE_USB)==0);struct phy p={d};assert(combo_usb2_mode(&p,PHY_MODE_USB_HOST,0)==0);assert(combo_reset_deassert(&d->reset,0)==0);assert(combo_usb3_mode(&p,PHY_MODE_USB_HOST,0)==0);assert(d->pipe_active);} +static void stop_usb(struct t6050_dp*d){struct phy p={d};assert(combo_usb3_poweroff(&p)==0);assert(!d->pipe_active);assert(combo_reset_assert(&d->reset,0)==0);assert(!d->controller_active);} +static void test_usb_dp_usb(void){struct t6050_dp d=setup();clearlog();attach_usb(&d);clearlog();assert(mode(&d,TYPEC_DP_STATE_C)==-EBUSY);assert(d.pipe_active&&!d.enabled);assert(logbuf[0]==0);stop_usb(&d);assert(mode(&d,TYPEC_DP_STATE_C)==0);assert(strstr(logbuf,"pipe-lock usb3-stop pipe-dummy")&&strstr(logbuf,"dp4-start"));assert(d.enabled&&!d.usb_prepared);clearlog();assert(mode(&d,TYPEC_STATE_USB)==0);assert(strstr(logbuf,"dp4-stop") DP -> USB full lifecycle PASS");} +static void test_safe_disconnect(void){struct t6050_dp d=setup();clearlog();attach_usb(&d);assert(mode(&d,TYPEC_STATE_SAFE)==-EBUSY);assert(combo_orientation(&d,TYPEC_ORIENTATION_NONE)==-EBUSY);stop_usb(&d);assert(mode(&d,TYPEC_STATE_SAFE)==0);assert(combo_orientation(&d,TYPEC_ORIENTATION_NONE)==0);assert(!d.enabled&&!d.usb_prepared&&!d.usb2_active&&!d.domain[0].active&&!d.domain[1].active);puts("# active-controller SAFE/orientation exclusion PASS");} +static void test_reverse_and_redundant(void){struct t6050_dp d=setup();clearlog();assert(combo_orientation(&d,TYPEC_ORIENTATION_REVERSE)==0);assert(mode(&d,TYPEC_STATE_USB)==0);assert(strstr(logbuf,"usb3-reverse"));clearlog();assert(mode(&d,TYPEC_STATE_USB)==0);assert(combo_orientation(&d,TYPEC_ORIENTATION_REVERSE)==0);assert(logbuf[0]==0);puts("# reverse orientation and redundant callback PASS");} +static void test_dp2_shared(void){struct t6050_dp d=setup();clearlog();assert(combo_orientation(&d,TYPEC_ORIENTATION_NORMAL)==0);assert(mode(&d,TYPEC_DP_STATE_D)==0);assert(strstr(logbuf,"dp2-start")&&!strstr(logbuf,"usb3-normal"));struct phy p={&d};assert(combo_reset_deassert(&d.reset,0)==0);assert(combo_usb3_mode(&p,PHY_MODE_USB_HOST,0)==0);clearlog();stop_usb(&d);assert(strstr(logbuf,"pipe-lock dp2-stop pipe-dummy"));assert(!strstr(logbuf,"usb3-stop"));assert(mode(&d,TYPEC_STATE_USB)==0);puts("# multifunction selects DP analog backend and ordered PIPE teardown PASS");} +static void test_dp4_dummy_usb2(void){struct t6050_dp d=setup();clearlog();assert(combo_orientation(&d,TYPEC_ORIENTATION_NORMAL)==0);assert(mode(&d,TYPEC_DP_STATE_E)==0);struct phy p={&d};assert(combo_usb2_mode(&p,PHY_MODE_USB_HOST,0)==0);assert(combo_reset_deassert(&d.reset,0)==0);clearlog();assert(combo_usb3_mode(&p,PHY_MODE_USB_HOST,0)==0);assert(!d.pipe_active&&logbuf[0]==0);assert(combo_usb2_reset(&p)==0);stop_usb(&d);assert(d.enabled);assert(mode(&d,TYPEC_STATE_SAFE)==0);assert(!d.enabled);puts("# DP4 retains dummy PIPE and independent USB2 PASS");} +static void test_failed_transitions_latch(void){for(unsigned i=0;i<4;i++){struct t6050_dp d=setup();clearlog();assert(combo_orientation(&d,TYPEC_ORIENTATION_NORMAL)==0);fail_at=(const char*[]){"domain-on","usb2-start","dp4-start","usb3-normal"}[i];assert(mode(&d,i==3?TYPEC_STATE_USB:TYPEC_DP_STATE_C)==-EIO);assert(d.faulted);clearlog();assert(mode(&d,TYPEC_STATE_USB)==-EIO);assert(combo_orientation(&d,TYPEC_ORIENTATION_REVERSE)==-EIO);assert(logbuf[0]==0);}puts("# partial hardware failures latch ownership PASS");} +static void test_pipe_failure_retains(void){struct t6050_dp d=setup();clearlog();attach_usb(&d);fail_at="pipe-dummy";struct phy p={&d};assert(combo_usb3_poweroff(&p)==-EIO);assert(d.pipe_active&&d.controller_active&&d.faulted);clearlog();assert(mode(&d,TYPEC_DP_STATE_C)==-EIO);assert(logbuf[0]==0);puts("# failed PIPE handoff cannot grant DP ownership PASS");} +static void test_unconnected_and_unsupported(void){struct t6050_dp d=setup();clearlog();assert(mode(&d,TYPEC_STATE_USB)==-ENOLINK);assert(mode(&d,TYPEC_DP_STATE_C)==-ENOLINK);struct typec_mux_state invalid={33,NULL};assert(combo_mux(&d,&invalid)==-EOPNOTSUPP);struct phy p={&d};assert(combo_usb2_mode(&p,PHY_MODE_USB_DEVICE,0)==-EOPNOTSUPP);assert(logbuf[0]==0);puts("# absent cable and unsupported roles fail without MMIO PASS");} +int main(void){test_usb_dp_usb();test_safe_disconnect();test_reverse_and_redundant();test_dp2_shared();test_dp4_dummy_usb2();test_failed_transitions_latch();test_pipe_failure_retains();test_unconnected_and_unsupported();puts("# 8 lifecycle groups PASS");} +''' + +print("TAP version 13\n1..1", flush=True) +if not shutil.which("cc"): + print("ok 1 - Type-C coordination # SKIP cc unavailable") + raise SystemExit(4) +try: + with tempfile.TemporaryDirectory(prefix="t6050-combo-") as temporary: + source=Path(temporary)/"test.c" + binary=Path(temporary)/"test" + source.write_text(preamble+'\n'.join(fn(n) for n in names)+tests) + subprocess.run(["cc","-std=gnu11","-Wall","-Wno-unused-parameter",str(source),"-o",str(binary)],check=True) + subprocess.run([str(binary)],check=True) +except subprocess.CalledProcessError: + print("not ok 1 - Type-C shared ownership and transitions") + raise SystemExit(1) +print("ok 1 - Type-C shared ownership and transitions")