diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index c3a2f54af0387..ce8c4daad85f2 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -119,7 +119,10 @@ properties: - description: Index into the provider's reg property. - description: Index in the firmware display register table. - description: Byte offset of the scratch register within the resource. - description: PMGR bandwidth scratch resource, display index and offset. + description: | + PMGR bandwidth scratch resource, display index and offset. On the T8132 + external DCPs it is the pipe's 8-byte realtime bandwidth request slot, + which lies inside the display register window the index names (disp-4). apple,bw-doorbell: $ref: /schemas/types.yaml#/definitions/phandle-array diff --git a/arch/arm64/boot/dts/apple/t8132-j713-dcpext.dtsi b/arch/arm64/boot/dts/apple/t8132-j713-dcpext.dtsi index 541cf598f29b7..49d2983481b04 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713-dcpext.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-j713-dcpext.dtsi @@ -5,7 +5,8 @@ * T8132 has two external display pipes, dispext0 and dispext1, each with * its own DCP, mailbox, DCP DART and display DART. They mirror the * internal display (t8132-j713-display.dtsi) at other bases and share its - * firmware clock window (disp-4). + * firmware clock window (disp-4), which also holds their realtime bandwidth + * request slots in the PMC. * * iBoot starts both and leaves them quiesced, as it does the internal one. * When the boot loader hands them over (opt-in, and only after the internal @@ -81,6 +82,7 @@ power-domains = <&ps_dispext0_cpu>; dma-noncoherent; apple,dcp-index = <1>; + apple,bw-scratch = <&pmgr_dcpext 0 4 0>; #address-cells = <2>; #size-cells = <2>; @@ -91,6 +93,17 @@ }; }; + /* + * The external pipes' realtime bandwidth request slots in the PMC, + * 0x2c000 and 0x30000 into the window the dcpexts have as disp-4. + */ + pmgr_dcpext: power-management@38282c000 { + reg = <0x3 0x8282c000 0x0 0x8>, + <0x3 0x82830000 0x0 0x8>; + reg-names = "dcpext0-bw-scratch", "dcpext1-bw-scratch"; + #apple,bw-scratch-cells = <3>; + }; + dcpext1_mbox: mailbox@48ae08000 { compatible = "apple,t8132-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x4 0x8ae08000 0x0 0x4000>; @@ -142,6 +155,7 @@ power-domains = <&ps_dispext1_cpu>; dma-noncoherent; apple,dcp-index = <2>; + apple,bw-scratch = <&pmgr_dcpext 1 4 0>; #address-cells = <2>; #size-cells = <2>; diff --git a/drivers/gpu/drm/apple/dcp-m4-callback.h b/drivers/gpu/drm/apple/dcp-m4-callback.h index 2cf3f73790ade..7a6fb73856e1a 100644 --- a/drivers/gpu/drm/apple/dcp-m4-callback.h +++ b/drivers/gpu/drm/apple/dcp-m4-callback.h @@ -21,6 +21,11 @@ struct dcp_m4_callback_ops { int (*memory)(void *cookie, unsigned int method, const void *input, void *output); /* Idle caching: D117 state, D578/D579 fence, D580 cache-surface release. */ int (*idle)(void *cookie, unsigned int method, const void *input, void *output); + /* + * D003 rt_bandwidth_setup_ap: its reply, @output zeroed (0x3c bytes). + * Absent: the optional service is declined (status 1). + */ + int (*bandwidth)(void *cookie, void *output); }; struct dcp_m4_callback_shape { @@ -129,6 +134,8 @@ static inline int dcp_m4_callback_dispatch(const struct dcp_m4_callback_ops *ops put_unaligned_le32(1, out); return 0; case 3: + if (ops->bandwidth) + return dcp_m4_callback_result(ops->bandwidth(cookie, out)); put_unaligned_le32(1, out + 56); /* Decline optional bandwidth service. */ return 0; case 6: diff --git a/drivers/gpu/drm/apple/dcp-m4-frame.h b/drivers/gpu/drm/apple/dcp-m4-frame.h index 0bf00b68d6aec..665f271e3c437 100644 --- a/drivers/gpu/drm/apple/dcp-m4-frame.h +++ b/drivers/gpu/drm/apple/dcp-m4-frame.h @@ -404,4 +404,21 @@ static inline int dcp_m4_swap_take_completion(struct dcp_m4_swap_state *swap) return 1; } +/* + * Hand a replied swap over to @to, which tracks it from then on (its D590 may + * be in already): @swap counts as taken, so that the next swap may begin. + */ +static inline int dcp_m4_swap_hand_off(struct dcp_m4_swap_state *swap, + struct dcp_m4_swap_state *to) +{ + if (swap->error) + return swap->error; + if (!swap->id || !swap->replied || swap->emitted) + return dcp_m4_swap_fail(swap, -EPROTO); + *to = *swap; + swap->completed = true; + swap->emitted = true; + return 0; +} + #endif diff --git a/drivers/gpu/drm/apple/iomfb_m4.c b/drivers/gpu/drm/apple/iomfb_m4.c index 494cfbaa98452..3e639595accda 100644 --- a/drivers/gpu/drm/apple/iomfb_m4.c +++ b/drivers/gpu/drm/apple/iomfb_m4.c @@ -80,8 +80,37 @@ struct iomfb_m4_ext_mode { /* The timing's IsPreferred and firmware score */ bool fw_preferred; s64 score; + /* Its colour element is one the timing lists as needing DSC */ + bool dsc; }; +/* A scratch-only realtime bandwidth mailbox (D003) is this long */ +#define DCP_M4_BW_MAILBOX_SIZE 8 + +/* + * How many external direct swaps may be in flight (A407 answered, the D590 + * not yet in), 1 or 2, read at each frame. At depth 1 every flip event is + * sent with its swap's D590. At depth 2 a frame's flip event can be sent + * before the firmware confirms that the frame replaced the previous one, so a + * compositor may draw into a buffer that is still on screen. + */ +#define DCP_M4_EXT_INFLIGHT 2 +static unsigned int t8132_ext_swap_depth = DCP_M4_EXT_INFLIGHT; + +static int t8132_ext_swap_depth_set(const char *val, const struct kernel_param *kp) +{ + return param_set_uint_minmax(val, kp, 1, DCP_M4_EXT_INFLIGHT); +} + +static const struct kernel_param_ops t8132_ext_swap_depth_ops = { + .set = t8132_ext_swap_depth_set, + .get = param_get_uint, +}; + +module_param_cb(t8132_ext_swap_depth, &t8132_ext_swap_depth_ops, &t8132_ext_swap_depth, 0644); +MODULE_PARM_DESC(t8132_ext_swap_depth, + "J713 external direct swaps in flight at most, 2 (default) or 1, from the next frame. At 2 a flip event can come before the firmware confirms the frame replaced the previous one, so a compositor may draw into a buffer still on screen; at 1 every flip event comes with its swap's completion"); + struct iomfb_m4 { struct apple_dcp *owner; struct device *dev; @@ -89,6 +118,8 @@ struct iomfb_m4 { struct iommu_group *scanout_group; resource_size_t coproc_phys; size_t coproc_size; + /* The realtime bandwidth mailbox from the DT (0: none), see dcp_m4_bw_mailbox() */ + u64 bw_mailbox; struct work_struct backlight_work; void __iomem *coproc; struct apple_rtkit *rtkit; @@ -173,7 +204,6 @@ struct iomfb_m4 { static void dcp_m4_runtime_wake(struct iomfb_m4 *dcp); struct dcp_m4_runtime; -static void dcp_m4_ext_async_done(struct dcp_m4_runtime *r); static void dcp_m4_ext_async_work(struct work_struct *work); static int dcp_m4_set_power_state(struct iomfb_m4 *dcp, bool on); static void dcp_m4_runtime_receive(struct iomfb_m4 *dcp, u64 word); @@ -712,8 +742,8 @@ static int dcp_m4_create_piodma_ext(struct iomfb_m4 *dcp) /* * A host window of SID0 (dva, 0: not claimed) and the framebuffer it maps (key, * NULL: empty), with a reference held by @put's owner; whether the last - * confirmed swap shows it; the given-up swap that may still show it; pinned: - * its unmap failed, kept until reboot. + * confirmed swap shows it (given-up swaps that may still show it are in + * ext_lost_swap); pinned: its unmap failed, kept until reboot. */ #define DCP_M4_EXT_WINDOWS 6 struct dcp_m4_ext_window { @@ -722,10 +752,51 @@ struct dcp_m4_ext_window { void (*put)(void *key); unsigned int count; bool on_screen, pinned; - u32 lost_id; unsigned long used; }; +/* + * A swap given up before its D590 (see dcp_m4_ext_swap_give_up()): its ID (0: + * a free slot), the window it may still put on screen (-1: none, or retired + * by a confirmed swap since), and whether a newer swap was shown before it. + */ +struct dcp_m4_ext_lost { + u32 id; + int win; + bool superseded; +}; + +/* + * A direct swap whose A407 reply came and whose D590 has not: its state, its + * window (-1: none), the link generation and the deadline it went out with, + * whether its flip waits for its completion or the older one's, whether a + * and whether a newer swap was shown before it completed. + */ +struct dcp_m4_ext_inflight { + struct dcp_m4_swap_state swap; + int win; + int gen; + unsigned long deadline; + bool flip_held; + bool superseded; +}; + +/* How a direct swap leaves flight, see dcp_m4_ext_async_done(). */ +enum dcp_m4_ext_end { + DCP_M4_EXT_END_SHOWN, /* its own D590, displayed */ + DCP_M4_EXT_END_KEPT, /* named in a later swap's D590, or shown before a newer one */ + DCP_M4_EXT_END_UNSHOWN, /* its own D590, not displayed */ + DCP_M4_EXT_END_TAKEN, /* its D590 before its A407 reply, taken account of then */ +}; + +/* What the D590 being handled says: shown, and the earlier swaps it completes. */ +struct dcp_m4_ext_done { + bool valid; + bool shown; + u32 count; + u32 hol[4]; +}; + struct dcp_m4_runtime { struct iomfb_m4 *dcp; struct dcp_m4_properties *properties; @@ -812,19 +883,31 @@ struct dcp_m4_runtime { unsigned int ext_back; struct drm_rect ext_prev_damage; /* - * The host windows; the swap in flight without waiting for its D590 - * (its ID, 0: none; its window, -1: none; the link generation and the - * deadline it went out with); whether the next swap_full returns after - * its A407 reply; the work that gives the swap up at its deadline or - * when its link goes. + * The host windows; the direct swaps in flight without waiting for their + * D590, oldest first, and how many; the window of the direct swap going + * out (-1: none); how many the next swap_full may leave in flight, + * returning after its A407 reply (0: it waits for its D590), and whether + * its flip may go at once (none older in flight); what the D590 being + * handled says (dcp_m4_external_callback()), and whether the swap going + * out was shown, by a D590 before its A407 reply; the work that gives the + * oldest up at its deadline or when its link goes. */ struct dcp_m4_ext_window ext_win[DCP_M4_EXT_WINDOWS]; - u32 ext_async_id; - int ext_async_win; - int ext_async_gen; - unsigned long ext_async_deadline; - bool ext_swap_async; + struct dcp_m4_ext_inflight ext_q[DCP_M4_EXT_INFLIGHT]; + unsigned int ext_qn; + int ext_swap_win; + unsigned int ext_swap_async; + bool ext_swap_now; + struct dcp_m4_ext_done ext_done; + bool ext_swap_shown; struct delayed_work ext_async_work; + /* + * Direct swaps completed by a later swap's D590 (named in it), whose own + * D590 may still come: dropped then (0: a free slot). Never evicted: with + * no slot for each swap in flight no direct swap goes out, and A406 + * giving one of these IDs again orphans its start, as for ext_lost_swap. + */ + u32 ext_early[16]; /* * The link's generation, which a released route * (iomfb_m4_external_link_gone()) and a D575(0) move on. @@ -832,10 +915,10 @@ struct dcp_m4_runtime { atomic_t ext_link_gen; /* * Swaps given up before their D590 (see dcp_m4_ext_swap_give_up()), - * each kept until its D590 comes (0: a free slot), so that the late - * D590 is told apart and dropped. + * each kept until its D590 comes (ID 0: a free slot), so that the late + * D590 is told apart and dropped, with the window each may still show. */ - u32 ext_lost_swap[16]; + struct dcp_m4_ext_lost ext_lost_swap[16]; /* * The given-up swap that holds swaps (0: none), and whether a DRM * enable lets one try (a probe); surface of the swap in flight (-1: @@ -858,6 +941,10 @@ struct dcp_m4_runtime { u32 ext_buf_lost[2]; }; +static void dcp_m4_ext_async_done(struct dcp_m4_runtime *r, int i, enum dcp_m4_ext_end end); +static void dcp_m4_ext_shown(struct dcp_m4_runtime *r, u32 id, int win, int older, + enum dcp_m4_ext_end end); + static void dcp_m4_runtime_wake(struct iomfb_m4 *dcp) { struct dcp_m4_runtime *r = smp_load_acquire(&dcp->runtime); @@ -1063,69 +1150,151 @@ static int dcp_m4_runtime_event(void *cookie, const char *name, const void *dict static int dcp_m4_ext_lost_slot(struct dcp_m4_runtime *r, u32 id) { for (unsigned int i = 0; id && i < ARRAY_SIZE(r->ext_lost_swap); i++) - if (r->ext_lost_swap[i] == id) + if (r->ext_lost_swap[i].id == id) return i; return -1; } +/* Whether a given-up swap may still put window @i on screen. */ +static bool dcp_m4_ext_win_lost(struct dcp_m4_runtime *r, int i) +{ + for (unsigned int j = 0; j < ARRAY_SIZE(r->ext_lost_swap); j++) + if (r->ext_lost_swap[j].id && r->ext_lost_swap[j].win == i) + return true; + return false; +} + /* - * The late D590 of the given-up swap @id: dropped, and its - * ID forgotten. A surface it put on screen, which no confirmed swap has - * retired since, is the one on screen now and the other one free; the hold - * it is responsible for, if any, ends. False if @id is no given-up swap's. + * The late D590 of the given-up swap @id: dropped, and its ID forgotten. + * Displayed (@shown): a surface it put on screen, which no confirmed swap has + * retired since, is the one on screen now and the other one free, or, with a + * newer swap shown before it, on screen with that one. Not displayed: what is + * on screen stays, and it no longer makes its surfaces uncertain (others + * given up may still). The hold it is responsible for, if any, ends. False if + * @id is no given-up swap's. */ -static bool dcp_m4_ext_late_d590(struct dcp_m4_runtime *r, u32 id) +static bool dcp_m4_ext_late_d590(struct dcp_m4_runtime *r, u32 id, bool shown) { int slot = dcp_m4_ext_lost_slot(r, id); - bool shown = false, held = r->ext_hold_id == id; + bool front = false, held = r->ext_hold_id == id; + struct dcp_m4_ext_lost lost; if (slot < 0) return false; - r->ext_lost_swap[slot] = 0; + lost = r->ext_lost_swap[slot]; + r->ext_lost_swap[slot] = (struct dcp_m4_ext_lost){ .win = -1 }; for (int i = 0; i < 2; i++) { if (r->ext_buf_lost[i] != id) continue; r->ext_buf_lost[i] = 0; + if (!shown) + continue; r->ext_front = i; r->ext_back = i ^ 1; r->ext_buf_stale[0] = true; r->ext_buf_stale[1] = true; - shown = true; - } - /* A window it put on screen is the one shown now. */ - for (int i = 0; i < DCP_M4_EXT_WINDOWS; i++) { - if (!r->ext_win[i].lost_id || r->ext_win[i].lost_id != id) - continue; - for (int j = 0; j < DCP_M4_EXT_WINDOWS; j++) - r->ext_win[j].on_screen = false; - r->ext_win[i].lost_id = 0; - r->ext_win[i].on_screen = true; - r->ext_front = -1; - r->ext_buf_stale[0] = true; - r->ext_buf_stale[1] = true; - shown = true; + front = true; + } + /* A window it put on screen is the one shown now, or one of them. */ + if (shown && lost.win >= 0) { + if (!lost.superseded) { + for (int j = 0; j < DCP_M4_EXT_WINDOWS; j++) + r->ext_win[j].on_screen = false; + r->ext_front = -1; + r->ext_buf_stale[0] = true; + r->ext_buf_stale[1] = true; + } + r->ext_win[lost.win].on_screen = true; + front = true; } if (held) { r->ext_hold_id = 0; r->ext_hold_probe = false; } dev_info(r->dcp->dev, "swap %u: its D590 came late: dropped%s%s\n", id, - shown ? "; its surface is on screen" : "", held ? "; swaps go on" : ""); + !shown ? "; not displayed, the frame on screen stays" : + front ? "; its surface is on screen" : "", held ? "; swaps go on" : ""); return true; } +/* The slot of @id among the swaps completed early (0: a free one), or -1. */ +static int dcp_m4_ext_early_slot(struct dcp_m4_runtime *r, u32 id) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(r->ext_early); i++) + if (r->ext_early[i] == id) + return i; + return -1; +} + +/* The index of the direct swap @id in flight, or -1. */ +static int dcp_m4_ext_q_find(struct dcp_m4_runtime *r, u32 id) +{ + for (unsigned int i = 0; id && i < r->ext_qn; i++) + if (r->ext_q[i].swap.id == id) + return i; + return -1; +} + static int dcp_m4_runtime_swap_done(void *cookie, u32 id) { struct dcp_m4_runtime *r = cookie; - int ret; - + struct dcp_m4_ext_done done = r->ext_done; + int ret, i, slot; + + if (!r->dcp->external) + return dcp_m4_swap_record_done(&r->swap, id); + r->ext_done.valid = false; + /* Its payload unread: shown, and no earlier swap named. */ + if (!done.valid) + done = (struct dcp_m4_ext_done){ .shown = true }; /* The late D590 of a swap given up is not this swap's. */ - if (r->dcp->external && dcp_m4_ext_late_d590(r, id)) + if (dcp_m4_ext_late_d590(r, id, done.shown)) + return 0; + /* Nor is that of a swap a later one's D590 named. */ + slot = dcp_m4_ext_early_slot(r, id); + if (slot >= 0) { + r->ext_early[slot] = 0; + dev_dbg(r->dcp->dev, "swap %u: its D590 after a later one's: dropped\n", id); + return 0; + } + /* + * The earlier direct swaps in flight it names complete first, kept on + * screen with what is there (macOS completes them as displayed), each + * remembered: its own D590 may still come. Unnamed ones stay in flight. + */ + for (unsigned int j = 0; j < done.count; j++) { + i = done.hol[j] == id ? -1 : dcp_m4_ext_q_find(r, done.hol[j]); + if (i < 0) + continue; + slot = dcp_m4_ext_early_slot(r, 0); + if (slot < 0) { + dev_warn_ratelimited(r->dcp->dev, "swap %u named done by swap %u, with no slot to remember it: left in flight\n", + done.hol[j], id); + continue; + } + r->ext_early[slot] = done.hol[j]; + dcp_m4_ext_async_done(r, i, DCP_M4_EXT_END_KEPT); + } + /* A direct swap in flight is done: its flip, or the next one's. */ + i = dcp_m4_ext_q_find(r, id); + if (i >= 0) { + dcp_m4_ext_async_done(r, i, done.shown ? DCP_M4_EXT_END_SHOWN : + DCP_M4_EXT_END_UNSHOWN); return 0; + } ret = dcp_m4_swap_record_done(&r->swap, id); - /* A direct swap in flight is done: its flip now. */ - if (!ret && r->dcp->external && r->ext_async_id == id && r->swap.replied) - dcp_m4_ext_async_done(r); + /* + * The swap going out, before its A407 reply: whether it was shown, and + * for a direct one what that means for the screen, now, in D590 order; + * only its completion and flip wait for the reply. + */ + if (!ret) { + r->ext_swap_shown = done.shown; + if (r->ext_swap_async) + dcp_m4_ext_shown(r, id, r->ext_swap_win, r->ext_qn, + done.shown ? DCP_M4_EXT_END_SHOWN : + DCP_M4_EXT_END_UNSHOWN); + } return ret; } @@ -1170,6 +1339,27 @@ static int dcp_m4_runtime_idle_cache(void *cookie, unsigned int method, return -EOPNOTSUPP; } +/* + * D003 rt_bandwidth_setup_ap, answered as macOS answers it for a T8132 + * external pipe: the DCP's 8-byte bandwidth request slot as the mailbox, + * whose length (8) selects the scratch-only protocol, with no doorbell. The + * firmware then writes its realtime bandwidth requests there. Without a + * mailbox from the DT (the internal panel) the service is declined. + */ +static int dcp_m4_runtime_bandwidth(void *cookie, void *output) +{ + struct dcp_m4_runtime *r = cookie; + u8 *out = output; + + if (!r->dcp->bw_mailbox) { + put_unaligned_le32(1, out + 0x38); /* status: declined */ + return 0; + } + put_unaligned_le64(r->dcp->bw_mailbox, out + 0x08); + put_unaligned_le32(DCP_M4_BW_MAILBOX_SIZE, out + 0x2c); + return 0; +} + static const struct dcp_m4_callback_ops dcp_m4_runtime_ops = { .call = dcp_m4_runtime_call, .property = dcp_m4_runtime_property, @@ -1177,6 +1367,7 @@ static const struct dcp_m4_callback_ops dcp_m4_runtime_ops = { .event = dcp_m4_runtime_event, .swap_done = dcp_m4_runtime_swap_done, .idle = dcp_m4_runtime_idle_cache, + .bandwidth = dcp_m4_runtime_bandwidth, }; static void dcp_m4_runtime_bad_word(struct dcp_m4_runtime *r, u64 word, int error) @@ -1332,6 +1523,9 @@ static int dcp_m4_external_shape(u32 tag, u32 input, u32 output) if (method == 134) return !input && output == 4 ? method : -EMSGSIZE; + /* D411 mapDeviceMemoryWithIndex, see dcp_m4_external_callback(). */ + if (method == 411) + return input == 0x10 && output == 0x1c ? method : -EMSGSIZE; cb = method < 0 ? NULL : dcp_m4_ext_cb(method); if (!cb) return -EOPNOTSUPP; @@ -1348,6 +1542,46 @@ static int dcp_m4_external_callback(struct dcp_m4_runtime *r, u32 tag, bool remo u8 one[4], out[4]; int ret; + /* + * D590 swap_complete_ap_gated: whether the swap was displayed (+0x4), + * and the earlier swaps it completes, a count at +0x19 of the IDs at + * +0x5, with +0x1d set (and +0x72c, no completion data, clear). Kept for + * its swap_done hook, which the shared dispatcher calls next. + */ + if (method == 590 && input_size == dcp_m4_pf->done_size) { + struct dcp_m4_ext_done *d = &r->ext_done; + u32 count = get_unaligned_le32(input + 0x19); + + *d = (struct dcp_m4_ext_done){ .valid = true, .shown = input[4] != 0 }; + if (input[0x1d] == 1 && !input[0x72c]) { + if (count > ARRAY_SIZE(d->hol)) + dev_warn_ratelimited(r->dcp->dev, "D590 of swap %u names %u earlier swaps: ignored\n", + get_unaligned_le32(input), count); + else + for (d->count = 0; d->count < count; d->count++) + d->hol[d->count] = + get_unaligned_le32(input + 5 + 4 * d->count); + } + return -ENOENT; + } + /* + * D581 swap_complete_head_of_line: a completion too, whose 0xc bytes the + * driver does not decode: logged and acknowledged, its swap left to a + * D590 or to its give-up. + */ + if (method == 581 && input_size == 0xc) { + static DEFINE_RATELIMIT_STATE(rs, 5 * HZ, 4); + u32 id = get_unaligned_le32(input); + + if (__ratelimit(&rs)) { + dev_warn(r->dcp->dev, "D581 swap_complete_head_of_line, word 0 %u (%s): left to a D590 or its give-up\n", + id, dcp_m4_ext_q_find(r, id) >= 0 ? "a swap in flight" : + "no swap in flight"); + print_hex_dump_debug("D581: ", DUMP_PREFIX_OFFSET, 16, 1, input, + input_size, false); + } + return -ENOENT; + } /* * D572 powerUpDART(on): the firmware switches its display DART * on or off, which the host does nothing for but must not touch the @@ -1442,6 +1676,43 @@ static int dcp_m4_external_callback(struct dcp_m4_runtime *r, u32 tag, bool remo put_unaligned_le32(0, output); return 0; } + /* + * D411 mapDeviceMemoryWithIndex, which the firmware asks for its register + * windows (the one holding the bandwidth mailbox among them): "VORP", the + * display register index and flags in; the window's DVA, address and + * length, then a u32 status out. Only a disp-N window the DT describes is + * given. As on the desktop 26.x firmware, the DVA is the physical address: + * the firmware reaches its register windows on its DART-bypass stream. + * Any other index gets status 1. + */ + if (method == 411) { + struct resource *res = NULL; + u8 *o = output; + char name[16]; + u32 index; + + if (input_size != 0x10 || output_size != 0x1c) + return -EMSGSIZE; + index = get_unaligned_le32(input + 4); + if (!memcmp(input, "VORP", 4) && index < MAX_DISP_REGISTERS) { + snprintf(name, sizeof(name), "disp-%u", index); + res = platform_get_resource_byname(to_platform_device(r->dcp->dev), + IORESOURCE_MEM, name); + } + memset(o, 0, 0x1c); + if (!res) { + put_unaligned_le32(1, o + 24); + dev_warn(r->dcp->dev, "D411: register index %u is not described: refused\n", + index); + return 0; + } + put_unaligned_le64(res->start, o); + put_unaligned_le64(res->start, o + 8); + put_unaligned_le64(resource_size(res), o + 16); + dev_dbg(r->dcp->dev, "D411: register index %u (flags %#x) mapped: %pR\n", index, + get_unaligned_le32(input + 8), res); + return 0; + } /* * D575, the hotplug report: the connection state and a 76-byte * tiled-display record (its null flag at 84) in, a 76-byte record out. @@ -1463,8 +1734,8 @@ static int dcp_m4_external_callback(struct dcp_m4_runtime *r, u32 tag, bool remo /* A sink that went takes a new power-on and mode. */ if (!connected) { atomic_inc(&r->ext_link_gen); - /* A direct swap in flight is given up now. */ - if (r->ext_async_id) + /* The direct swaps in flight are given up now. */ + if (r->ext_qn) mod_delayed_work(r->wq, &r->ext_async_work, 0); WRITE_ONCE(r->ext_powered, false); WRITE_ONCE(r->ext_cur_timing, 0); @@ -1743,14 +2014,16 @@ static int dcp_m4_ext_swap_wait(struct dcp_m4_runtime *r, int link_gen, u64 *wor * After one given up on a live link, swaps are held (iomfb_m4_ext_present()) * until its D590 or a confirmed swap after a DRM enable, so that a stuck * pipe does not cost every frame 5 s. With every slot taken the swap is - * not given up. True if the swap was given up so; anything else is the - * session's failure as before. + * not given up. @swap is its state: r->swap, or a direct swap's in flight, + * @win its window (-1: none) and @superseded whether a newer swap was shown + * before it. True if the swap was given up so; anything else is the session's + * failure as before. */ -static bool dcp_m4_ext_swap_give_up(struct dcp_m4_runtime *r, int error) +static bool dcp_m4_ext_swap_give_up(struct dcp_m4_runtime *r, struct dcp_m4_swap_state *swap, + int win, bool superseded, int error) { struct iomfb_m4 *dcp = r->dcp; struct dcp_m4_transaction *t = &dcp->transaction; - struct dcp_m4_swap_state *swap = &r->swap; int slot; if (!dcp->external || (error != -ETIMEDOUT && error != -ENOLINK) || @@ -1762,14 +2035,16 @@ static bool dcp_m4_ext_swap_give_up(struct dcp_m4_runtime *r, int error) return false; slot = -1; for (unsigned int i = 0; slot < 0 && i < ARRAY_SIZE(r->ext_lost_swap); i++) - if (!r->ext_lost_swap[i]) + if (!r->ext_lost_swap[i].id) slot = i; if (slot < 0) { dev_warn(dcp->dev, "swap %u: %d given-up swaps still without their D590: not given up\n", swap->id, (int)ARRAY_SIZE(r->ext_lost_swap)); return false; } - r->ext_lost_swap[slot] = swap->id; + r->ext_lost_swap[slot] = (struct dcp_m4_ext_lost){ + .id = swap->id, .win = win, .superseded = superseded, + }; swap->emitted = true; if (r->ext_swap_surface >= 0) r->ext_buf_lost[r->ext_swap_surface] = swap->id; @@ -1843,9 +2118,9 @@ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, if (!ret) ret = dcp_m4_swap_start_reply(started, sizeof(started), &id); /* - * An ID whose given-up swap's D590 may still come is not reused: the - * two completions could not be told apart, as a D590 names the swap by - * its ID alone. A407 is not sent, and the start is left orphaned: no + * An ID whose given-up (or early-completed) swap's D590 may still come + * is not reused: the two completions could not be told apart, as a D590 + * names the swap by its ID alone. A407 is not sent, and the start is left orphaned: no * host call ends or discards a started swap, and completing it (with a * surfaceless A407) would leave two completions under one ID that a * single D590 could belong to either of, while the firmware may swallow @@ -1853,7 +2128,8 @@ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, * (dcp_m4_runtime_swap_full()) until reboot; the old swap's late D590 * is still dropped, but ends no hold. */ - if (!ret && ext && dcp_m4_ext_lost_slot(r, id) >= 0) { + if (!ret && ext && + (dcp_m4_ext_lost_slot(r, id) >= 0 || dcp_m4_ext_early_slot(r, id) >= 0)) { dev_warn(dcp->dev, "A406 gave swap %u again, whose D590 may still come: not submitted, the start left orphaned; no swap starts until reboot\n", id); r->ext_orphan_start = id; @@ -1876,25 +2152,46 @@ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, if (!ret) ret = dcp_m4_swap_record_reply(&r->swap, submitted, sizeof(submitted)); /* - * A direct swap does not wait for its D590: that completes it - * (dcp_m4_runtime_swap_done()), or its work gives it up at the deadline - * or when the link goes (dcp_m4_ext_async_work()). Nothing of it is - * outstanding on the RPC buffer: A406 and A407 are answered. + * A direct swap does not wait for its D590: it joins the swaps in flight + * and is tracked there (r->swap may begin the next one) until its D590, + * or a later swap's, completes it (dcp_m4_runtime_swap_done()), or its + * work gives it up at the deadline or when the link goes + * (dcp_m4_ext_async_work()). Nothing of it is outstanding on the RPC + * buffer: A406 and A407 are answered. Its flip goes at once with none + * older in flight and a depth over 1 (the previous frame's buffer may still + * be on screen then), else with the completion of the older one, or its + * own. */ if (!ret && ext && r->ext_swap_async) { - r->deadline = 0; - kfree(input); - r->ext_async_id = r->swap.id; - r->ext_async_gen = link_gen; - r->ext_async_deadline = jiffies + msecs_to_jiffies(5000); - if (r->swap.completed) { - dcp_m4_ext_async_done(r); - } else { - queue_delayed_work(r->wq, &r->ext_async_work, msecs_to_jiffies(5000)); - if (!r->ext_connected || atomic_read(&r->ext_link_gen) != link_gen) - mod_delayed_work(r->wq, &r->ext_async_work, 0); + struct dcp_m4_ext_inflight *e = + &r->ext_q[min_t(unsigned int, r->ext_qn, DCP_M4_EXT_INFLIGHT - 1)]; + struct dcp_m4_swap_state handed; + + /* iomfb_m4_ext_present_direct() left room for it. */ + ret = WARN_ON_ONCE(r->ext_qn >= DCP_M4_EXT_INFLIGHT) ? -EBUSY : + dcp_m4_swap_hand_off(&r->swap, &handed); + if (!ret) { + r->deadline = 0; + kfree(input); + *e = (struct dcp_m4_ext_inflight){ + .swap = handed, + .win = r->ext_swap_win, + .gen = link_gen, + .deadline = jiffies + msecs_to_jiffies(5000), + .flip_held = r->ext_qn || r->ext_swap_async < 2, + }; + r->ext_swap_now = !e->flip_held; + r->ext_qn++; + if (e->swap.completed) { + dcp_m4_ext_async_done(r, r->ext_qn - 1, DCP_M4_EXT_END_TAKEN); + } else { + queue_delayed_work(r->wq, &r->ext_async_work, + msecs_to_jiffies(5000)); + if (!r->ext_connected || atomic_read(&r->ext_link_gen) != link_gen) + mod_delayed_work(r->wq, &r->ext_async_work, 0); + } + return 0; } - return 0; } while (!ret && !r->swap.completed) { if (ext) { @@ -1914,7 +2211,7 @@ static int dcp_m4_runtime_swap_full(struct dcp_m4_runtime *r, bool primary, /* Only the copied host input is freed; the firmware-visible RPC stays pinned. */ kfree(input); /* Only its D590 missing: given up, the session kept. */ - if (ret && d590_missing && dcp_m4_ext_swap_give_up(r, ret)) + if (ret && d590_missing && dcp_m4_ext_swap_give_up(r, &r->swap, -1, false, ret)) return ret; if (ret) { dcp_m4_swap_fail(&r->swap, ret); @@ -2016,8 +2313,8 @@ static int dcp_m4_startup(struct iomfb_m4 *dcp) /* No surface on screen or in flight yet. */ r->ext_swap_surface = -1; r->ext_front = -1; - /* No direct swap in flight. */ - r->ext_async_win = -1; + /* No direct swap going out. */ + r->ext_swap_win = -1; INIT_DELAYED_WORK(&r->ext_async_work, dcp_m4_ext_async_work); init_completion(&r->received); INIT_WORK(&r->idle, dcp_m4_runtime_idle); @@ -2187,6 +2484,44 @@ static bool iomfb_m4_firmware_property(struct device_node *np, const char *name) version[0] == 26 && version[1] == 6 && version[2] == 2; } +/* + * The realtime bandwidth mailbox the DT gives @dev, or 0: apple,bw-scratch + * names the provider's reg range, the firmware's display register index and + * an offset into the range. The mailbox is DCP_M4_BW_MAILBOX_SIZE bytes, and + * must lie inside display register window 4, through which the firmware + * reaches a scratch-only mailbox. + */ +static u64 dcp_m4_bw_mailbox(struct device *dev) +{ + struct of_phandle_args args; + struct resource range, *win = NULL; + u64 slot = 0; + int ret; + + ret = of_parse_phandle_with_args(dev->of_node, "apple,bw-scratch", + "#apple,bw-scratch-cells", 0, &args); + if (ret == -ENOENT) + return 0; + if (ret) { + dev_warn(dev, "apple,bw-scratch: %d: realtime bandwidth declined\n", ret); + return 0; + } + if (args.args_count == 3 && args.args[1] == 4 && + !of_address_to_resource(args.np, args.args[0], &range) && + resource_size(&range) >= DCP_M4_BW_MAILBOX_SIZE && + args.args[2] <= resource_size(&range) - DCP_M4_BW_MAILBOX_SIZE) { + win = platform_get_resource_byname(to_platform_device(dev), IORESOURCE_MEM, + "disp-4"); + slot = range.start + args.args[2]; + } + of_node_put(args.np); + if (!win || slot < win->start || slot + DCP_M4_BW_MAILBOX_SIZE - 1 > win->end) { + dev_warn(dev, "apple,bw-scratch: no mailbox inside disp-4: realtime bandwidth declined\n"); + return 0; + } + return slot; +} + int iomfb_m4_init(struct apple_dcp *owner) { struct platform_device *pdev = to_platform_device(owner->dev); @@ -2230,6 +2565,7 @@ int iomfb_m4_init(struct apple_dcp *owner) return -ENOMEM; dcp->owner = owner; dcp->dev = dev; + dcp->bw_mailbox = dcp_m4_bw_mailbox(dev); mutex_init(&dcp->lock); mutex_init(&dcp->ext_modes_lock); spin_lock_init(&dcp->state_lock); @@ -2516,8 +2852,8 @@ void iomfb_m4_external_link_gone(struct apple_dcp *owner) if (r && dcp->external) { /* A swap waiting for its D590 stops waiting (dcp_m4_ext_swap_wait()). */ atomic_inc(&r->ext_link_gen); - /* A direct swap in flight is given up now. */ - if (READ_ONCE(r->ext_async_id)) + /* The direct swaps in flight are given up now. */ + if (READ_ONCE(r->ext_qn)) mod_delayed_work(r->wq, &r->ext_async_work, 0); /* A new link takes a new power-on and mode. */ WRITE_ONCE(r->ext_powered, false); @@ -2567,6 +2903,16 @@ static u64 dcp_m4_ext_capacity(struct apple_dcp *owner) return (u64)lanes * rate * 270 * 1000 * 8 / 10; } +/* + * Whether a cached mode fits the link: at 24 bpp uncompressed, at 8 bpp (the + * lowest DSC target) with a colour that needs DSC, whose rate the firmware + * picks itself. + */ +static bool dcp_m4_ext_fits(const struct iomfb_m4_ext_mode *e, u64 capacity) +{ + return !capacity || (u64)e->mode.clock * (e->dsc ? 8 : 24) <= capacity; +} + /* * The firmware's IDs of a mode iomfb_m4_ext_get_modes() * offered: -ENOENT for any other mode, -ENOSPC for one over the link. @@ -2587,13 +2933,16 @@ int iomfb_m4_ext_mode_ids(struct apple_dcp *owner, const struct drm_display_mode if (!drm_mode_match(&e->mode, mode, DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK | DRM_MODE_MATCH_FLAGS)) continue; - if (capacity && (u64)e->mode.clock * 24 > capacity) { + if (!dcp_m4_ext_fits(e, capacity)) { ret = -ENOSPC; continue; } ids->timing = e->timing; ids->color = e->color; ids->bootstrap = e->bootstrap; + dev_dbg(dcp->dev, "mode %ux%u@%d: timing %u, colour %u%s\n", + e->mode.hdisplay, e->mode.vdisplay, drm_mode_vrefresh(&e->mode), + e->timing, e->color, e->dsc ? " (DSC)" : ""); ret = 0; break; } @@ -2601,11 +2950,6 @@ int iomfb_m4_ext_mode_ids(struct apple_dcp *owner, const struct drm_display_mode return ret; } -static bool dcp_m4_ext_fits(const struct drm_display_mode *m, u64 capacity) -{ - return !capacity || (u64)m->clock * 24 <= capacity; -} - /* A mode's refresh rate in mHz, to compare 59.94 with 60 Hz. */ static u64 dcp_m4_ext_mhz(const struct drm_display_mode *m) { @@ -2634,7 +2978,8 @@ static bool dcp_m4_ext_sink_preferred(const struct iomfb_m4 *dcp, /* * The cached mode to mark preferred, -1 for none (the - * 1920x1080 fallback), with the rule that chose it in @why: + * 1920x1080 fallback), never one that needs DSC (the user picks those), with + * the rule that chose it in @why: * 1. the native size is the largest (pixel area) of the sink's preferred * timings: its PreferredTimingElements (their own sizes too) and its * IsPreferred timings; @@ -2670,8 +3015,8 @@ static int dcp_m4_ext_pick_preferred(struct iomfb_m4 *dcp, u64 capacity, const c u64 mhz = dcp_m4_ext_mhz(&e[i].mode), best_mhz; bool pref, best_pref; - if (!dcp_m4_ext_same_size(&e[i].mode, width, height) || - !dcp_m4_ext_fits(&e[i].mode, capacity) || mhz < DCP_M4_EXT_MIN_MHZ) + if (!dcp_m4_ext_same_size(&e[i].mode, width, height) || e[i].dsc || + !dcp_m4_ext_fits(&e[i], capacity) || mhz < DCP_M4_EXT_MIN_MHZ) continue; if (best < 0) { best = i; @@ -2692,8 +3037,8 @@ static int dcp_m4_ext_pick_preferred(struct iomfb_m4 *dcp, u64 capacity, const c u64 a = (u64)e[i].mode.hdisplay * e[i].mode.vdisplay, b; u64 mhz = dcp_m4_ext_mhz(&e[i].mode); - if (!dcp_m4_ext_sink_preferred(dcp, &e[i]) || - !dcp_m4_ext_fits(&e[i].mode, capacity) || mhz < DCP_M4_EXT_MIN_MHZ) + if (!dcp_m4_ext_sink_preferred(dcp, &e[i]) || e[i].dsc || + !dcp_m4_ext_fits(&e[i], capacity) || mhz < DCP_M4_EXT_MIN_MHZ) continue; b = best < 0 ? 0 : (u64)e[best].mode.hdisplay * e[best].mode.vdisplay; if (best < 0 || a > b || (a == b && mhz > dcp_m4_ext_mhz(&e[best].mode))) @@ -2724,7 +3069,7 @@ int iomfb_m4_ext_get_modes(struct apple_dcp *owner, struct drm_connector *connec const struct drm_display_mode *m = &dcp->ext_modes[i].mode; struct drm_display_mode *mode; - if (capacity && (u64)m->clock * 24 > capacity) { + if (!dcp_m4_ext_fits(&dcp->ext_modes[i], capacity)) { skipped++; continue; } @@ -2736,8 +3081,10 @@ int iomfb_m4_ext_get_modes(struct apple_dcp *owner, struct drm_connector *connec mode->type |= DRM_MODE_TYPE_PREFERRED; preferred = mode; } - if (!fallback || (mode->hdisplay == 1920 && mode->vdisplay == 1080 && - drm_mode_vrefresh(mode) == 60)) + /* A DSC mode is never the fallback either: the user picks one. */ + if (!dcp->ext_modes[i].dsc && + (!fallback || (mode->hdisplay == 1920 && mode->vdisplay == 1080 && + drm_mode_vrefresh(mode) == 60))) fallback = mode; drm_mode_probed_add(connector, mode); added++; @@ -3040,9 +3387,10 @@ static_assert(sizeof(struct dcp_m4_mode_info_req) == 0x7c); /* * The colour element of a timing: RGB with the SDR gamma EOTF, not virtual, - * not unsafe or needing DSC for the timing; the lowest depth offered (8 bpc - * if there is one); then sRGB/BT.709 colorimetry and full range over - * others, then the higher score. Never PQ or HLG. + * not unsafe for the timing, at 8 bpc if the timing lists it as needing DSC; + * the lowest depth offered (8 bpc if there is one); at that depth one that + * needs no DSC over one that does, then sRGB/BT.709 colorimetry and full + * range over others, then the higher score. Never PQ or HLG. */ struct dcp_m4_ext_pick { struct dcp_color_element best; @@ -3061,16 +3409,18 @@ static void dcp_m4_ext_pick_color(void *data, const struct dcp_color_element *e) struct dcp_m4_ext_pick *p = data; unsigned int rank, best_rank; - if (e->is_virtual || e->restricted || e->id < 0 || e->id > U32_MAX || e->score < 0 || + if (e->is_virtual || e->unsafe || e->id < 0 || e->id > U32_MAX || e->score < 0 || e->depth <= 0 || e->encoding != DCP_COLOR_FORMAT_RGB || - e->eotf != DCP_EOTF_SDR_GAMMA) + e->eotf != DCP_EOTF_SDR_GAMMA || (e->dsc_required && e->depth != 8)) return; rank = dcp_m4_ext_color_rank(e); best_rank = p->found ? dcp_m4_ext_color_rank(&p->best) : 0; if (p->found && (e->depth > p->best.depth || (e->depth == p->best.depth && - (rank < best_rank || - (rank == best_rank && e->score <= p->best.score))))) + (e->dsc_required > p->best.dsc_required || + (e->dsc_required == p->best.dsc_required && + (rank < best_rank || + (rank == best_rank && e->score <= p->best.score))))))) return; p->best = *e; p->found = true; @@ -3094,7 +3444,8 @@ static bool dcp_m4_ext_timing_color(const void *blob, u32 size, u32 timing, /* * The timings of the sink's TimingElements @blob with an * 8-bpc RGB SDR colour element (see dcp_m4_ext_pick_color()), not - * interlaced, for its DRM connector. Replaces the previous list and starts + * interlaced, for its DRM connector; those whose only such element needs DSC + * are marked so. Replaces the previous list and starts * the sink's catalog (no PreferredTimingElements yet); not if received in * sink generation @gen, before a released route (stale). True if cached. */ @@ -3136,6 +3487,11 @@ static bool dcp_m4_ext_cache_modes(struct iomfb_m4 *dcp, const void *blob, u32 s list[kept].bootstrap = m->has_bootstrap ? m->bootstrap_color_id : 0; list[kept].fw_preferred = m->firmware_preferred; list[kept].score = m->score; + list[kept].dsc = color.dsc_required; + if (color.dsc_required) + dev_dbg(dcp->dev, "timing %u %ux%u@%d: DSC colour %u\n", + m->timing_mode_id, m->mode.hdisplay, m->mode.vdisplay, + drm_mode_vrefresh(&m->mode), (u32)color.id); kept++; } kfree(modes); @@ -3439,17 +3795,16 @@ static bool dcp_m4_ext_ids_current(struct iomfb_m4 *dcp, const struct iomfb_m4_e found = e->timing == ids->timing && e->color == ids->color && e->bootstrap == ids->bootstrap && e->mode.hdisplay == width && - e->mode.vdisplay == height && - (!capacity || (u64)e->mode.clock * 24 <= capacity); + e->mode.vdisplay == height && dcp_m4_ext_fits(e, capacity); } mutex_unlock(&dcp->ext_modes_lock); return found; } /* - * DRM: before an enable or disable, which must not be refused: a direct swap - * in flight completes (D590) or is given up (5 s, or at once when the link - * goes). The session lock is dropped while waiting, so those can run; + * DRM: before an enable or disable, which must not be refused: the direct + * swaps in flight complete (D590) or are given up (5 s, or at once when the + * link goes). The session lock is dropped while waiting, so those can run; * nothing here holds a DRM lock they need. -EBUSY if it has not after 10 s. */ static int dcp_m4_ext_settle(struct iomfb_m4 *dcp, struct dcp_m4_runtime *r, const char *what) @@ -3457,16 +3812,17 @@ static int dcp_m4_ext_settle(struct iomfb_m4 *dcp, struct dcp_m4_runtime *r, con unsigned long end = jiffies + msecs_to_jiffies(10000); lockdep_assert_held(&dcp->lock); - while (r->ext_async_id) { + while (r->ext_qn) { long left = (long)(end - jiffies); if (left <= 0) { - dev_warn(dcp->dev, "%s: a direct swap still under way after 10 s\n", what); + dev_warn(dcp->dev, "%s: %u direct swaps still under way after 10 s\n", what, + r->ext_qn); return -EBUSY; } - dev_dbg(dcp->dev, "%s waits for the direct swap in flight\n", what); + dev_dbg(dcp->dev, "%s waits for the %u direct swaps in flight\n", what, r->ext_qn); mutex_unlock(&dcp->lock); - wait_event_timeout(dcp->ext_settle_wq, !READ_ONCE(r->ext_async_id), left); + wait_event_timeout(dcp->ext_settle_wq, !READ_ONCE(r->ext_qn), left); mutex_lock(&dcp->lock); } return 0; @@ -3507,7 +3863,7 @@ int iomfb_m4_ext_enable(struct apple_dcp *owner, const struct iomfb_m4_ext_ids * /* The link and sink this enable is for, to tell if they changed meanwhile. */ link_gen = atomic_read(&r->ext_link_gen); sink_gen = atomic_read(&dcp->ext_sink_gen); - /* Never refused: a direct swap in flight ends first. */ + /* Never refused: the direct swaps in flight end first. */ ret = dcp_m4_ext_settle(dcp, r, "enable"); if (ret) goto out; @@ -3587,6 +3943,19 @@ int iomfb_m4_ext_enable(struct apple_dcp *owner, const struct iomfb_m4_ext_ids * return ret; } +/* + * Whether the synchronous swap just completed was displayed, as its D590 + * says; logged if not, for @what. + */ +static bool dcp_m4_ext_sync_shown(struct dcp_m4_runtime *r, const char *what) +{ + if (r->ext_swap_shown) + return true; + dev_dbg(r->dcp->dev, "%s: swap %u completed, not displayed: the frame on screen stays\n", + what, r->swap.id); + return false; +} + /* * A swap of surface @shown (-1: surfaceless) completed, * D590 included: that surface is the one on screen, the other one free; @@ -3607,10 +3976,10 @@ static void dcp_m4_ext_retired(struct dcp_m4_runtime *r, int shown) if (shown >= 0) r->ext_back = shown ^ 1; /* No window is on screen, nor may a given-up swap show one. */ - for (int i = 0; i < DCP_M4_EXT_WINDOWS; i++) { + for (int i = 0; i < DCP_M4_EXT_WINDOWS; i++) r->ext_win[i].on_screen = false; - r->ext_win[i].lost_id = 0; - } + for (unsigned int i = 0; i < ARRAY_SIZE(r->ext_lost_swap); i++) + r->ext_lost_swap[i].win = -1; } /* Rows of @rect from @src (@pitch) into @dst (@stride); its pixels. */ @@ -3684,7 +4053,7 @@ int iomfb_m4_ext_present(struct apple_dcp *owner, const struct iosys_map *src, u goto out; ret = -EBUSY; if ((r->ext_hold_id && !r->ext_hold_probe) || r->ext_orphan_start || - r->ext_async_id || r->ext_sleeping) + r->ext_qn || r->ext_sleeping) goto out; r->ext_hold_probe = false; /* @@ -3704,6 +4073,10 @@ int iomfb_m4_ext_present(struct apple_dcp *owner, const struct iosys_map *src, u back ^ 1, r->ext_buf_lost[0], r->ext_buf_lost[1], ret); if (ret) goto out; + /* Not displayed: no surface was freed. */ + ret = -EBUSY; + if (!dcp_m4_ext_sync_shown(r, "copy")) + goto out; dcp_m4_ext_retired(r, -1); r->ext_buf_stale[0] = true; r->ext_buf_stale[1] = true; @@ -3757,9 +4130,17 @@ int iomfb_m4_ext_present(struct apple_dcp *owner, const struct iosys_map *src, u r->ext_buf_stale[back] = true; goto out; } - dcp_m4_ext_retired(r, back); r->ext_buf_stale[back] = false; r->ext_prev_damage = cur; + /* + * Not displayed: what was on screen stays, this surface the back one; the + * other one never got this frame's damage, so it takes a whole frame. + */ + if (!dcp_m4_ext_sync_shown(r, "copy")) { + r->ext_buf_stale[back ^ 1] = true; + goto out; + } + dcp_m4_ext_retired(r, back); /* A frame shown, for the replay after a sleep. */ if (r->ext_restore_pending) WRITE_ONCE(r->ext_restore_frame, true); @@ -3811,14 +4192,20 @@ static void iomfb_m4_ext_put_work(struct work_struct *work) /* * Whether window @i may be unmapped: it maps a framebuffer that no swap shows - * (as last confirmed), may still show (a given-up one) or has in flight, and - * its unmap never failed. + * (as last confirmed), may still show (a given-up one) or has going out or in + * flight, and its unmap never failed. */ static bool dcp_m4_ext_win_idle(struct dcp_m4_runtime *r, int i) { const struct dcp_m4_ext_window *w = &r->ext_win[i]; - return w->key && !w->on_screen && !w->lost_id && !w->pinned && r->ext_async_win != i; + if (!w->key || w->on_screen || w->pinned || r->ext_swap_win == i || + dcp_m4_ext_win_lost(r, i)) + return false; + for (unsigned int j = 0; j < r->ext_qn; j++) + if (r->ext_q[j].win == i) + return false; + return true; } /* @@ -3964,66 +4351,135 @@ static int dcp_m4_ext_win_get(struct dcp_m4_runtime *r, const struct iomfb_m4_ex return pick; } +/* Take the direct swap at @i out of flight. */ +static void dcp_m4_ext_q_remove(struct dcp_m4_runtime *r, int i) +{ + r->ext_qn--; + memmove(&r->ext_q[i], &r->ext_q[i + 1], (r->ext_qn - i) * sizeof(r->ext_q[0])); + if (!r->ext_qn) + cancel_delayed_work(&r->ext_async_work); + wake_up_all(&r->dcp->ext_settle_wq); +} + /* - * The direct swap in flight is done, A407 reply and D590 in: its window is the - * one on screen, every other window and the copy surfaces retired (and those - * surfaces stale), and its flip is sent. Session lock held. + * The direct swap @e out of flight (completed or given up): its flip if it + * waited for that, and the oldest one's if that waited for an older one. */ -static void dcp_m4_ext_async_done(struct dcp_m4_runtime *r) +static void dcp_m4_ext_flip_after(struct dcp_m4_runtime *r, const struct dcp_m4_ext_inflight *e) { - int w = r->ext_async_win; + bool send = e->flip_held; + + if (r->ext_qn && r->ext_q[0].flip_held) { + r->ext_q[0].flip_held = false; + send = true; + } + if (send) + t8132_ext_drm_flip_done(r->dcp->dev); +} - if (dcp_m4_swap_take_completion(&r->swap) != 1) - dev_warn(r->dcp->dev, "direct swap %u: completion not taken\n", - r->ext_async_id); - cancel_delayed_work(&r->ext_async_work); - dcp_m4_ext_retired(r, -1); - if (w >= 0) - r->ext_win[w].on_screen = true; +/* + * What the D590 of direct swap @id (or a later one naming it) means for the + * screen, taken account of as it comes: @win its window (-1: none), @older how + * many direct swaps in flight are older. SHOWN: its window is the one on + * screen, every other window and the copy surfaces retired; the older ones + * complete as KEPT then. KEPT: its window counts as on screen too, nothing + * else is retired. UNSHOWN: what was on screen stays, and its window is free. + * The copy surfaces go stale and a hold ends either way. Session lock held. + */ +static void dcp_m4_ext_shown(struct dcp_m4_runtime *r, u32 id, int win, int older, + enum dcp_m4_ext_end end) +{ + if (end == DCP_M4_EXT_END_SHOWN) { + dcp_m4_ext_retired(r, -1); + for (int j = 0; j < older; j++) + r->ext_q[j].superseded = true; + } else { + if (end == DCP_M4_EXT_END_UNSHOWN) + dev_dbg(r->dcp->dev, "swap %u completed, not displayed: the frame on screen stays\n", + id); + if (r->ext_hold_id) + dev_info(r->dcp->dev, "a swap completed: the hold for swap %u ends\n", + r->ext_hold_id); + r->ext_hold_id = 0; + r->ext_hold_probe = false; + } + if (end != DCP_M4_EXT_END_UNSHOWN) { + if (win >= 0) + r->ext_win[win].on_screen = true; + /* A frame shown, for the replay after a sleep. */ + if (r->ext_restore_pending) + WRITE_ONCE(r->ext_restore_frame, true); + } r->ext_buf_stale[0] = true; r->ext_buf_stale[1] = true; - r->ext_async_id = 0; - r->ext_async_win = -1; - wake_up_all(&r->dcp->ext_settle_wq); - /* A frame shown, for the replay after a sleep. */ - if (r->ext_restore_pending) - WRITE_ONCE(r->ext_restore_frame, true); - t8132_ext_drm_flip_done(r->dcp->dev); } /* - * Give the direct swap in flight up, as a waiting swap would be (its window may - * still show), its link gone (@went) or its time out; its flip goes as lost. - * Session lock held. + * The direct swap in flight at @i is done, A407 reply and D590 in, and leaves + * flight; older ones not completed stay. @end as dcp_m4_ext_shown(), SHOWN + * as KEPT for one superseded; TAKEN: its D590 came before its A407 reply and + * was taken account of then. Its flip, or the next one's. Session lock held. + */ +static void dcp_m4_ext_async_done(struct dcp_m4_runtime *r, int i, enum dcp_m4_ext_end end) +{ + struct dcp_m4_ext_inflight e = r->ext_q[i]; + + if (end == DCP_M4_EXT_END_SHOWN && e.superseded) + end = DCP_M4_EXT_END_KEPT; + e.swap.completed = true; + if (dcp_m4_swap_take_completion(&e.swap) != 1) + dev_warn_ratelimited(r->dcp->dev, "direct swap %u: completion not taken\n", + e.swap.id); + if (i) + dev_warn_ratelimited(r->dcp->dev, "swap %u completed before the older swap %u, which stays in flight\n", + e.swap.id, r->ext_q[0].swap.id); + dcp_m4_ext_q_remove(r, i); + if (end != DCP_M4_EXT_END_TAKEN) + dcp_m4_ext_shown(r, e.swap.id, e.win, i, end); + dcp_m4_ext_flip_after(r, &e); +} + +/* Whether the oldest direct swap in flight is due its give-up, and its link went (@went). */ +static bool dcp_m4_ext_async_due(struct dcp_m4_runtime *r, bool *went) +{ + const struct dcp_m4_ext_inflight *e = &r->ext_q[0]; + + *went = !r->ext_connected || atomic_read(&r->ext_link_gen) != e->gen; + return *went || !time_before(jiffies, e->deadline); +} + +/* + * Give the oldest direct swap in flight up, as a waiting swap would be (its + * window may still show), its link gone (@went) or its time out; its flip goes + * as lost (or the next one's, dcp_m4_ext_flip_after()). Session lock held. */ static void dcp_m4_ext_async_expire(struct dcp_m4_runtime *r, bool went) { struct iomfb_m4 *dcp = r->dcp; - u32 id = r->ext_async_id; - int w = r->ext_async_win; + struct dcp_m4_ext_inflight e = r->ext_q[0]; int ret = went ? -ENOLINK : -ETIMEDOUT; r->ext_swap_surface = -1; - if (!READ_ONCE(dcp->error) && dcp_m4_ext_swap_give_up(r, ret)) { - if (w >= 0) - r->ext_win[w].lost_id = id; - } else if (!READ_ONCE(dcp->error)) { + /* The copy surfaces never got this frame: whole frames, whatever its late D590 says. */ + r->ext_buf_stale[0] = true; + r->ext_buf_stale[1] = true; + if (!READ_ONCE(dcp->error) && + !dcp_m4_ext_swap_give_up(r, &e.swap, e.win, e.superseded, ret)) { dcp_m4_swap_fail(&r->swap, ret); dcp_m4_transaction_fail(&dcp->transaction, ret); dcp_m4_failed(dcp, ret); } - r->ext_async_id = 0; - r->ext_async_win = -1; - wake_up_all(&dcp->ext_settle_wq); - t8132_ext_drm_flip_done(dcp->dev); + dcp_m4_ext_q_remove(r, 0); + dcp_m4_ext_flip_after(r, &e); } /* - * The direct swap in flight at its deadline, or after its link went (D575(0), - * a released route): words already received are taken first (its D590 among - * them maybe); then it is given up as a waiting swap would be - * (dcp_m4_ext_swap_give_up(): its window may still show, and is kept), and - * its flip is sent as lost. + * The oldest direct swap in flight at its deadline, or after its link went + * (D575(0), a released route): words already received are taken first (its + * D590 among them maybe); then each one due is given up as a waiting swap + * would be (dcp_m4_ext_swap_give_up(): its window may still show, and is + * kept), and its flip is sent as lost. The work comes back at the next one's + * deadline. */ static void dcp_m4_ext_async_work(struct work_struct *work) { @@ -4034,21 +4490,20 @@ static void dcp_m4_ext_async_work(struct work_struct *work) int ret; mutex_lock(&dcp->lock); - if (!r->ext_async_id) - goto out; - went = !r->ext_connected || atomic_read(&r->ext_link_gen) != r->ext_async_gen; - if (!went && time_before(jiffies, r->ext_async_deadline)) { - queue_delayed_work(r->wq, &r->ext_async_work, r->ext_async_deadline - jiffies); - goto out; + if (r->ext_qn && dcp_m4_ext_async_due(r, &went)) { + ret = READ_ONCE(dcp->error) ?: dcp_m4_runtime_drain(r, 32); + if (ret < 0) { + dcp_m4_transaction_fail(&dcp->transaction, ret); + dcp_m4_failed(dcp, ret); + } + while (r->ext_qn && dcp_m4_ext_async_due(r, &went)) + dcp_m4_ext_async_expire(r, went); } - ret = READ_ONCE(dcp->error) ?: dcp_m4_runtime_drain(r, 32); - if (ret < 0) { - dcp_m4_transaction_fail(&dcp->transaction, ret); - dcp_m4_failed(dcp, ret); + if (r->ext_qn) { + long left = (long)(r->ext_q[0].deadline - jiffies); + + queue_delayed_work(r->wq, &r->ext_async_work, max(left, 0L)); } - if (r->ext_async_id) - dcp_m4_ext_async_expire(r, went); -out: mutex_unlock(&dcp->lock); } @@ -4056,10 +4511,13 @@ static void dcp_m4_ext_async_work(struct work_struct *work) * One DRM frame scanned out directly * from its framebuffer, mapped into a SID0 host window (kept per * framebuffer, see dcp_m4_ext_win_get()), with a one-surface swap that - * returns after its A407 reply; the flip is sent with its D590, or as lost - * when it is given up. One swap in flight: -EBUSY while one is, checked - * before any A406. -ENOSPC without a window, -EOPNOTSUPP for a framebuffer - * that does not fit the mode or the window: the caller copies it instead. + * returns after its A407 reply. Up to t8132_ext_swap_depth swaps in flight: + * -EBUSY while that many are, checked before any A406. 1 once submitted with + * none older in flight and a depth over 1: its flip now; 0 once submitted + * otherwise: its flip is sent with the older one's completion, or its own + * (D590), or as lost when that is given up. -ENOSPC without a window, + * -EOPNOTSUPP for a framebuffer that does not fit the mode or the window: the + * caller copies it instead. */ int iomfb_m4_ext_present_direct(struct apple_dcp *owner, const struct iomfb_m4_ext_fb *fb) { @@ -4067,6 +4525,7 @@ int iomfb_m4_ext_present_direct(struct apple_dcp *owner, const struct iomfb_m4_e struct dcp_m4_runtime *r; struct dcp_m4_frame f; int ret = -ENOLINK, w; + unsigned int depth, free; size_t need; if (!dcp || !dcp->external) @@ -4082,9 +4541,23 @@ int iomfb_m4_ext_present_direct(struct apple_dcp *owner, const struct iomfb_m4_e if (r->ext_mode_stale) goto out; ret = -EBUSY; + depth = clamp_val(READ_ONCE(t8132_ext_swap_depth), 1, DCP_M4_EXT_INFLIGHT); if ((r->ext_hold_id && !r->ext_hold_probe) || r->ext_orphan_start || - r->ext_async_id || r->ext_sleeping) + r->ext_qn >= depth || r->ext_sleeping) goto out; + /* + * A slot for each swap in flight, this one included, that a later D590 + * may complete (dcp_m4_runtime_swap_done()); else frames are copied. + */ + ret = -ENOSPC; + free = 0; + for (unsigned int i = 0; i < ARRAY_SIZE(r->ext_early); i++) + free += !r->ext_early[i]; + if (free <= r->ext_qn) { + dev_warn_ratelimited(dcp->dev, "%u swaps completed by a later D590 still without their own: frames copied\n", + (unsigned int)ARRAY_SIZE(r->ext_early) - free); + goto out; + } ret = -EOPNOTSUPP; need = PAGE_ALIGN((size_t)fb->pitch * fb->height); if (fb->width != r->ext_width || fb->height != r->ext_height || @@ -4119,12 +4592,14 @@ int iomfb_m4_ext_present_direct(struct apple_dcp *owner, const struct iomfb_m4_e }; r->ext_hold_probe = false; r->ext_swap_surface = -1; - r->ext_async_win = w; - r->ext_swap_async = true; + r->ext_swap_win = w; + r->ext_swap_async = depth; + r->ext_swap_now = false; ret = dcp_m4_runtime_swap_full(r, true, &f, NULL, 0, false, 0, 0, 0); - r->ext_swap_async = false; - if (ret && !r->ext_async_id) - r->ext_async_win = -1; + r->ext_swap_async = 0; + r->ext_swap_win = -1; + if (!ret && r->ext_swap_now) + ret = 1; out: mutex_unlock(&dcp->lock); return ret; @@ -4150,7 +4625,7 @@ int iomfb_m4_ext_blank(struct apple_dcp *owner) mutex_unlock(&dcp->lock); return 0; } - /* Never refused: a direct swap in flight ends first. */ + /* Never refused: the direct swaps in flight end first. */ /* Pairs with the release store of r->done in startup. */ if (r && smp_load_acquire(&r->done)) { int link_gen = atomic_read(&r->ext_link_gen); @@ -4179,7 +4654,8 @@ int iomfb_m4_ext_blank(struct apple_dcp *owner) dcp_m4_runtime_swap_full(r, true, NULL, NULL, 0, false, 0, 0, 0); if (ret == -EBUSY) dev_info(dcp->dev, "disable: swaps held or a start orphaned: the last frame stays\n"); - if (!ret) { + /* Only a displayed blank takes the windows off screen. */ + if (!ret && dcp_m4_ext_sync_shown(r, "disable")) { dcp_m4_ext_retired(r, -1); /* The windows are off screen: let them go. */ if (dcp_m4_ext_win_release_idle(r, 0)) @@ -4223,12 +4699,12 @@ bool iomfb_m4_ext_sleep_mark(struct apple_dcp *owner) /* * PM_SUSPEND_PREPARE, before the tasks freeze, every worker running, after - * iomfb_m4_ext_sleep_mark(): the display down for the sleep: the swap in flight - * settles (its D590, or its give-up); a surfaceless swap; every window let go - * of; the display powered off (A473 0) and its firmware's D572(0) waited for - * (1 s), so that the DCP's suspend finds the display DART off, else it refuses - * the sleep. The DRM state replayed after the sleep powers on and sets the - * mode from scratch. + * iomfb_m4_ext_sleep_mark(): the display down for the sleep: the swaps in + * flight settle (their D590, or their give-up); a surfaceless swap; every + * window let go of; the display powered off (A473 0) and its firmware's + * D572(0) waited for (1 s), so that the DCP's suspend finds the display DART + * off, else it refuses the sleep. The DRM state replayed after the sleep + * powers on and sets the mode from scratch. */ void iomfb_m4_ext_sleep_down(struct apple_dcp *owner) { @@ -4246,9 +4722,10 @@ void iomfb_m4_ext_sleep_down(struct apple_dcp *owner) mutex_unlock(&dcp->lock); return; } - dev_dbg(dcp->dev, "sleep: display going down (display DART %s, swap in flight %u)\n", - READ_ONCE(dcp->ext_dart_down) ? "off" : "on", r->ext_async_id); - /* The workers run: the swap in flight completes or is given up as ever. */ + dev_dbg(dcp->dev, "sleep: display going down (display DART %s, %u swaps in flight, oldest %u)\n", + READ_ONCE(dcp->ext_dart_down) ? "off" : "on", r->ext_qn, + r->ext_qn ? r->ext_q[0].swap.id : 0); + /* The workers run: the swaps in flight complete or are given up as ever. */ ret = dcp_m4_ext_settle(dcp, r, "sleep"); if (!ret && dcp_m4_ext_route_ready(dcp, r) && READ_ONCE(r->ext_cur_timing)) { r->ext_swap_surface = -1; @@ -4256,7 +4733,8 @@ void iomfb_m4_ext_sleep_down(struct apple_dcp *owner) dcp_m4_runtime_swap_full(r, true, NULL, NULL, 0, false, 0, 0, 0); if (ret) dev_warn(dcp->dev, "sleep: surfaceless swap failed: %d\n", ret); - if (!ret) { + /* Only a displayed blank takes the windows off screen. */ + if (!ret && dcp_m4_ext_sync_shown(r, "sleep")) { dcp_m4_ext_retired(r, -1); for (int i = 0; i < DCP_M4_EXT_WINDOWS; i++) mapped += !!r->ext_win[i].key; @@ -4462,7 +4940,7 @@ int iomfb_m4_suspend(struct apple_dcp *owner) mutex_lock(&dcp->lock); if (!READ_ONCE(dcp->ext_dart_down)) why = "the display DART is on (last D572 was 1)"; - else if (dcp->runtime->ext_async_id) + else if (dcp->runtime->ext_qn) why = "a swap is under way"; mutex_unlock(&dcp->lock); if (why) { diff --git a/drivers/gpu/drm/apple/iomfb_m4.h b/drivers/gpu/drm/apple/iomfb_m4.h index f76966b88db41..c45d737b1d726 100644 --- a/drivers/gpu/drm/apple/iomfb_m4.h +++ b/drivers/gpu/drm/apple/iomfb_m4.h @@ -59,7 +59,7 @@ int iomfb_m4_ext_enable(struct apple_dcp *dcp, const struct iomfb_m4_ext_ids *id int iomfb_m4_ext_present(struct apple_dcp *dcp, const struct iosys_map *src, u32 pitch, const struct drm_rect *damage); int iomfb_m4_ext_blank(struct apple_dcp *dcp); -/* 0 once submitted; the flip comes with its D590. */ +/* 0 once submitted, its flip later (t8132_ext_drm_flip_done()); 1: its flip now. */ int iomfb_m4_ext_present_direct(struct apple_dcp *dcp, const struct iomfb_m4_ext_fb *fb); /* Why an external DCP's display refuses system sleep, or NULL. */ const char *iomfb_m4_ext_sleep_veto(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 037c0f4e9ba99..6e48211d69f53 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -2296,7 +2296,7 @@ static int parse_color_element(struct dcp_parse_ctx *handle, struct dcp_color_el *e = (struct dcp_color_element) { .id = -1, .depth = -1, .range = -1, .eotf = -1, .encoding = -1, - .colorimetry = -1, .score = -1, .is_virtual = true, .restricted = false, + .colorimetry = -1, .score = -1, .is_virtual = true, }; if (ret) return ret; @@ -2354,31 +2354,32 @@ static int parse_id_list(struct dcp_parse_ctx *handle, s64 *ids, unsigned int *c /* * Each colour element (virtual ones too) of the timing @timing_id in a - * TimingElements catalog, flagged if the timing lists it as unsafe or as - * needing DSC; whether the timing is interlaced. A malformed timing entry is - * skipped. -ENOENT if the catalog has no such timing. + * TimingElements catalog, flagged if the timing lists it as unsafe and if it + * lists it as needing DSC; whether the timing is interlaced. A malformed + * timing entry is skipped. -ENOENT if the catalog has no such timing. */ int dcp_parse_timing_colors(const void *blob, size_t len, u32 timing_id, dcp_color_element_fn emit, void *data, bool *interlaced) { struct dcp_parse_ctx ctx; struct iterator timings; - s64 *restricted; + s64 *unsafe, *dsc; int ret = parse(blob, len, &ctx); if (!ret) ret = iterator_begin(&ctx, &timings, false); if (ret) return ret; - restricted = kcalloc(DCP_TIMING_RESTRICTED_MAX, sizeof(*restricted), GFP_KERNEL); - if (!restricted) + unsafe = kcalloc(2 * DCP_TIMING_RESTRICTED_MAX, sizeof(*unsafe), GFP_KERNEL); + if (!unsafe) return -ENOMEM; + dsc = unsafe + DCP_TIMING_RESTRICTED_MAX; ret = -ENOENT; for (; timings.idx < timings.len; ++timings.idx) { struct dcp_parse_ctx entry_start = ctx, colors = {}; struct iterator entry, it; bool match = false, have_colors = false, is_interlaced = false; - unsigned int nr_restricted = 0; + unsigned int nr_unsafe = 0, nr_dsc = 0; int err; /* ColorModes may come before the ID: find both, then walk the colours. */ @@ -2398,9 +2399,10 @@ int dcp_parse_timing_colors(const void *blob, size_t len, u32 timing_id, colors = ctx; have_colors = true; err = skip(&ctx); - } else if (!strcmp(key, "UnsafeColorElementIDs") || - !strcmp(key, "DSCRequiredColorElementIDs")) { - err = parse_id_list(&ctx, restricted, &nr_restricted); + } else if (!strcmp(key, "UnsafeColorElementIDs")) { + err = parse_id_list(&ctx, unsafe, &nr_unsafe); + } else if (!strcmp(key, "DSCRequiredColorElementIDs")) { + err = parse_id_list(&ctx, dsc, &nr_dsc); } else if (!strcmp(key, "IsInterlaced")) { err = parse_bool(&ctx, &is_interlaced); } else { @@ -2431,14 +2433,16 @@ int dcp_parse_timing_colors(const void *blob, size_t len, u32 timing_id, ret = parse_color_element(it.handle, &e); if (ret) break; - for (unsigned int i = 0; i < nr_restricted; i++) - e.restricted |= restricted[i] == e.id; + for (unsigned int i = 0; i < nr_unsafe; i++) + e.unsafe |= unsafe[i] == e.id; + for (unsigned int i = 0; i < nr_dsc; i++) + e.dsc_required |= dsc[i] == e.id; emit(data, &e); } goto out; } out: - kfree(restricted); + kfree(unsafe); return ret; } diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index ef2424a2bdaa3..3fa384ecf68c4 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -272,8 +272,8 @@ int parse_system_log_mnits_pairs(const void *blob, size_t size, struct dcp_color_element { s64 id, depth, range, eotf, encoding, colorimetry, score; bool is_virtual; - /* The timing lists it as unsafe or as needing DSC (timing walks only). */ - bool restricted; + /* The timing lists it as unsafe, or as needing DSC (timing walks only). */ + bool unsafe, dsc_required; }; typedef void (*dcp_color_element_fn)(void *data, const struct dcp_color_element *element); diff --git a/drivers/gpu/drm/apple/t8132_ext_drm.c b/drivers/gpu/drm/apple/t8132_ext_drm.c index 6d35ea3ada2d2..f6cc2f7767190 100644 --- a/drivers/gpu/drm/apple/t8132_ext_drm.c +++ b/drivers/gpu/drm/apple/t8132_ext_drm.c @@ -73,7 +73,10 @@ struct t8132_ext_out { struct drm_connector connector; struct platform_device *pdev; struct work_struct hotplug_work; - /* The held flip event, sent with its swap's D590 or later (drm->event_lock) */ + /* + * The held flip event, sent once its frame is on its way (see + * t8132_ext_crtc_flush()) or later (drm->event_lock) + */ struct drm_pending_vblank_event *event; struct delayed_work flip_work; /* The DRM device suspends before this DCP and resumes after it */ @@ -340,9 +343,10 @@ static void t8132_ext_direct_refused(struct t8132_ext_out *out, struct drm_frame } /* - * The frame scanned out directly from @fb: 2 once its swap is - * submitted (the flip comes with its D590), -ENOSPC or -EOPNOTSUPP to copy - * it instead, else the error (the frame lost). A linear XRGB8888 or + * The frame scanned out directly from @fb: 3 once its swap is submitted + * with none older in flight (its flip now), 2 once submitted otherwise (its + * flip comes later, t8132_ext_drm_flip_done()), -ENOSPC or -EOPNOTSUPP to + * copy it instead, else the error (the frame lost). A linear XRGB8888 or * ARGB8888 GEM DMA buffer, own or imported, from its first byte, at the * mode's size, the pitch a multiple of 64 bytes, at most 32 MiB, and every * 16 KiB of it one 16 KiB page (t8132_ext_fb_pages()). @@ -406,15 +410,16 @@ static int t8132_ext_present_direct(struct t8132_ext_out *out, struct apple_dcp ret = iomfb_m4_ext_present_direct(owner, &d); if (ret == -EOPNOTSUPP) t8132_ext_direct_refused(out, fb, t8132_ext_pages_refused); - return ret ?: 2; + return ret < 0 ? ret : ret ? 3 : 2; } /* * Present the primary plane's new frame: scanned out directly where it can - * be, else copied into the DCP and swapped in; 2 once a direct frame's swap - * is submitted, 1 if the commit has no new frame (no plane state), so - * nothing was tried. A copied framebuffer is read through its kernel - * mapping (dma-buf vmap for an imported one) inside CPU access. + * be, else copied into the DCP and swapped in; 2 or 3 once a direct frame's + * swap is submitted (see t8132_ext_present_direct()), 1 if the commit has no + * new frame (no plane state), so nothing was tried. A copied framebuffer is + * read through its kernel mapping (dma-buf vmap for an imported one) inside + * CPU access. */ static int t8132_ext_present(struct t8132_ext_out *out, struct drm_atomic_commit *state) { @@ -503,7 +508,7 @@ static void t8132_ext_crtc_flush(struct drm_crtc *crtc, struct drm_atomic_commit /* Its damage is lost with it: whole frames until one is shown. */ if (ret <= 0) out->frame_lost = !!ret; - if (fb && !out->first_frame && (ret <= 0 || ret == 2)) { + if (fb && !out->first_frame && (ret <= 0 || ret >= 2)) { out->first_frame = true; dev_dbg(&out->pdev->dev, "%s: first frame %ux%u %p4cc, modifier %#llx: %d\n", crtc->name, fb->width, fb->height, &fb->format->format, fb->modifier, ret); @@ -512,9 +517,10 @@ static void t8132_ext_crtc_flush(struct drm_crtc *crtc, struct drm_atomic_commit crtc->name, ret); } /* - * A copied frame's swap has completed (D590): its flip now, else a - * frame later. A direct frame's comes with its D590 (or when its swap - * is given up). + * A copied frame's swap has completed (D590), or a direct one went out + * with none older in flight (3): its flip now, else a frame later. Any + * other direct frame's (2) comes when the older swap in flight + * completes, or its own at a depth of 1 (or when that is given up). */ if (event && ret != 2) { /* Torn down: no work queued again, the event now. */ @@ -709,10 +715,12 @@ bool t8132_ext_drm_in_commit(struct drm_atomic_commit *state) /* * Every flip of these CRTCs in @state, before its old planes are cleaned up: - * a frame's flip comes with its swap's D590, or when the swap is given up - * (5 s, or at once when the link goes), so 6 s bound it. Until then the - * old framebuffer may still be scanned out: blocking commits return, and - * DRM lets go of it, only after. + * a frame's flip comes once its swap is submitted, or with the completion of + * the older swap in flight (or its own), or when that swap is given up (5 s, + * or at once when the link goes), so 6 s bound it. Blocking commits return, + * and DRM lets go of the old framebuffer, only after; a framebuffer on screen + * or in flight keeps a reference of its window's (iomfb_m4.c) until no swap + * shows or holds it. */ void t8132_ext_drm_wait_flips(struct drm_atomic_commit *state) { @@ -734,8 +742,9 @@ void t8132_ext_drm_wait_flips(struct drm_atomic_commit *state) } /* - * The swap of @dcpext's last direct frame completed (D590) or was - * given up: its held flip event now. Any context but hard IRQ. + * @dcpext's last direct frame is due its flip (the older swap in flight, or + * its own, completed or was given up): its held flip event now. Any context + * but hard IRQ. */ void t8132_ext_drm_flip_done(struct device *dcpext) {