sync with OpenBSD -current

This commit is contained in:
purplerain 2024-01-23 02:06:39 +00:00
parent f913a3fe74
commit 85b7ec3495
Signed by: purplerain
GPG key ID: F42C07F07E2E35B7
58 changed files with 10776 additions and 147 deletions

989
sys/dev/pci/drm/apple/afk.c Normal file
View file

@ -0,0 +1,989 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/soc/apple/rtkit.h>
#include "afk.h"
#include "trace.h"
struct afk_receive_message_work {
struct apple_dcp_afkep *ep;
u64 message;
struct work_struct work;
};
#define RBEP_TYPE GENMASK(63, 48)
enum rbep_msg_type {
RBEP_INIT = 0x80,
RBEP_INIT_ACK = 0xa0,
RBEP_GETBUF = 0x89,
RBEP_GETBUF_ACK = 0xa1,
RBEP_INIT_TX = 0x8a,
RBEP_INIT_RX = 0x8b,
RBEP_START = 0xa3,
RBEP_START_ACK = 0x86,
RBEP_SEND = 0xa2,
RBEP_RECV = 0x85,
RBEP_SHUTDOWN = 0xc0,
RBEP_SHUTDOWN_ACK = 0xc1,
};
#define BLOCK_SHIFT 6
#define GETBUF_SIZE GENMASK(31, 16)
#define GETBUF_TAG GENMASK(15, 0)
#define GETBUF_ACK_DVA GENMASK(47, 0)
#define INITRB_OFFSET GENMASK(47, 32)
#define INITRB_SIZE GENMASK(31, 16)
#define INITRB_TAG GENMASK(15, 0)
#define SEND_WPTR GENMASK(31, 0)
static void afk_send(struct apple_dcp_afkep *ep, u64 message)
{
dcp_send_message(ep->dcp, ep->endpoint, message);
}
struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
const struct apple_epic_service_ops *ops)
{
struct apple_dcp_afkep *afkep;
int ret;
afkep = devm_kzalloc(dcp->dev, sizeof(*afkep), GFP_KERNEL);
if (!afkep)
return ERR_PTR(-ENOMEM);
afkep->ops = ops;
afkep->dcp = dcp;
afkep->endpoint = endpoint;
afkep->wq = alloc_ordered_workqueue("apple-dcp-afkep%02x",
WQ_MEM_RECLAIM, endpoint);
if (!afkep->wq) {
ret = -ENOMEM;
goto out_free_afkep;
}
// TODO: devm_ for wq
init_completion(&afkep->started);
init_completion(&afkep->stopped);
mtx_init(&afkep->lock, IPL_TTY);
return afkep;
out_free_afkep:
devm_kfree(dcp->dev, afkep);
return ERR_PTR(ret);
}
int afk_start(struct apple_dcp_afkep *ep)
{
int ret;
reinit_completion(&ep->started);
apple_rtkit_start_ep(ep->dcp->rtk, ep->endpoint);
afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_INIT));
ret = wait_for_completion_timeout(&ep->started, msecs_to_jiffies(1000));
if (ret <= 0)
return -ETIMEDOUT;
else
return 0;
}
static void afk_getbuf(struct apple_dcp_afkep *ep, u64 message)
{
u16 size = FIELD_GET(GETBUF_SIZE, message) << BLOCK_SHIFT;
u16 tag = FIELD_GET(GETBUF_TAG, message);
u64 reply;
trace_afk_getbuf(ep, size, tag);
if (ep->bfr) {
dev_err(ep->dcp->dev,
"Got GETBUF message but buffer already exists\n");
return;
}
ep->bfr = dmam_alloc_coherent(ep->dcp->dev, size, &ep->bfr_dma,
GFP_KERNEL);
if (!ep->bfr) {
dev_err(ep->dcp->dev, "Failed to allocate %d bytes buffer\n",
size);
return;
}
ep->bfr_size = size;
ep->bfr_tag = tag;
reply = FIELD_PREP(RBEP_TYPE, RBEP_GETBUF_ACK);
reply |= FIELD_PREP(GETBUF_ACK_DVA, ep->bfr_dma);
afk_send(ep, reply);
}
static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
struct afk_ringbuffer *bfr)
{
u16 base = FIELD_GET(INITRB_OFFSET, message) << BLOCK_SHIFT;
u16 size = FIELD_GET(INITRB_SIZE, message) << BLOCK_SHIFT;
u16 tag = FIELD_GET(INITRB_TAG, message);
u32 bufsz, end;
if (tag != ep->bfr_tag) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x",
ep->endpoint, ep->bfr_tag, tag);
return;
}
if (bfr->ready) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: buffer is already initialized\n",
ep->endpoint);
return;
}
if (base >= ep->bfr_size) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx",
ep->endpoint, base, ep->bfr_size);
return;
}
end = base + size;
if (end > ep->bfr_size) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: requested end 0x%x > max size 0x%lx",
ep->endpoint, end, ep->bfr_size);
return;
}
bfr->hdr = ep->bfr + base;
bufsz = le32_to_cpu(bfr->hdr->bufsz);
if (bufsz + sizeof(*bfr->hdr) != size) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx",
ep->endpoint, bufsz, sizeof(*bfr->hdr));
return;
}
bfr->buf = bfr->hdr + 1;
bfr->bufsz = bufsz;
bfr->ready = true;
if (ep->rxbfr.ready && ep->txbfr.ready)
afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_START));
}
static const struct apple_epic_service_ops *
afk_match_service(struct apple_dcp_afkep *ep, const char *name)
{
const struct apple_epic_service_ops *ops;
if (!name[0])
return NULL;
if (!ep->ops)
return NULL;
for (ops = ep->ops; ops->name[0]; ops++) {
if (strcmp(ops->name, name))
continue;
return ops;
}
return NULL;
}
static struct apple_epic_service *afk_epic_find_service(struct apple_dcp_afkep *ep,
u32 channel)
{
for (u32 i = 0; i < ep->num_channels; i++)
if (ep->services[i].enabled && ep->services[i].channel == channel)
return &ep->services[i];
return NULL;
}
static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
u8 *payload, size_t payload_size)
{
char name[32];
s64 epic_unit = -1;
u32 ch_idx;
const char *service_name = name;
const char *epic_name = NULL, *epic_class = NULL;
const struct apple_epic_service_ops *ops;
struct dcp_parse_ctx ctx;
u8 *props = payload + sizeof(name);
size_t props_size = payload_size - sizeof(name);
WARN_ON(afk_epic_find_service(ep, channel));
if (payload_size < sizeof(name)) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n",
ep->endpoint, payload_size);
return;
}
if (ep->num_channels >= AFK_MAX_CHANNEL) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: too many enabled services!\n",
ep->endpoint);
return;
}
strlcpy(name, payload, sizeof(name));
/*
* in DCP firmware 13.2 DCP reports interface-name as name which starts
* with "dispext%d" using -1 s ID for "dcp". In the 12.3 firmware
* EPICProviderClass was used. If the init call has props parse them and
* use EPICProviderClass to match the service.
*/
if (props_size > 36) {
int ret = parse(props, props_size, &ctx);
if (ret) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: Failed to parse service init props for %s\n",
ep->endpoint, name);
return;
}
ret = parse_epic_service_init(&ctx, &epic_name, &epic_class, &epic_unit);
if (ret) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: failed to extract init props: %d\n",
ep->endpoint, ret);
return;
}
service_name = epic_class;
} else {
service_name = name;
}
ops = afk_match_service(ep, service_name);
if (!ops) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: unable to match service %s on channel %d\n",
ep->endpoint, service_name, channel);
goto free;
}
ch_idx = ep->num_channels++;
mtx_init(&ep->services[ch_idx].lock, IPL_TTY);
ep->services[ch_idx].enabled = true;
ep->services[ch_idx].ops = ops;
ep->services[ch_idx].ep = ep;
ep->services[ch_idx].channel = channel;
ep->services[ch_idx].cmd_tag = 0;
ops->init(&ep->services[ch_idx], epic_name, epic_class, epic_unit);
dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n",
ep->endpoint, service_name, channel);
free:
kfree(epic_name);
kfree(epic_class);
}
static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel)
{
struct apple_epic_service *service;
const struct apple_epic_service_ops *ops;
unsigned long flags;
service = afk_epic_find_service(ep, channel);
if (!service) {
dev_warn(ep->dcp->dev, "AFK[ep:%02x]: teardown for disabled channel %u\n",
ep->endpoint, channel);
return;
}
// TODO: think through what locking is necessary
spin_lock_irqsave(&service->lock, flags);
service->enabled = false;
ops = service->ops;
spin_unlock_irqrestore(&service->lock, flags);
if (ops->teardown)
ops->teardown(service);
}
static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel,
u16 tag, void *payload, size_t payload_size)
{
struct epic_cmd *cmd = payload;
struct apple_epic_service *service;
unsigned long flags;
u8 idx = tag & 0xff;
void *rxbuf, *txbuf;
dma_addr_t rxbuf_dma, txbuf_dma;
size_t rxlen, txlen;
service = afk_epic_find_service(ep, channel);
if (!service) {
dev_warn(ep->dcp->dev, "AFK[ep:%02x]: command reply on disabled channel %u\n",
ep->endpoint, channel);
return;
}
if (payload_size < sizeof(*cmd)) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: command reply on channel %d too small: %ld\n",
ep->endpoint, channel, payload_size);
return;
}
if (idx >= MAX_PENDING_CMDS) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: command reply on channel %d out of range: %d\n",
ep->endpoint, channel, idx);
return;
}
spin_lock_irqsave(&service->lock, flags);
if (service->cmds[idx].done) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: command reply on channel %d already handled\n",
ep->endpoint, channel);
spin_unlock_irqrestore(&service->lock, flags);
return;
}
if (tag != service->cmds[idx].tag) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: command reply on channel %d has invalid tag: expected 0x%04x != 0x%04x\n",
ep->endpoint, channel, tag, service->cmds[idx].tag);
spin_unlock_irqrestore(&service->lock, flags);
return;
}
service->cmds[idx].done = true;
service->cmds[idx].retcode = le32_to_cpu(cmd->retcode);
if (service->cmds[idx].free_on_ack) {
/* defer freeing until we're no longer in atomic context */
rxbuf = service->cmds[idx].rxbuf;
txbuf = service->cmds[idx].txbuf;
rxlen = service->cmds[idx].rxlen;
txlen = service->cmds[idx].txlen;
rxbuf_dma = service->cmds[idx].rxbuf_dma;
txbuf_dma = service->cmds[idx].txbuf_dma;
bitmap_release_region(service->cmd_map, idx, 0);
} else {
rxbuf = txbuf = NULL;
rxlen = txlen = 0;
}
if (service->cmds[idx].completion)
complete(service->cmds[idx].completion);
spin_unlock_irqrestore(&service->lock, flags);
if (rxbuf && rxlen)
dma_free_coherent(ep->dcp->dev, rxlen, rxbuf, rxbuf_dma);
if (txbuf && txlen)
dma_free_coherent(ep->dcp->dev, txlen, txbuf, txbuf_dma);
}
struct epic_std_service_ap_call {
__le32 unk0;
__le32 unk1;
__le32 type;
__le32 len;
__le32 magic;
u8 _unk[48];
} __attribute__((packed));
static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel,
u32 type, struct epic_hdr *ehdr,
struct epic_sub_hdr *eshdr,
void *payload, size_t payload_size)
{
struct apple_epic_service *service = afk_epic_find_service(ep, channel);
if (!service) {
dev_warn(ep->dcp->dev,
"AFK[ep:%02x]: std service notify on disabled channel %u\n",
ep->endpoint, channel);
return;
}
if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) {
struct epic_std_service_ap_call *call = payload;
size_t call_size;
void *reply;
int ret;
if (payload_size < sizeof(*call))
return;
call_size = le32_to_cpu(call->len);
if (payload_size < sizeof(*call) + call_size)
return;
if (!service->ops->call)
return;
reply = kzalloc(payload_size, GFP_KERNEL);
if (!reply)
return;
ret = service->ops->call(service, le32_to_cpu(call->type),
payload + sizeof(*call), call_size,
reply + sizeof(*call), call_size);
if (ret) {
kfree(reply);
return;
}
memcpy(reply, call, sizeof(*call));
afk_send_epic(ep, channel, le16_to_cpu(eshdr->tag),
EPIC_TYPE_NOTIFY_ACK, EPIC_CAT_REPLY,
EPIC_SUBTYPE_STD_SERVICE, reply, payload_size);
kfree(reply);
return;
}
if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) {
if (service->ops->report)
service->ops->report(service, le16_to_cpu(eshdr->type),
payload, payload_size);
return;
}
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: channel %d received unhandled standard service message: %x / %x\n",
ep->endpoint, channel, type, eshdr->category);
print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload,
payload_size, true);
}
static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
u8 *data, size_t data_size)
{
struct apple_epic_service *service;
struct epic_hdr *ehdr = (struct epic_hdr *)data;
struct epic_sub_hdr *eshdr =
(struct epic_sub_hdr *)(data + sizeof(*ehdr));
u16 subtype = le16_to_cpu(eshdr->type);
u8 *payload = data + sizeof(*ehdr) + sizeof(*eshdr);
size_t payload_size;
if (data_size < sizeof(*ehdr) + sizeof(*eshdr)) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n",
ep->endpoint, data_size);
return;
}
payload_size = data_size - sizeof(*ehdr) - sizeof(*eshdr);
trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr);
service = afk_epic_find_service(ep, channel);
if (!service) {
if (type != EPIC_TYPE_NOTIFY && type != EPIC_TYPE_REPLY) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n",
ep->endpoint, type, channel);
return;
}
if (eshdr->category != EPIC_CAT_REPORT) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: expected report but got 0x%x on channel %d\n",
ep->endpoint, eshdr->category, channel);
return;
}
if (subtype == EPIC_SUBTYPE_TEARDOWN) {
dev_dbg(ep->dcp->dev,
"AFK[ep:%02x]: teardown without service on channel %d\n",
ep->endpoint, channel);
return;
}
if (subtype != EPIC_SUBTYPE_ANNOUNCE) {
dev_err(ep->dcp->dev,
"AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n",
ep->endpoint, subtype, channel);
return;
}
return afk_recv_handle_init(ep, channel, payload, payload_size);
}
if (!service) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n",
ep->endpoint, channel);
return;
}
if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT &&
subtype == EPIC_SUBTYPE_TEARDOWN)
return afk_recv_handle_teardown(ep, channel);
if (type == EPIC_TYPE_REPLY && eshdr->category == EPIC_CAT_REPLY)
return afk_recv_handle_reply(ep, channel,
le16_to_cpu(eshdr->tag), payload,
payload_size);
if (subtype == EPIC_SUBTYPE_STD_SERVICE)
return afk_recv_handle_std_service(
ep, channel, type, ehdr, eshdr, payload, payload_size);
dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d received unhandled message "
"(type %x subtype %x)\n", ep->endpoint, channel, type, subtype);
print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload,
payload_size, true);
}
static bool afk_recv(struct apple_dcp_afkep *ep)
{
struct afk_qe *hdr;
u32 rptr, wptr;
u32 magic, size, channel, type;
if (!ep->rxbfr.ready) {
dev_err(ep->dcp->dev, "AFK[ep:%02x]: got RECV but not ready\n",
ep->endpoint);
return false;
}
rptr = le32_to_cpu(ep->rxbfr.hdr->rptr);
wptr = le32_to_cpu(ep->rxbfr.hdr->wptr);
trace_afk_recv_rwptr_pre(ep, rptr, wptr);
if (rptr == wptr)
return false;
if (rptr > (ep->rxbfr.bufsz - sizeof(*hdr))) {
dev_warn(ep->dcp->dev,
"AFK[ep:%02x]: rptr out of bounds: 0x%x > 0x%lx\n",
ep->endpoint, rptr, ep->rxbfr.bufsz - sizeof(*hdr));
return false;
}
dma_rmb();
hdr = ep->rxbfr.buf + rptr;
magic = le32_to_cpu(hdr->magic);
size = le32_to_cpu(hdr->size);
trace_afk_recv_qe(ep, rptr, magic, size);
if (magic != QE_MAGIC) {
dev_warn(ep->dcp->dev, "AFK[ep:%02x]: invalid queue entry magic: 0x%x\n",
ep->endpoint, magic);
return false;
}
/*
* If there's not enough space for the payload the co-processor inserted
* the current dummy queue entry and we have to advance to the next one
* which will contain the real data.
*/
if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) {
rptr = 0;
hdr = ep->rxbfr.buf + rptr;
magic = le32_to_cpu(hdr->magic);
size = le32_to_cpu(hdr->size);
trace_afk_recv_qe(ep, rptr, magic, size);
if (magic != QE_MAGIC) {
dev_warn(ep->dcp->dev,
"AFK[ep:%02x]: invalid next queue entry magic: 0x%x\n",
ep->endpoint, magic);
return false;
}
ep->rxbfr.hdr->rptr = cpu_to_le32(rptr);
}
if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) {
dev_warn(ep->dcp->dev,
"AFK[ep:%02x]: queue entry out of bounds: 0x%lx > 0x%lx\n",
ep->endpoint, rptr + size + sizeof(*hdr), ep->rxbfr.bufsz);
return false;
}
channel = le32_to_cpu(hdr->channel);
type = le32_to_cpu(hdr->type);
rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT);
if (WARN_ON(rptr > ep->rxbfr.bufsz))
rptr = 0;
if (rptr == ep->rxbfr.bufsz)
rptr = 0;
dma_mb();
ep->rxbfr.hdr->rptr = cpu_to_le32(rptr);
trace_afk_recv_rwptr_post(ep, rptr, wptr);
/*
* TODO: this is theoretically unsafe since DCP could overwrite data
* after the read pointer was updated above. Do it anyway since
* it avoids 2 problems in the DCP tracer:
* 1. the tracer sees replies before the the notifies from dcp
* 2. the tracer tries to read buffers after they are unmapped.
*/
afk_recv_handle(ep, channel, type, hdr->data, size);
return true;
}
static void afk_receive_message_worker(struct work_struct *work_)
{
struct afk_receive_message_work *work;
u16 type;
work = container_of(work_, struct afk_receive_message_work, work);
type = FIELD_GET(RBEP_TYPE, work->message);
switch (type) {
case RBEP_INIT_ACK:
break;
case RBEP_START_ACK:
complete_all(&work->ep->started);
break;
case RBEP_SHUTDOWN_ACK:
complete_all(&work->ep->stopped);
break;
case RBEP_GETBUF:
afk_getbuf(work->ep, work->message);
break;
case RBEP_INIT_TX:
afk_init_rxtx(work->ep, work->message, &work->ep->txbfr);
break;
case RBEP_INIT_RX:
afk_init_rxtx(work->ep, work->message, &work->ep->rxbfr);
break;
case RBEP_RECV:
while (afk_recv(work->ep))
;
break;
default:
dev_err(work->ep->dcp->dev,
"Received unknown AFK message type: 0x%x\n", type);
}
kfree(work);
}
int afk_receive_message(struct apple_dcp_afkep *ep, u64 message)
{
struct afk_receive_message_work *work;
// TODO: comment why decoupling from rtkit thread is required here
work = kzalloc(sizeof(*work), GFP_KERNEL);
if (!work)
return -ENOMEM;
work->ep = ep;
work->message = message;
INIT_WORK(&work->work, afk_receive_message_worker);
queue_work(ep->wq, &work->work);
return 0;
}
int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag,
enum epic_type etype, enum epic_category ecat, u8 stype,
const void *payload, size_t payload_len)
{
u32 rptr, wptr;
struct afk_qe *hdr, *hdr2;
struct epic_hdr *ehdr;
struct epic_sub_hdr *eshdr;
unsigned long flags;
size_t total_epic_size, total_size;
int ret;
spin_lock_irqsave(&ep->lock, flags);
dma_rmb();
rptr = le32_to_cpu(ep->txbfr.hdr->rptr);
wptr = le32_to_cpu(ep->txbfr.hdr->wptr);
trace_afk_send_rwptr_pre(ep, rptr, wptr);
total_epic_size = sizeof(*ehdr) + sizeof(*eshdr) + payload_len;
total_size = sizeof(*hdr) + total_epic_size;
hdr = hdr2 = NULL;
/*
* We need to figure out how to place the entire headers and payload
* into the ring buffer:
* - If the write pointer is in front of the read pointer we just need
* enough space inbetween to store everything.
* - If the read pointer has already wrapper around the end of the
* buffer we can
* a) either store the entire payload at the writer pointer if
* there's enough space until the end,
* b) or just store the queue entry at the write pointer to indicate
* that we need to wrap to the start and then store the headers
* and the payload at the beginning of the buffer. The queue
* header has to be store twice in this case.
* In either case we have to ensure that there's always enough space
* so that we don't accidentally overwrite other buffers.
*/
if (wptr < rptr) {
/*
* If wptr < rptr we can't wrap around and only have to make
* sure that there's enough space for the entire payload.
*/
if (wptr + total_size > rptr) {
ret = -ENOMEM;
goto out;
}
hdr = ep->txbfr.buf + wptr;
wptr += sizeof(*hdr);
} else {
/* We need enough space to place at least a queue entry */
if (wptr + sizeof(*hdr) > ep->txbfr.bufsz) {
ret = -ENOMEM;
goto out;
}
/*
* If we can place a single queue entry but not the full payload
* we need to place one queue entry at the end of the ring
* buffer and then another one together with the entire
* payload at the beginning.
*/
if (wptr + total_size > ep->txbfr.bufsz) {
/*
* Ensure there's space for the queue entry at the
* beginning
*/
if (sizeof(*hdr) > rptr) {
ret = -ENOMEM;
goto out;
}
/*
* Place two queue entries to indicate we want to wrap
* around to the firmware.
*/
hdr = ep->txbfr.buf + wptr;
hdr2 = ep->txbfr.buf;
wptr = sizeof(*hdr);
/* Ensure there's enough space for the entire payload */
if (wptr + total_epic_size > rptr) {
ret = -ENOMEM;
goto out;
}
} else {
/* We have enough space to place the entire payload */
hdr = ep->txbfr.buf + wptr;
wptr += sizeof(*hdr);
}
}
/*
* At this point we're guaranteed that hdr (and possibly hdr2) point
* to a buffer large enough to fit the queue entry and that we have
* enough space at wptr to store the payload.
*/
hdr->magic = cpu_to_le32(QE_MAGIC);
hdr->size = cpu_to_le32(total_epic_size);
hdr->channel = cpu_to_le32(channel);
hdr->type = cpu_to_le32(etype);
if (hdr2)
memcpy(hdr2, hdr, sizeof(*hdr));
ehdr = ep->txbfr.buf + wptr;
memset(ehdr, 0, sizeof(*ehdr));
ehdr->version = 2;
ehdr->seq = cpu_to_le16(ep->qe_seq++);
ehdr->timestamp = cpu_to_le64(0);
wptr += sizeof(*ehdr);
eshdr = ep->txbfr.buf + wptr;
memset(eshdr, 0, sizeof(*eshdr));
eshdr->length = cpu_to_le32(payload_len);
eshdr->version = 4;
eshdr->category = ecat;
eshdr->type = cpu_to_le16(stype);
eshdr->timestamp = cpu_to_le64(0);
eshdr->tag = cpu_to_le16(tag);
if (ecat == EPIC_CAT_REPLY)
eshdr->inline_len = cpu_to_le16(payload_len - 4);
else
eshdr->inline_len = cpu_to_le16(0);
wptr += sizeof(*eshdr);
memcpy(ep->txbfr.buf + wptr, payload, payload_len);
wptr += payload_len;
wptr = ALIGN(wptr, 1 << BLOCK_SHIFT);
if (wptr == ep->txbfr.bufsz)
wptr = 0;
trace_afk_send_rwptr_post(ep, rptr, wptr);
ep->txbfr.hdr->wptr = cpu_to_le32(wptr);
afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_SEND) |
FIELD_PREP(SEND_WPTR, wptr));
ret = 0;
out:
spin_unlock_irqrestore(&ep->lock, flags);
return ret;
}
int afk_send_command(struct apple_epic_service *service, u8 type,
const void *payload, size_t payload_len, void *output,
size_t output_len, u32 *retcode)
{
struct epic_cmd cmd;
void *rxbuf, *txbuf;
dma_addr_t rxbuf_dma, txbuf_dma;
unsigned long flags;
int ret, idx;
u16 tag;
struct apple_dcp_afkep *ep = service->ep;
DECLARE_COMPLETION_ONSTACK(completion);
rxbuf = dma_alloc_coherent(ep->dcp->dev, output_len, &rxbuf_dma,
GFP_KERNEL);
if (!rxbuf)
return -ENOMEM;
txbuf = dma_alloc_coherent(ep->dcp->dev, payload_len, &txbuf_dma,
GFP_KERNEL);
if (!txbuf) {
ret = -ENOMEM;
goto err_free_rxbuf;
}
memcpy(txbuf, payload, payload_len);
memset(&cmd, 0, sizeof(cmd));
cmd.retcode = cpu_to_le32(0);
cmd.rxbuf = cpu_to_le64(rxbuf_dma);
cmd.rxlen = cpu_to_le32(output_len);
cmd.txbuf = cpu_to_le64(txbuf_dma);
cmd.txlen = cpu_to_le32(payload_len);
spin_lock_irqsave(&service->lock, flags);
idx = bitmap_find_free_region(service->cmd_map, MAX_PENDING_CMDS, 0);
if (idx < 0) {
ret = -ENOSPC;
goto err_unlock;
}
tag = (service->cmd_tag & 0xff) << 8;
tag |= idx & 0xff;
service->cmd_tag++;
service->cmds[idx].tag = tag;
service->cmds[idx].rxbuf = rxbuf;
service->cmds[idx].txbuf = txbuf;
service->cmds[idx].rxbuf_dma = rxbuf_dma;
service->cmds[idx].txbuf_dma = txbuf_dma;
service->cmds[idx].rxlen = output_len;
service->cmds[idx].txlen = payload_len;
service->cmds[idx].free_on_ack = false;
service->cmds[idx].done = false;
service->cmds[idx].completion = &completion;
init_completion(&completion);
spin_unlock_irqrestore(&service->lock, flags);
ret = afk_send_epic(service->ep, service->channel, tag,
EPIC_TYPE_COMMAND, EPIC_CAT_COMMAND, type, &cmd,
sizeof(cmd));
if (ret)
goto err_free_cmd;
ret = wait_for_completion_timeout(&completion,
msecs_to_jiffies(MSEC_PER_SEC));
if (ret <= 0) {
spin_lock_irqsave(&service->lock, flags);
/*
* Check again while we're inside the lock to make sure
* the command wasn't completed just after
* wait_for_completion_timeout returned.
*/
if (!service->cmds[idx].done) {
service->cmds[idx].completion = NULL;
service->cmds[idx].free_on_ack = true;
spin_unlock_irqrestore(&service->lock, flags);
return -ETIMEDOUT;
}
spin_unlock_irqrestore(&service->lock, flags);
}
ret = 0;
if (retcode)
*retcode = service->cmds[idx].retcode;
if (output && output_len)
memcpy(output, rxbuf, output_len);
err_free_cmd:
spin_lock_irqsave(&service->lock, flags);
bitmap_release_region(service->cmd_map, idx, 0);
err_unlock:
spin_unlock_irqrestore(&service->lock, flags);
dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma);
err_free_rxbuf:
dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma);
return ret;
}
int afk_service_call(struct apple_epic_service *service, u16 group, u32 command,
const void *data, size_t data_len, size_t data_pad,
void *output, size_t output_len, size_t output_pad)
{
struct epic_service_call *call;
void *bfr;
size_t bfr_len = max(data_len + data_pad, output_len + output_pad) +
sizeof(*call);
int ret;
u32 retcode;
u32 retlen;
bfr = kzalloc(bfr_len, GFP_KERNEL);
if (!bfr)
return -ENOMEM;
call = bfr;
memset(call, 0, sizeof(*call));
call->group = cpu_to_le16(group);
call->command = cpu_to_le32(command);
call->data_len = cpu_to_le32(data_len + data_pad);
call->magic = cpu_to_le32(EPIC_SERVICE_CALL_MAGIC);
memcpy(bfr + sizeof(*call), data, data_len);
ret = afk_send_command(service, EPIC_SUBTYPE_STD_SERVICE, bfr, bfr_len,
bfr, bfr_len, &retcode);
if (ret)
goto out;
if (retcode) {
ret = -EINVAL;
goto out;
}
if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC ||
le16_to_cpu(call->group) != group ||
le32_to_cpu(call->command) != command) {
ret = -EINVAL;
goto out;
}
retlen = le32_to_cpu(call->data_len);
if (output_len < retlen)
retlen = output_len;
if (output && output_len) {
memset(output, 0, output_len);
memcpy(output, bfr + sizeof(*call), retlen);
}
out:
kfree(bfr);
return ret;
}

192
sys/dev/pci/drm/apple/afk.h Normal file
View file

@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* AFK (Apple Firmware Kit) EPIC (EndPoint Interface Client) support
*/
/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
#ifndef _DRM_APPLE_DCP_AFK_H
#define _DRM_APPLE_DCP_AFK_H
#include <linux/completion.h>
#include <linux/types.h>
#include "dcp.h"
#define AFK_MAX_CHANNEL 16
#define MAX_PENDING_CMDS 16
struct apple_epic_service_ops;
struct apple_dcp_afkep;
struct epic_cmd_info {
u16 tag;
void *rxbuf;
void *txbuf;
dma_addr_t rxbuf_dma;
dma_addr_t txbuf_dma;
size_t rxlen;
size_t txlen;
u32 retcode;
bool done;
bool free_on_ack;
struct completion *completion;
};
struct apple_epic_service {
const struct apple_epic_service_ops *ops;
struct apple_dcp_afkep *ep;
struct epic_cmd_info cmds[MAX_PENDING_CMDS];
DECLARE_BITMAP(cmd_map, MAX_PENDING_CMDS);
u8 cmd_tag;
spinlock_t lock;
u32 channel;
bool enabled;
void *cookie;
};
enum epic_subtype;
struct apple_epic_service_ops {
const char name[32];
void (*init)(struct apple_epic_service *service, const char *name,
const char *class, s64 unit);
int (*call)(struct apple_epic_service *service, u32 idx,
const void *data, size_t data_size, void *reply,
size_t reply_size);
int (*report)(struct apple_epic_service *service, enum epic_subtype type,
const void *data, size_t data_size);
void (*teardown)(struct apple_epic_service *service);
};
struct afk_ringbuffer_header {
__le32 bufsz;
u32 unk;
u32 _pad1[14];
__le32 rptr;
u32 _pad2[15];
__le32 wptr;
u32 _pad3[15];
};
struct afk_qe {
#define QE_MAGIC 0x20504f49 // ' POI'
__le32 magic;
__le32 size;
__le32 channel;
__le32 type;
u8 data[];
};
struct epic_hdr {
u8 version;
__le16 seq;
u8 _pad;
__le32 unk;
__le64 timestamp;
} __attribute__((packed));
struct epic_sub_hdr {
__le32 length;
u8 version;
u8 category;
__le16 type;
__le64 timestamp;
__le16 tag;
__le16 unk;
__le32 inline_len;
} __attribute__((packed));
struct epic_cmd {
__le32 retcode;
__le64 rxbuf;
__le64 txbuf;
__le32 rxlen;
__le32 txlen;
u8 rxcookie;
u8 txcookie;
} __attribute__((packed));
struct epic_service_call {
u8 _pad0[2];
__le16 group;
__le32 command;
__le32 data_len;
#define EPIC_SERVICE_CALL_MAGIC 0x69706378
__le32 magic;
u8 _pad1[48];
} __attribute__((packed));
static_assert(sizeof(struct epic_service_call) == 64);
enum epic_type {
EPIC_TYPE_NOTIFY = 0,
EPIC_TYPE_COMMAND = 3,
EPIC_TYPE_REPLY = 4,
EPIC_TYPE_NOTIFY_ACK = 8,
};
enum epic_category {
EPIC_CAT_REPORT = 0x00,
EPIC_CAT_NOTIFY = 0x10,
EPIC_CAT_REPLY = 0x20,
EPIC_CAT_COMMAND = 0x30,
};
enum epic_subtype {
EPIC_SUBTYPE_ANNOUNCE = 0x30,
EPIC_SUBTYPE_TEARDOWN = 0x32,
EPIC_SUBTYPE_STD_SERVICE = 0xc0,
};
struct afk_ringbuffer {
bool ready;
struct afk_ringbuffer_header *hdr;
u32 rptr;
void *buf;
size_t bufsz;
};
struct apple_dcp_afkep {
struct apple_dcp *dcp;
u32 endpoint;
struct workqueue_struct *wq;
struct completion started;
struct completion stopped;
void *bfr;
u16 bfr_tag;
size_t bfr_size;
dma_addr_t bfr_dma;
struct afk_ringbuffer txbfr;
struct afk_ringbuffer rxbfr;
spinlock_t lock;
u16 qe_seq;
const struct apple_epic_service_ops *ops;
struct apple_epic_service services[AFK_MAX_CHANNEL];
u32 num_channels;
};
struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
const struct apple_epic_service_ops *ops);
int afk_start(struct apple_dcp_afkep *ep);
int afk_receive_message(struct apple_dcp_afkep *ep, u64 message);
int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag,
enum epic_type etype, enum epic_category ecat, u8 stype,
const void *payload, size_t payload_len);
int afk_send_command(struct apple_epic_service *service, u8 type,
const void *payload, size_t payload_len, void *output,
size_t output_len, u32 *retcode);
int afk_service_call(struct apple_epic_service *service, u16 group, u32 command,
const void *data, size_t data_len, size_t data_pad,
void *output, size_t output_len, size_t output_pad);
#endif

View file

@ -0,0 +1,267 @@
/* $OpenBSD: apldcp.c,v 1.1 2024/01/22 18:54:01 kettenis Exp $ */
/*
* Copyright (c) 2023 Mark Kettenis <kettenis@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/intr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>
#include <dev/ofw/ofw_power.h>
#include <dev/ofw/ofw_clock.h>
static const void *of_device_get_match_data(const struct device *);
#include "dcp.c"
struct apldcp_softc {
struct platform_device sc_dev;
};
int apldcp_match(struct device *, void *, void *);
void apldcp_attach(struct device *, struct device *, void *);
int apldcp_activate(struct device *, int);
const struct cfattach apldcp_ca = {
sizeof (struct apldcp_softc), apldcp_match, apldcp_attach,
NULL, apldcp_activate
};
struct cfdriver apldcp_cd = {
NULL, "apldcp", DV_DULL
};
int
apldcp_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "apple,dcp") ||
OF_is_compatible(faa->fa_node, "apple,dcpext");
}
void
apldcp_attach(struct device *parent, struct device *self, void *aux)
{
struct apldcp_softc *sc = (struct apldcp_softc *)self;
struct fdt_attach_args *faa = aux;
power_domain_enable(faa->fa_node);
reset_deassert_all(faa->fa_node);
printf("\n");
sc->sc_dev.faa = faa;
platform_device_register(&sc->sc_dev);
dcp_platform_probe(&sc->sc_dev);
}
int
apldcp_activate(struct device *self, int act)
{
int rv;
switch (act) {
case DVACT_QUIESCE:
rv = config_activate_children(self, act);
dcp_platform_suspend(self);
break;
case DVACT_WAKEUP:
dcp_platform_resume(self);
rv = config_activate_children(self, act);
break;
default:
rv = config_activate_children(self, act);
break;
}
return rv;
}
/*
* Linux RTKit interfaces.
*/
#include <arm64/dev/rtkit.h>
struct apple_rtkit_ep {
struct apple_rtkit *rtk;
uint8_t ep;
struct task task;
uint64_t msg;
};
struct apple_rtkit {
struct rtkit_state *state;
struct apple_rtkit_ep ep[64];
void *cookie;
struct platform_device *pdev;
const struct apple_rtkit_ops *ops;
struct taskq *tq;
};
paddr_t
apple_rtkit_logmap(void *cookie, bus_addr_t addr)
{
struct apple_rtkit *rtk = cookie;
int idx, len, node;
uint32_t *phandles;
uint32_t iommu_addresses[5];
bus_addr_t start;
bus_size_t size;
uint64_t reg[2];
len = OF_getproplen(rtk->pdev->node, "memory-region");
idx = OF_getindex(rtk->pdev->node, "dcp_data", "memory-region-names");
if (idx < 0 || idx >= len / sizeof(uint32_t))
return addr;
phandles = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
OF_getpropintarray(rtk->pdev->node, "memory-region",
phandles, len);
node = OF_getnodebyphandle(phandles[idx]);
free(phandles, M_TEMP, len);
if (node == 0)
return addr;
if (!OF_is_compatible(node, "apple,asc-mem"))
return addr;
if (OF_getpropint64array(node, "reg", reg, sizeof(reg)) != sizeof(reg))
return addr;
if (OF_getpropintarray(node, "iommu-addresses", iommu_addresses,
sizeof(iommu_addresses)) < sizeof(iommu_addresses))
return addr;
start = (uint64_t)iommu_addresses[1] << 32 | iommu_addresses[2];
size = (uint64_t)iommu_addresses[3] << 32 | iommu_addresses[4];
if (addr >= start && addr < start + size)
return reg[0] + (addr - start);
/* XXX some machines have truncated DVAs in "iommu-addresses" */
addr &= 0xffffffff;
if (addr >= start && addr < start + size)
return reg[0] + (addr - start);
return (paddr_t)-1;
}
void
apple_rtkit_do_recv(void *arg)
{
struct apple_rtkit_ep *rtkep = arg;
struct apple_rtkit *rtk = rtkep->rtk;
rtk->ops->recv_message(rtk->cookie, rtkep->ep, rtkep->msg);
}
void
apple_rtkit_recv(void *cookie, uint64_t msg)
{
struct apple_rtkit_ep *rtkep = cookie;
struct apple_rtkit *rtk = rtkep->rtk;
rtkep->msg = msg;
task_add(rtk->tq, &rtkep->task);
}
int
apple_rtkit_start_ep(struct apple_rtkit *rtk, uint8_t ep)
{
struct apple_rtkit_ep *rtkep;
int error;
rtkep = &rtk->ep[ep];
rtkep->rtk = rtk;
rtkep->ep = ep;
task_set(&rtkep->task, apple_rtkit_do_recv, rtkep);
error = rtkit_start_endpoint(rtk->state, ep, apple_rtkit_recv, rtkep);
return -error;
}
int
apple_rtkit_send_message(struct apple_rtkit *rtk, uint8_t ep, uint64_t msg,
struct completion *completion, int atomic)
{
int error;
error = rtkit_send_endpoint(rtk->state, ep, msg);
return -error;
}
int
apple_rtkit_wake(struct apple_rtkit *rtk)
{
int error;
error = rtkit_set_iop_pwrstate(rtk->state, RTKIT_MGMT_PWR_STATE_INIT);
if (error)
return -error;
error = rtkit_set_ap_pwrstate(rtk->state, RTKIT_MGMT_PWR_STATE_ON);
return -error;
}
struct apple_rtkit *
devm_apple_rtkit_init(struct device *dev, void *cookie,
const char *mbox_name, int mbox_idx, const struct apple_rtkit_ops *ops)
{
struct platform_device *pdev = (struct platform_device *)dev;
struct apple_rtkit *rtk;
struct rtkit *rk;
rtk = malloc(sizeof(*rtk), M_DEVBUF, M_WAITOK | M_ZERO);
rtk->tq = taskq_create("drmrtk", 1, IPL_HIGH, 0);
if (rtk->tq == NULL) {
free(rtk, M_DEVBUF, sizeof(*rtk));
return ERR_PTR(ENOMEM);
}
rk = malloc(sizeof(*rk), M_DEVBUF, M_WAITOK | M_ZERO);
rk->rk_cookie = rtk;
rk->rk_dmat = pdev->dmat;
rk->rk_logmap = apple_rtkit_logmap;
rtk->state = rtkit_init(pdev->node, mbox_name, 0, rk);
rtk->cookie = cookie;
rtk->pdev = pdev;
rtk->ops = ops;
return rtk;
}
static const void *
of_device_get_match_data(const struct device *dev)
{
struct platform_device *pdev = (struct platform_device *)dev;
int i;
for (i = 0; i < nitems(of_match); i++) {
if (OF_is_compatible(pdev->node, of_match[i].compatible))
return of_match[i].data;
}
return NULL;
}

View file

@ -0,0 +1,401 @@
/* $OpenBSD: apldrm.c,v 1.1 2024/01/22 18:54:01 kettenis Exp $ */
/*
* Copyright (c) 2023 Mark Kettenis <kettenis@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsdisplayvar.h>
#include <dev/rasops/rasops.h>
#include <linux/platform_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_framebuffer.h>
struct apldrm_softc {
struct platform_device sc_dev;
struct drm_device sc_ddev;
int sc_node;
struct rasops_info sc_ri;
struct wsscreen_descr sc_wsd;
struct wsscreen_list sc_wsl;
struct wsscreen_descr *sc_scrlist[1];
void (*sc_switchcb)(void *, int, int);
void *sc_switchcbarg;
void *sc_switchcookie;
struct task sc_switchtask;
int sc_burner_fblank;
struct task sc_burner_task;
};
#include "apple_drv.c"
int apldrm_match(struct device *, void *, void *);
void apldrm_attach(struct device *, struct device *, void *);
int apldrm_activate(struct device *, int);
const struct cfattach apldrm_ca = {
sizeof (struct apldrm_softc), apldrm_match, apldrm_attach,
NULL, apldrm_activate
};
struct cfdriver apldrm_cd = {
NULL, "apldrm", DV_DULL
};
void apldrm_attachhook(struct device *);
int
apldrm_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "apple,display-subsystem");
}
void
apldrm_attach(struct device *parent, struct device *self, void *aux)
{
struct apldrm_softc *sc = (struct apldrm_softc *)self;
struct fdt_attach_args *faa = aux;
int idx, len, node;
uint32_t *phandles;
uint64_t reg[2];
sc->sc_node = faa->fa_node;
/* Claim framebuffer to prevent attaching other drivers. */
len = OF_getproplen(faa->fa_node, "memory-region");
idx = OF_getindex(faa->fa_node, "framebuffer", "memory-region-names");
if (idx >= 0 && idx < len / sizeof(uint32_t)) {
phandles = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
OF_getpropintarray(faa->fa_node, "memory-region",
phandles, len);
node = OF_getnodebyphandle(phandles[idx]);
if (node) {
if (OF_getpropint64array(node, "reg", reg,
sizeof(reg)) == sizeof(reg))
rasops_claim_framebuffer(reg[0], reg[1], self);
}
free(phandles, M_TEMP, len);
}
/*
* Update our understanding of the console output node if
* we're using the framebuffer console.
*/
if (OF_is_compatible(stdout_node, "simple-framebuffer"))
stdout_node = sc->sc_node;
printf("\n");
sc->sc_dev.faa = faa;
platform_device_register(&sc->sc_dev);
drm_attach_platform((struct drm_driver *)&apple_drm_driver,
faa->fa_iot, faa->fa_dmat, self, &sc->sc_ddev);
config_mountroot(self, apldrm_attachhook);
}
int
apldrm_activate(struct device *self, int act)
{
int rv;
switch (act) {
case DVACT_QUIESCE:
rv = config_activate_children(self, act);
apple_platform_suspend(self);
break;
case DVACT_WAKEUP:
apple_platform_resume(self);
rv = config_activate_children(self, act);
break;
default:
rv = config_activate_children(self, act);
break;
}
return rv;
}
int
apldrm_wsioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
struct rasops_info *ri = v;
struct apldrm_softc *sc = ri->ri_hw;
struct wsdisplay_param *dp = (struct wsdisplay_param *)data;
struct wsdisplay_fbinfo *wdf;
struct backlight_device *bd;
bd = backlight_device_get_by_name("apple-panel-bl");
switch (cmd) {
case WSDISPLAYIO_GTYPE:
*(u_int *)data = WSDISPLAY_TYPE_KMS;
return 0;
case WSDISPLAYIO_GINFO:
wdf = (struct wsdisplay_fbinfo *)data;
wdf->width = ri->ri_width;
wdf->height = ri->ri_height;
wdf->depth = ri->ri_depth;
wdf->stride = ri->ri_stride;
wdf->offset = 0; /* XXX */
wdf->cmsize = 0;
return 0;
case WSDISPLAYIO_GETPARAM:
if (bd == NULL)
return -1;
switch (dp->param) {
case WSDISPLAYIO_PARAM_BRIGHTNESS:
dp->min = 0;
dp->max = bd->props.max_brightness;
dp->curval = bd->props.brightness;
return (dp->max > dp->min) ? 0 : -1;
}
break;
case WSDISPLAYIO_SETPARAM:
if (bd == NULL)
return -1;
switch (dp->param) {
case WSDISPLAYIO_PARAM_BRIGHTNESS:
bd->props.brightness = dp->curval;
backlight_update_status(bd);
knote_locked(&sc->sc_ddev.note, NOTE_CHANGE);
return 0;
}
break;
case WSDISPLAYIO_SVIDEO:
case WSDISPLAYIO_GVIDEO:
return 0;
}
return (-1);
}
paddr_t
apldrm_wsmmap(void *v, off_t off, int prot)
{
return (-1);
}
int
apldrm_alloc_screen(void *v, const struct wsscreen_descr *type,
void **cookiep, int *curxp, int *curyp, uint32_t *attrp)
{
return rasops_alloc_screen(v, cookiep, curxp, curyp, attrp);
}
void
apldrm_free_screen(void *v, void *cookie)
{
return rasops_free_screen(v, cookie);
}
void
apldrm_doswitch(void *v)
{
struct rasops_info *ri = v;
struct apldrm_softc *sc = ri->ri_hw;
struct drm_fb_helper *fb_helper = sc->sc_ddev.fb_helper;
rasops_show_screen(ri, sc->sc_switchcookie, 0, NULL, NULL);
drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
if (sc->sc_switchcb)
(sc->sc_switchcb)(sc->sc_switchcbarg, 0, 0);
}
int
apldrm_show_screen(void *v, void *cookie, int waitok,
void (*cb)(void *, int, int), void *cbarg)
{
struct rasops_info *ri = v;
struct apldrm_softc *sc = ri->ri_hw;
if (cookie == ri->ri_active)
return (0);
sc->sc_switchcb = cb;
sc->sc_switchcbarg = cbarg;
sc->sc_switchcookie = cookie;
if (cb) {
task_add(systq, &sc->sc_switchtask);
return (EAGAIN);
}
apldrm_doswitch(v);
return (0);
}
void
apldrm_enter_ddb(void *v, void *cookie)
{
struct rasops_info *ri = v;
struct apldrm_softc *sc = ri->ri_hw;
struct drm_fb_helper *fb_helper = sc->sc_ddev.fb_helper;
if (cookie == ri->ri_active)
return;
rasops_show_screen(ri, cookie, 0, NULL, NULL);
drm_fb_helper_debug_enter(fb_helper->info);
}
void
apldrm_burner(void *v, u_int on, u_int flags)
{
struct rasops_info *ri = v;
struct apldrm_softc *sc = ri->ri_hw;
task_del(systq, &sc->sc_burner_task);
if (on)
sc->sc_burner_fblank = FB_BLANK_UNBLANK;
else {
if (flags & WSDISPLAY_BURN_VBLANK)
sc->sc_burner_fblank = FB_BLANK_VSYNC_SUSPEND;
else
sc->sc_burner_fblank = FB_BLANK_NORMAL;
}
/*
* Setting the DPMS mode may sleep while waiting for vblank so
* hand things off to a taskq.
*/
task_add(systq, &sc->sc_burner_task);
}
void
apldrm_burner_cb(void *arg)
{
struct apldrm_softc *sc = arg;
struct drm_fb_helper *fb_helper = sc->sc_ddev.fb_helper;
drm_fb_helper_blank(sc->sc_burner_fblank, fb_helper->info);
}
struct wsdisplay_accessops apldrm_accessops = {
.ioctl = apldrm_wsioctl,
.mmap = apldrm_wsmmap,
.alloc_screen = apldrm_alloc_screen,
.free_screen = apldrm_free_screen,
.show_screen = apldrm_show_screen,
.enter_ddb = apldrm_enter_ddb,
.getchar = rasops_getchar,
.load_font = rasops_load_font,
.list_font = rasops_list_font,
.scrollback = rasops_scrollback,
.burn_screen = apldrm_burner
};
void
apldrm_attachhook(struct device *self)
{
struct apldrm_softc *sc = (struct apldrm_softc *)self;
struct drm_fb_helper *fb_helper;
struct rasops_info *ri = &sc->sc_ri;
struct wsemuldisplaydev_attach_args waa;
int console = 0;
uint32_t defattr;
int error;
error = apple_platform_probe(&sc->sc_dev);
if (error)
return;
if (sc->sc_node == stdout_node)
console = 1;
fb_helper = sc->sc_ddev.fb_helper;
ri->ri_hw = sc;
ri->ri_bits = fb_helper->info->screen_buffer;
ri->ri_flg = RI_CENTER | RI_VCONS | RI_WRONLY;
ri->ri_depth = fb_helper->fb->format->cpp[0] * 8;
ri->ri_stride = fb_helper->fb->pitches[0];
ri->ri_width = fb_helper->info->var.xres;
ri->ri_height = fb_helper->info->var.yres;
switch (fb_helper->fb->format->format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
ri->ri_rnum = 8;
ri->ri_rpos = 16;
ri->ri_gnum = 8;
ri->ri_gpos = 8;
ri->ri_bnum = 8;
ri->ri_bpos = 0;
break;
case DRM_FORMAT_XRGB2101010:
ri->ri_rnum = 10;
ri->ri_rpos = 20;
ri->ri_gnum = 10;
ri->ri_gpos = 10;
ri->ri_bnum = 10;
ri->ri_bpos = 0;
break;
}
rasops_init(ri, 160, 160);
strlcpy(sc->sc_wsd.name, "std", sizeof(sc->sc_wsd.name));
sc->sc_wsd.capabilities = ri->ri_caps;
sc->sc_wsd.nrows = ri->ri_rows;
sc->sc_wsd.ncols = ri->ri_cols;
sc->sc_wsd.textops = &ri->ri_ops;
sc->sc_wsd.fontwidth = ri->ri_font->fontwidth;
sc->sc_wsd.fontheight = ri->ri_font->fontheight;
sc->sc_scrlist[0] = &sc->sc_wsd;
sc->sc_wsl.nscreens = 1;
sc->sc_wsl.screens = (const struct wsscreen_descr **)sc->sc_scrlist;
task_set(&sc->sc_switchtask, apldrm_doswitch, ri);
task_set(&sc->sc_burner_task, apldrm_burner_cb, sc);
if (console) {
ri->ri_ops.pack_attr(ri->ri_active, 0, 0, 0, &defattr);
wsdisplay_cnattach(&sc->sc_wsd, ri->ri_active,
ri->ri_ccol, ri->ri_crow, defattr);
}
memset(&waa, 0, sizeof(waa));
waa.scrdata = &sc->sc_wsl;
waa.accessops = &apldrm_accessops;
waa.accesscookie = ri;
waa.console = console;
printf("%s: %dx%d, %dbpp\n", sc->sc_dev.dev.dv_xname,
ri->ri_width, ri->ri_height, ri->ri_depth);
config_found_sm(self, &waa, wsemuldisplaydevprint,
wsemuldisplaydevsubmatch);
}

View file

@ -0,0 +1,678 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
/* Based on meson driver which is
* Copyright (C) 2016 BayLibre, SAS
* Author: Neil Armstrong <narmstrong@baylibre.com>
* Copyright (C) 2015 Amlogic, Inc. All rights reserved.
* Copyright (C) 2014 Endless Mobile
*/
#include <linux/component.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <drm/drm_aperture.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_mode.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_module.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_fixed.h>
#include "dcp.h"
#define DRIVER_NAME "apple"
#define DRIVER_DESC "Apple display controller DRM driver"
#define FRAC_16_16(mult, div) (((mult) << 16) / (div))
#define MAX_COPROCESSORS 2
struct apple_drm_private {
struct drm_device drm;
};
DEFINE_DRM_GEM_DMA_FOPS(apple_fops);
#define DART_PAGE_SIZE 16384
static int apple_drm_gem_dumb_create(struct drm_file *file_priv,
struct drm_device *drm,
struct drm_mode_create_dumb *args)
{
args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64);
args->size = round_up(args->pitch * args->height, DART_PAGE_SIZE);
return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
}
static const struct drm_driver apple_drm_driver = {
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(apple_drm_gem_dumb_create),
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = "20221106",
.major = 1,
.minor = 0,
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.fops = &apple_fops,
};
static int apple_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_state *state)
{
struct drm_plane_state *new_plane_state;
struct drm_crtc_state *crtc_state;
new_plane_state = drm_atomic_get_new_plane_state(state, plane);
if (!new_plane_state->crtc)
return 0;
crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
/*
* DCP limits downscaling to 2x and upscaling to 4x. Attempting to
* scale outside these bounds errors out when swapping.
*
* This function also takes care of clipping the src/dest rectangles,
* which is required for correct operation. Partially off-screen
* surfaces may appear corrupted.
*
* DCP does not distinguish plane types in the hardware, so we set
* can_position. If the primary plane does not fill the screen, the
* hardware will fill in zeroes (black).
*/
return drm_atomic_helper_check_plane_state(new_plane_state,
crtc_state,
FRAC_16_16(1, 4),
FRAC_16_16(2, 1),
true, true);
}
static void apple_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_state *state)
{
/* Handled in atomic_flush */
}
static const struct drm_plane_helper_funcs apple_plane_helper_funcs = {
.atomic_check = apple_plane_atomic_check,
.atomic_update = apple_plane_atomic_update,
};
static void apple_plane_cleanup(struct drm_plane *plane)
{
drm_plane_cleanup(plane);
kfree(plane);
}
static const struct drm_plane_funcs apple_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = apple_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
/*
* Table of supported formats, mapping from DRM fourccs to DCP fourccs.
*
* For future work, DCP supports more formats not listed, including YUV
* formats, an extra RGBA format, and a biplanar RGB10_A8 format (fourcc b3a8)
* used for HDR.
*
* Note: we don't have non-alpha formats but userspace breaks without XRGB. It
* doesn't matter for the primary plane, but cursors/overlays must not
* advertise formats without alpha.
*/
static const u32 dcp_formats[] = {
DRM_FORMAT_XRGB2101010,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ABGR8888,
};
u64 apple_format_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID
};
static struct drm_plane *apple_plane_init(struct drm_device *dev,
unsigned long possible_crtcs,
enum drm_plane_type type)
{
int ret;
struct drm_plane *plane;
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
ret = drm_universal_plane_init(dev, plane, possible_crtcs,
&apple_plane_funcs,
dcp_formats, ARRAY_SIZE(dcp_formats),
apple_format_modifiers, type, NULL);
if (ret)
return ERR_PTR(ret);
drm_plane_helper_add(plane, &apple_plane_helper_funcs);
return plane;
}
static enum drm_connector_status
apple_connector_detect(struct drm_connector *connector, bool force)
{
struct apple_connector *apple_connector = to_apple_connector(connector);
return apple_connector->connected ? connector_status_connected :
connector_status_disconnected;
}
static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct drm_crtc_state *crtc_state;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (crtc_state->active_changed && crtc_state->active) {
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
dcp_poweron(apple_crtc->dcp);
dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
}
if (crtc_state->active)
dcp_crtc_atomic_modeset(crtc, state);
}
static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct drm_crtc_state *crtc_state;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (crtc_state->active_changed && !crtc_state->active) {
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
dcp_poweroff(apple_crtc->dcp);
dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
}
if (crtc->state->event && !crtc->state->active) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
}
static void apple_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
unsigned long flags;
if (crtc->state->event) {
spin_lock_irqsave(&crtc->dev->event_lock, flags);
apple_crtc->event = crtc->state->event;
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
crtc->state->event = NULL;
}
}
static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state)
{
struct drm_device *dev = old_state->dev;
drm_atomic_helper_commit_modeset_disables(dev, old_state);
drm_atomic_helper_commit_modeset_enables(dev, old_state);
drm_atomic_helper_commit_planes(dev, old_state,
DRM_PLANE_COMMIT_ACTIVE_ONLY);
drm_atomic_helper_fake_vblank(old_state);
drm_atomic_helper_commit_hw_done(old_state);
drm_atomic_helper_wait_for_flip_done(dev, old_state);
drm_atomic_helper_cleanup_planes(dev, old_state);
}
static void apple_crtc_cleanup(struct drm_crtc *crtc)
{
drm_crtc_cleanup(crtc);
kfree(to_apple_crtc(crtc));
}
static const struct drm_crtc_funcs apple_crtc_funcs = {
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.destroy = apple_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.set_config = drm_atomic_helper_set_config,
};
static const struct drm_mode_config_funcs apple_mode_config_funcs = {
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
.fb_create = drm_gem_fb_create,
};
static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = {
.atomic_commit_tail = dcp_atomic_commit_tail,
};
static void appledrm_connector_cleanup(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
kfree(to_apple_connector(connector));
}
static const struct drm_connector_funcs apple_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = appledrm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.detect = apple_connector_detect,
};
static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
.get_modes = dcp_get_modes,
.mode_valid = dcp_mode_valid,
};
static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
.atomic_begin = apple_crtc_atomic_begin,
.atomic_check = dcp_crtc_atomic_check,
.atomic_flush = dcp_flush,
.atomic_enable = apple_crtc_atomic_enable,
.atomic_disable = apple_crtc_atomic_disable,
.mode_fixup = dcp_crtc_mode_fixup,
};
static int apple_probe_per_dcp(struct device *dev,
struct drm_device *drm,
struct platform_device *dcp,
int num, bool dcp_ext)
{
struct apple_crtc *crtc;
struct apple_connector *connector;
struct apple_encoder *enc;
struct drm_plane *primary;
int ret;
primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY);
if (IS_ERR(primary))
return PTR_ERR(primary);
crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL,
&apple_crtc_funcs, NULL);
if (ret)
return ret;
drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs);
drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0);
enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base,
DRM_MODE_ENCODER_TMDS);
if (IS_ERR(enc))
return PTR_ERR(enc);
enc->base.possible_crtcs = drm_crtc_mask(&crtc->base);
connector = kzalloc(sizeof(*connector), GFP_KERNEL);
drm_connector_helper_add(&connector->base,
&apple_connector_helper_funcs);
#ifdef __linux__
// HACK:
if (dcp_ext)
connector->base.fwnode = fwnode_handle_get(dev->fwnode);
#endif
ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs,
dcp_get_connector_type(dcp));
if (ret)
return ret;
connector->base.polled = DRM_CONNECTOR_POLL_HPD;
connector->connected = false;
connector->dcp = dcp;
INIT_WORK(&connector->hotplug_wq, dcp_hotplug);
crtc->dcp = dcp;
dcp_link(dcp, crtc, connector);
return drm_connector_attach_encoder(&connector->base, &enc->base);
}
static int apple_get_fb_resource(struct device *dev, const char *name,
struct resource *fb_r)
{
int idx, ret = -ENODEV;
struct device_node *node;
idx = of_property_match_string(dev->of_node, "memory-region-names", name);
node = of_parse_phandle(dev->of_node, "memory-region", idx);
if (!node) {
dev_err(dev, "reserved-memory node '%s' not found\n", name);
return -ENODEV;
}
if (!of_device_is_available(node)) {
dev_err(dev, "reserved-memory node '%s' is unavailable\n", name);
goto err;
}
if (!of_device_is_compatible(node, "framebuffer")) {
dev_err(dev, "reserved-memory node '%s' is incompatible\n",
node->full_name);
goto err;
}
ret = of_address_to_resource(node, 0, fb_r);
err:
of_node_put(node);
return ret;
}
static const struct of_device_id apple_dcp_id_tbl[] = {
{ .compatible = "apple,dcp" },
{ .compatible = "apple,dcpext" },
{},
};
static int apple_drm_init_dcp(struct device *dev)
{
struct apple_drm_private *apple = dev_get_drvdata(dev);
struct platform_device *dcp[MAX_COPROCESSORS];
struct device_node *np;
u64 timeout;
int i, ret, num_dcp = 0;
for_each_matching_node(np, apple_dcp_id_tbl) {
bool dcp_ext;
if (!of_device_is_available(np)) {
of_node_put(np);
continue;
}
dcp_ext = of_device_is_compatible(np, "apple,dcpext");
dcp[num_dcp] = of_find_device_by_node(np);
of_node_put(np);
if (!dcp[num_dcp])
continue;
ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp],
num_dcp, dcp_ext);
if (ret)
continue;
ret = dcp_start(dcp[num_dcp]);
if (ret)
continue;
num_dcp++;
}
if (num_dcp < 1)
return -ENODEV;
/*
* Starting DPTX might take some time.
*/
timeout = get_jiffies_64() + msecs_to_jiffies(3000);
for (i = 0; i < num_dcp; ++i) {
u64 jiffies = get_jiffies_64();
u64 wait = time_after_eq64(jiffies, timeout) ?
0 :
timeout - jiffies;
ret = dcp_wait_ready(dcp[i], wait);
/* There is nothing we can do if a dcp/dcpext does not boot
* (successfully). Ignoring it should not do any harm now.
* Needs to reevaluated whenn adding dcpext support.
*/
if (ret)
dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret);
}
/* HACK: Wait for dcp* to settle before a modeset */
drm_msleep(100);
return 0;
}
static int apple_drm_init(struct device *dev)
{
struct apple_drm_private *apple;
struct resource fb_r;
resource_size_t fb_size;
int ret;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42));
if (ret)
return ret;
ret = apple_get_fb_resource(dev, "framebuffer", &fb_r);
if (ret)
return ret;
fb_size = fb_r.end - fb_r.start + 1;
ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size,
&apple_drm_driver);
if (ret) {
dev_err(dev, "Failed remove fb: %d\n", ret);
goto err_unbind;
}
#ifdef __linux__
apple = devm_drm_dev_alloc(dev, &apple_drm_driver,
struct apple_drm_private, drm);
if (IS_ERR(apple))
return PTR_ERR(apple);
#else
struct apldrm_softc *sc = (struct apldrm_softc *)dev;
apple = (struct apple_drm_private *)&sc->sc_ddev;
#endif
dev_set_drvdata(dev, apple);
ret = component_bind_all(dev, apple);
if (ret)
return ret;
ret = drmm_mode_config_init(&apple->drm);
if (ret)
goto err_unbind;
/*
* IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane
* requires a minimum of 32x32 for the source buffer" if smaller
*/
apple->drm.mode_config.min_width = 32;
apple->drm.mode_config.min_height = 32;
/*
* TODO: this is the max framebuffer size not the maximal supported
* output resolution. DCP reports the maximal framebuffer size take it
* from there.
* Hardcode it for now to the M1 Max DCP reported 'MaxSrcBufferWidth'
* and 'MaxSrcBufferHeight' of 16384.
*/
apple->drm.mode_config.max_width = 16384;
apple->drm.mode_config.max_height = 16384;
apple->drm.mode_config.funcs = &apple_mode_config_funcs;
apple->drm.mode_config.helper_private = &apple_mode_config_helpers;
ret = apple_drm_init_dcp(dev);
if (ret)
goto err_unbind;
drm_mode_config_reset(&apple->drm);
ret = drm_dev_register(&apple->drm, 0);
if (ret)
goto err_unbind;
drm_fbdev_dma_setup(&apple->drm, 32);
return 0;
err_unbind:
component_unbind_all(dev, NULL);
return ret;
}
static void apple_drm_uninit(struct device *dev)
{
struct apple_drm_private *apple = dev_get_drvdata(dev);
drm_dev_unregister(&apple->drm);
drm_atomic_helper_shutdown(&apple->drm);
component_unbind_all(dev, NULL);
dev_set_drvdata(dev, NULL);
}
static int apple_drm_bind(struct device *dev)
{
return apple_drm_init(dev);
}
static void apple_drm_unbind(struct device *dev)
{
apple_drm_uninit(dev);
}
const struct component_master_ops apple_drm_ops = {
.bind = apple_drm_bind,
.unbind = apple_drm_unbind,
};
static int add_dcp_components(struct device *dev,
struct component_match **matchptr)
{
struct device_node *np;
int num = 0;
for_each_matching_node(np, apple_dcp_id_tbl) {
if (of_device_is_available(np)) {
drm_of_component_match_add(dev, matchptr,
component_compare_of, np);
num++;
}
of_node_put(np);
}
return num;
}
static int apple_platform_probe(struct platform_device *pdev)
{
struct device *mdev = &pdev->dev;
struct component_match *match = NULL;
int num_dcp;
/* add DCP components, handle less than 1 as probe error */
num_dcp = add_dcp_components(mdev, &match);
if (num_dcp < 1)
return -ENODEV;
return component_master_add_with_match(mdev, &apple_drm_ops, match);
}
#ifdef __linux__
static int apple_platform_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &apple_drm_ops);
return 0;
}
static const struct of_device_id of_match[] = {
{ .compatible = "apple,display-subsystem" },
{}
};
MODULE_DEVICE_TABLE(of, of_match);
#endif
#ifdef CONFIG_PM_SLEEP
static int apple_platform_suspend(struct device *dev)
{
struct apple_drm_private *apple = dev_get_drvdata(dev);
if (apple)
return drm_mode_config_helper_suspend(&apple->drm);
return 0;
}
static int apple_platform_resume(struct device *dev)
{
struct apple_drm_private *apple = dev_get_drvdata(dev);
if (apple)
drm_mode_config_helper_resume(&apple->drm);
return 0;
}
static const struct dev_pm_ops apple_platform_pm_ops = {
.suspend = apple_platform_suspend,
.resume = apple_platform_resume,
};
#endif
#ifdef __linux__
static struct platform_driver apple_platform_driver = {
.driver = {
.name = "apple-drm",
.of_match_table = of_match,
#ifdef CONFIG_PM_SLEEP
.pm = &apple_platform_pm_ops,
#endif
},
.probe = apple_platform_probe,
.remove = apple_platform_remove,
};
drm_module_platform_driver(apple_platform_driver);
MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("Dual MIT/GPL");
#endif

View file

@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
#ifndef __APPLE_DCP_INTERNAL_H__
#define __APPLE_DCP_INTERNAL_H__
#include <linux/backlight.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/mutex.h>
#include <linux/mux/consumer.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include "dptxep.h"
#include "iomfb.h"
#include "iomfb_v12_3.h"
#include "iomfb_v13_3.h"
#define DCP_MAX_PLANES 2
struct apple_dcp;
struct apple_dcp_afkep;
enum dcp_firmware_version {
DCP_FIRMWARE_UNKNOWN,
DCP_FIRMWARE_V_12_3,
DCP_FIRMWARE_V_13_5,
};
enum {
SYSTEM_ENDPOINT = 0x20,
TEST_ENDPOINT = 0x21,
DCP_EXPERT_ENDPOINT = 0x22,
DISP0_ENDPOINT = 0x23,
DPTX_ENDPOINT = 0x2a,
HDCP_ENDPOINT = 0x2b,
REMOTE_ALLOC_ENDPOINT = 0x2d,
IOMFB_ENDPOINT = 0x37,
};
/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */
struct dcp_chunks {
size_t length;
void *data;
};
#define DCP_MAX_MAPPINGS (128) /* should be enough */
#define MAX_DISP_REGISTERS (7)
struct dcp_mem_descriptor {
size_t size;
void *buf;
dma_addr_t dva;
struct sg_table map;
u64 reg;
};
/* Limit on call stack depth (arbitrary). Some nesting is required */
#define DCP_MAX_CALL_DEPTH 8
typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
struct dcp_channel {
dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH];
void *cookies[DCP_MAX_CALL_DEPTH];
void *output[DCP_MAX_CALL_DEPTH];
u16 end[DCP_MAX_CALL_DEPTH];
/* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */
u8 depth;
};
struct dcp_fb_reference {
struct list_head head;
struct drm_framebuffer *fb;
u32 swap_id;
};
#define MAX_NOTCH_HEIGHT 160
struct dcp_brightness {
struct backlight_device *bl_dev;
u32 maximum;
u32 dac;
int nits;
int scale;
bool update;
};
/** laptop/AiO integrated panel parameters from DT */
struct dcp_panel {
/// panel width in millimeter
int width_mm;
/// panel height in millimeter
int height_mm;
/// panel has a mini-LED backllight
bool has_mini_led;
};
struct apple_dcp_hw_data {
u32 num_dptx_ports;
};
/* TODO: move IOMFB members to its own struct */
struct apple_dcp {
struct device *dev;
struct platform_device *piodma;
struct iommu_domain *iommu_dom;
struct apple_rtkit *rtk;
struct apple_crtc *crtc;
struct apple_connector *connector;
struct apple_dcp_hw_data hw;
/* firmware version and compatible firmware version */
enum dcp_firmware_version fw_compat;
/* Coprocessor control register */
void __iomem *coproc_reg;
/* DCP has crashed */
bool crashed;
/************* IOMFB **************************************************
* everything below is mostly used inside IOMFB but it could make *
* sense keep some of the the members in apple_dcp. *
**********************************************************************/
/* clock rate request by dcp in */
struct clk *clk;
/* DCP shared memory */
void *shmem;
/* Display registers mappable to the DCP */
struct resource *disp_registers[MAX_DISP_REGISTERS];
unsigned int nr_disp_registers;
struct resource disp_bw_scratch_res;
struct resource disp_bw_doorbell_res;
u32 disp_bw_scratch_index;
u32 disp_bw_scratch_offset;
u32 disp_bw_doorbell_index;
u32 disp_bw_doorbell_offset;
u32 index;
/* Bitmap of memory descriptors used for mappings made by the DCP */
DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS);
/* Indexed table of memory descriptors */
struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS];
struct dcp_channel ch_cmd, ch_oobcmd;
struct dcp_channel ch_cb, ch_oobcb, ch_async, ch_oobasync;
/* iomfb EP callback handlers */
const iomfb_cb_handler *cb_handlers;
/* Active chunked transfer. There can only be one at a time. */
struct dcp_chunks chunks;
/* Queued swap. Owned by the DCP to avoid per-swap memory allocation */
union {
struct dcp_swap_submit_req_v12_3 v12_3;
struct dcp_swap_submit_req_v13_3 v13_3;
} swap;
/* swap id of the last completed swap */
u32 last_swap_id;
/* Current display mode */
bool during_modeset;
bool valid_mode;
struct dcp_set_digital_out_mode_req mode;
/* completion for active turning true */
struct completion start_done;
/* Is the DCP booted? */
bool active;
/* eDP display without DP-HDMI conversion */
bool main_display;
/* clear all surfaces on init */
bool surfaces_cleared;
/* Modes valid for the connected display */
struct dcp_display_mode *modes;
unsigned int nr_modes;
/* Attributes of the connector */
int connector_type;
/* Attributes of the connected display */
int width_mm, height_mm;
unsigned notch_height;
/* Workqueue for sending vblank events when a dcp swap is not possible */
struct work_struct vblank_wq;
/* List of referenced drm_framebuffers which can be unreferenced
* on the next successfully completed swap.
*/
struct list_head swapped_out_fbs;
struct dcp_brightness brightness;
/* Workqueue for updating the initial initial brightness */
struct work_struct bl_register_wq;
struct rwlock bl_register_mutex;
/* integrated panel if present */
struct dcp_panel panel;
struct apple_dcp_afkep *systemep;
struct completion systemep_done;
struct apple_dcp_afkep *ibootep;
struct apple_dcp_afkep *dptxep;
struct dptx_port dptxport[2];
/* these fields are output port specific */
struct phy *phy;
struct mux_control *xbar;
struct gpio_desc *hdmi_hpd;
struct gpio_desc *hdmi_pwren;
struct gpio_desc *dp2hdmi_pwren;
struct rwlock hpd_mutex;
u32 dptx_phy;
u32 dptx_die;
int hdmi_hpd_irq;
};
int dcp_backlight_register(struct apple_dcp *dcp);
bool dcp_has_panel(struct apple_dcp *dcp);
#define DCP_AUDIO_MAX_CHANS 15
#endif /* __APPLE_DCP_INTERNAL_H__ */

1115
sys/dev/pci/drm/apple/dcp.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
#ifndef __APPLE_DCP_H__
#define __APPLE_DCP_H__
#include <drm/drm_atomic.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fourcc.h>
#include "dcp-internal.h"
#include "parser.h"
struct apple_crtc {
struct drm_crtc base;
struct drm_pending_vblank_event *event;
bool vsync_disabled;
/* Reference to the DCP device owning this CRTC */
struct platform_device *dcp;
};
#define to_apple_crtc(x) container_of(x, struct apple_crtc, base)
void dcp_hotplug(struct work_struct *work);
struct apple_connector {
struct drm_connector base;
bool connected;
struct platform_device *dcp;
/* Workqueue for sending hotplug events to the associated device */
struct work_struct hotplug_wq;
};
#define to_apple_connector(x) container_of(x, struct apple_connector, base)
struct apple_encoder {
struct drm_encoder base;
};
#define to_apple_encoder(x) container_of(x, struct apple_encoder, base)
void dcp_poweroff(struct platform_device *pdev);
void dcp_poweron(struct platform_device *pdev);
int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state);
int dcp_get_connector_type(struct platform_device *pdev);
void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
struct apple_connector *connector);
int dcp_start(struct platform_device *pdev);
int dcp_wait_ready(struct platform_device *pdev, u64 timeout);
void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
bool dcp_is_initialized(struct platform_device *pdev);
void apple_crtc_vblank(struct apple_crtc *apple);
void dcp_drm_crtc_vblank(struct apple_crtc *crtc);
int dcp_get_modes(struct drm_connector *connector);
int dcp_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode);
int dcp_crtc_atomic_modeset(struct drm_crtc *crtc,
struct drm_atomic_state *state);
bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void dcp_set_dimensions(struct apple_dcp *dcp);
void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message);
int iomfb_start_rtkit(struct apple_dcp *dcp);
void iomfb_shutdown(struct apple_dcp *dcp);
/* rtkit message handler for IOMFB messages */
void iomfb_recv_msg(struct apple_dcp *dcp, u64 message);
int systemep_init(struct apple_dcp *dcp);
int dptxep_init(struct apple_dcp *dcp);
int ibootep_init(struct apple_dcp *dcp);
#endif

View file

@ -0,0 +1,231 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright (C) The Asahi Linux Contributors */
#include <drm/drm_atomic.h>
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_modeset_lock.h>
#include <linux/backlight.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include "linux/jiffies.h"
#include "dcp.h"
#include "dcp-internal.h"
#define MIN_BRIGHTNESS_PART1 2U
#define MAX_BRIGHTNESS_PART1 99U
#define MIN_BRIGHTNESS_PART2 103U
#define MAX_BRIGHTNESS_PART2 510U
/*
* lookup for display brightness 2 to 99 nits
* */
static u32 brightness_part1[] = {
0x0000000, 0x0810038, 0x0f000bd, 0x143011c,
0x1850165, 0x1bc01a1, 0x1eb01d4, 0x2140200,
0x2380227, 0x2590249, 0x2770269, 0x2930285,
0x2ac02a0, 0x2c402b8, 0x2d902cf, 0x2ee02e4,
0x30102f8, 0x314030b, 0x325031c, 0x335032d,
0x345033d, 0x354034d, 0x362035b, 0x3700369,
0x37d0377, 0x38a0384, 0x3960390, 0x3a2039c,
0x3ad03a7, 0x3b803b3, 0x3c303bd, 0x3cd03c8,
0x3d703d2, 0x3e103dc, 0x3ea03e5, 0x3f303ef,
0x3fc03f8, 0x4050400, 0x40d0409, 0x4150411,
0x41d0419, 0x4250421, 0x42d0429, 0x4340431,
0x43c0438, 0x443043f, 0x44a0446, 0x451044d,
0x4570454, 0x45e045b, 0x4640461, 0x46b0468,
0x471046e, 0x4770474, 0x47d047a, 0x4830480,
0x4890486, 0x48e048b, 0x4940491, 0x4990497,
0x49f049c, 0x4a404a1, 0x4a904a7, 0x4ae04ac,
0x4b304b1, 0x4b804b6, 0x4bd04bb, 0x4c204c0,
0x4c704c5, 0x4cc04c9, 0x4d004ce, 0x4d504d3,
0x4d904d7, 0x4de04dc, 0x4e204e0, 0x4e704e4,
0x4eb04e9, 0x4ef04ed, 0x4f304f1, 0x4f704f5,
0x4fb04f9, 0x4ff04fd, 0x5030501, 0x5070505,
0x50b0509, 0x50f050d, 0x5130511, 0x5160515,
0x51a0518, 0x51e051c, 0x5210520, 0x5250523,
0x5290527, 0x52c052a, 0x52f052e, 0x5330531,
0x5360535, 0x53a0538, 0x53d053b, 0x540053f,
0x5440542, 0x5470545, 0x54a0548, 0x54d054c,
0x550054f, 0x5530552, 0x5560555, 0x5590558,
0x55c055b, 0x55f055e, 0x5620561, 0x5650564,
0x5680567, 0x56b056a, 0x56e056d, 0x571056f,
0x5740572, 0x5760575, 0x5790578, 0x57c057b,
0x57f057d, 0x5810580, 0x5840583, 0x5870585,
0x5890588, 0x58c058b, 0x58f058d
};
static u32 brightness_part12[] = { 0x58f058d, 0x59d058f };
/*
* lookup table for display brightness 103.3 to 510 nits
* */
static u32 brightness_part2[] = {
0x59d058f, 0x5b805ab, 0x5d105c5, 0x5e805dd,
0x5fe05f3, 0x6120608, 0x625061c, 0x637062e,
0x6480640, 0x6580650, 0x6680660, 0x677066f,
0x685067e, 0x693068c, 0x6a00699, 0x6ac06a6,
0x6b806b2, 0x6c406be, 0x6cf06ca, 0x6da06d5,
0x6e506df, 0x6ef06ea, 0x6f906f4, 0x70206fe,
0x70c0707, 0x7150710, 0x71e0719, 0x7260722,
0x72f072a, 0x7370733, 0x73f073b, 0x7470743,
0x74e074a, 0x7560752, 0x75d0759, 0x7640760,
0x76b0768, 0x772076e, 0x7780775, 0x77f077c,
0x7850782, 0x78c0789, 0x792078f, 0x7980795,
0x79e079b, 0x7a407a1, 0x7aa07a7, 0x7af07ac,
0x7b507b2, 0x7ba07b8, 0x7c007bd, 0x7c507c2,
0x7ca07c8, 0x7cf07cd, 0x7d407d2, 0x7d907d7,
0x7de07dc, 0x7e307e1, 0x7e807e5, 0x7ec07ea,
0x7f107ef, 0x7f607f3, 0x7fa07f8, 0x7fe07fc
};
static int dcp_get_brightness(struct backlight_device *bd)
{
struct apple_dcp *dcp = bl_get_data(bd);
return dcp->brightness.nits;
}
#define SCALE_FACTOR (1 << 10)
static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size)
{
u32 frac;
u64 low, high;
u32 interpolated = (tbl_size - 1) * ((val - min) * SCALE_FACTOR) / (max - min);
size_t index = interpolated / SCALE_FACTOR;
if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u", index, val))
return tbl[tbl_size / 2];
frac = interpolated & (SCALE_FACTOR - 1);
low = tbl[index];
high = tbl[index + 1];
return ((frac * high) + ((SCALE_FACTOR - frac) * low)) / SCALE_FACTOR;
}
static u32 calculate_dac(struct apple_dcp *dcp, int val)
{
u32 dac;
if (val <= MIN_BRIGHTNESS_PART1)
return 16 * brightness_part1[0];
else if (val == MAX_BRIGHTNESS_PART1)
return 16 * brightness_part1[ARRAY_SIZE(brightness_part1) - 1];
else if (val == MIN_BRIGHTNESS_PART2)
return 16 * brightness_part2[0];
else if (val >= MAX_BRIGHTNESS_PART2)
return brightness_part2[ARRAY_SIZE(brightness_part2) - 1];
if (val < MAX_BRIGHTNESS_PART1) {
dac = interpolate(val, MIN_BRIGHTNESS_PART1, MAX_BRIGHTNESS_PART1,
brightness_part1, ARRAY_SIZE(brightness_part1));
} else if (val > MIN_BRIGHTNESS_PART2) {
dac = interpolate(val, MIN_BRIGHTNESS_PART2, MAX_BRIGHTNESS_PART2,
brightness_part2, ARRAY_SIZE(brightness_part2));
} else {
dac = interpolate(val, MAX_BRIGHTNESS_PART1, MIN_BRIGHTNESS_PART2,
brightness_part12, ARRAY_SIZE(brightness_part12));
}
return 16 * dac;
}
static int drm_crtc_set_brightness(struct apple_dcp *dcp)
{
struct drm_atomic_state *state;
struct drm_crtc_state *crtc_state;
struct drm_modeset_acquire_ctx ctx;
struct drm_crtc *crtc = &dcp->crtc->base;
int ret = 0;
DRM_MODESET_LOCK_ALL_BEGIN(crtc->dev, ctx, 0, ret);
if (!dcp->brightness.update)
goto done;
state = drm_atomic_state_alloc(crtc->dev);
if (!state)
return -ENOMEM;
state->acquire_ctx = &ctx;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state)) {
ret = PTR_ERR(crtc_state);
goto fail;
}
crtc_state->color_mgmt_changed |= true;
ret = drm_atomic_commit(state);
fail:
drm_atomic_state_put(state);
done:
DRM_MODESET_LOCK_ALL_END(crtc->dev, ctx, ret);
return ret;
}
static int dcp_set_brightness(struct backlight_device *bd)
{
int ret = 0;
struct apple_dcp *dcp = bl_get_data(bd);
struct drm_modeset_acquire_ctx ctx;
int brightness = backlight_get_brightness(bd);
DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
dcp->brightness.dac = calculate_dac(dcp, brightness);
dcp->brightness.update = true;
DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
/*
* Do not actively try to change brightness if no mode is set.
* TODO: should this be reflected the in backlight's power property?
* defer this hopefully until it becomes irrelevant due to proper
* drm integrated backlight handling
*/
if (!dcp->valid_mode)
return 0;
/* Wait 1 vblank cycle in the hope an atomic swap has already updated
* the brightness */
drm_msleep((1001 + 23) / 24); // 42ms for 23.976 fps
return drm_crtc_set_brightness(dcp);
}
static const struct backlight_ops dcp_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.get_brightness = dcp_get_brightness,
.update_status = dcp_set_brightness,
};
int dcp_backlight_register(struct apple_dcp *dcp)
{
struct device *dev = dcp->dev;
struct backlight_device *bl_dev;
struct backlight_properties props = {
.type = BACKLIGHT_PLATFORM,
.brightness = dcp->brightness.nits,
.scale = BACKLIGHT_SCALE_LINEAR,
};
props.max_brightness = min(dcp->brightness.maximum, MAX_BRIGHTNESS_PART2 - 1);
bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp,
&dcp_backlight_ops, &props);
if (IS_ERR(bl_dev))
return PTR_ERR(bl_dev);
dcp->brightness.bl_dev = bl_dev;
dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits);
return 0;
}

View file

@ -0,0 +1,604 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/phy/phy.h>
#include <linux/delay.h>
#include "afk.h"
#include "dcp.h"
#include "dptxep.h"
#include "parser.h"
#include "trace.h"
struct dcpdptx_connection_cmd {
__le32 unk;
__le32 target;
} __attribute__((packed));
struct dcpdptx_hotplug_cmd {
u8 _pad0[16];
__le32 unk;
} __attribute__((packed));
struct dptxport_apcall_link_rate {
__le32 retcode;
u8 _unk0[12];
__le32 link_rate;
u8 _unk1[12];
} __attribute__((packed));
struct dptxport_apcall_lane_count {
__le32 retcode;
u8 _unk0[12];
__le64 lane_count;
u8 _unk1[8];
} __attribute__((packed));
struct dptxport_apcall_set_active_lane_count {
__le32 retcode;
u8 _unk0[12];
__le64 lane_count;
u8 _unk1[8];
} __packed;
struct dptxport_apcall_get_support {
__le32 retcode;
u8 _unk0[12];
__le32 supported;
u8 _unk1[12];
} __attribute__((packed));
struct dptxport_apcall_max_drive_settings {
__le32 retcode;
u8 _unk0[12];
__le32 max_drive_settings[2];
u8 _unk1[8];
};
struct dptxport_apcall_drive_settings {
__le32 retcode;
u8 _unk0[12];
__le32 unk1;
__le32 unk2;
__le32 unk3;
__le32 unk4;
__le32 unk5;
__le32 unk6;
__le32 unk7;
};
int dptxport_validate_connection(struct apple_epic_service *service, u8 core,
u8 atc, u8 die)
{
struct dptx_port *dptx = service->cookie;
struct dcpdptx_connection_cmd cmd, resp;
int ret;
u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) |
FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) |
FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) |
DCPDPTX_REMOTE_PORT_CONNECTED;
trace_dptxport_validate_connection(dptx, core, atc, die);
cmd.target = cpu_to_le32(target);
cmd.unk = cpu_to_le32(0x100);
ret = afk_service_call(service, 0, 12, &cmd, sizeof(cmd), 40, &resp,
sizeof(resp), 40);
if (ret)
return ret;
if (le32_to_cpu(resp.target) != target)
return -EINVAL;
if (le32_to_cpu(resp.unk) != 0x100)
return -EINVAL;
return 0;
}
int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
u8 die)
{
struct dptx_port *dptx = service->cookie;
struct dcpdptx_connection_cmd cmd, resp;
u32 unk_field = 0x0; // seen as 0x100 under some conditions
int ret;
u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) |
FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) |
FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) |
DCPDPTX_REMOTE_PORT_CONNECTED;
trace_dptxport_connect(dptx, core, atc, die);
cmd.target = cpu_to_le32(target);
cmd.unk = cpu_to_le32(unk_field);
ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp,
sizeof(resp), 24);
if (ret)
return ret;
if (le32_to_cpu(resp.target) != target)
return -EINVAL;
if (le32_to_cpu(resp.unk) != unk_field)
dev_notice(service->ep->dcp->dev, "unexpected unk field in reply: 0x%x (0x%x)\n",
le32_to_cpu(resp.unk), unk_field);
return 0;
}
int dptxport_request_display(struct apple_epic_service *service)
{
return afk_service_call(service, 0, 6, NULL, 0, 16, NULL, 0, 16);
}
int dptxport_release_display(struct apple_epic_service *service)
{
return afk_service_call(service, 0, 7, NULL, 0, 16, NULL, 0, 16);
}
int dptxport_set_hpd(struct apple_epic_service *service, bool hpd)
{
struct dcpdptx_hotplug_cmd cmd, resp;
int ret;
memset(&cmd, 0, sizeof(cmd));
if (hpd)
cmd.unk = cpu_to_le32(1);
ret = afk_service_call(service, 8, 8, &cmd, sizeof(cmd), 12, &resp,
sizeof(resp), 12);
if (ret)
return ret;
if (le32_to_cpu(resp.unk) != 1)
return -EINVAL;
return 0;
}
static int
dptxport_call_get_max_drive_settings(struct apple_epic_service *service,
void *reply_, size_t reply_size)
{
struct dptxport_apcall_max_drive_settings *reply = reply_;
if (reply_size < sizeof(*reply))
return -EINVAL;
reply->retcode = cpu_to_le32(0);
reply->max_drive_settings[0] = cpu_to_le32(0x3);
reply->max_drive_settings[1] = cpu_to_le32(0x3);
return 0;
}
static int
dptxport_call_get_drive_settings(struct apple_epic_service *service,
const void *request_, size_t request_size,
void *reply_, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
const struct dptxport_apcall_drive_settings *request = request_;
struct dptxport_apcall_drive_settings *reply = reply_;
if (reply_size < sizeof(*reply) || request_size < sizeof(*request))
return -EINVAL;
*reply = *request;
/* Clear the rest of the buffer */
memset(reply_ + sizeof(*reply), 0, reply_size - sizeof(*reply));
if (reply->retcode != 4)
dev_err(service->ep->dcp->dev,
"get_drive_settings: unexpected retcode %d\n",
reply->retcode);
reply->retcode = 4; /* Should already be 4? */
reply->unk5 = dptx->drive_settings[0];
reply->unk6 = 0;
reply->unk7 = dptx->drive_settings[1];
return 0;
}
static int
dptxport_call_set_drive_settings(struct apple_epic_service *service,
const void *request_, size_t request_size,
void *reply_, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
const struct dptxport_apcall_drive_settings *request = request_;
struct dptxport_apcall_drive_settings *reply = reply_;
if (reply_size < sizeof(*reply) || request_size < sizeof(*request))
return -EINVAL;
*reply = *request;
reply->retcode = cpu_to_le32(0);
dev_info(service->ep->dcp->dev, "set_drive_settings: %d:%d:%d:%d:%d:%d:%d\n",
request->unk1, request->unk2, request->unk3, request->unk4,
request->unk5, request->unk6, request->unk7);
dptx->drive_settings[0] = reply->unk5;
dptx->drive_settings[1] = reply->unk7;
return 0;
}
static int dptxport_call_get_max_link_rate(struct apple_epic_service *service,
void *reply_, size_t reply_size)
{
struct dptxport_apcall_link_rate *reply = reply_;
if (reply_size < sizeof(*reply))
return -EINVAL;
reply->retcode = cpu_to_le32(0);
reply->link_rate = cpu_to_le32(LINK_RATE_HBR3);
return 0;
}
static int dptxport_call_get_max_lane_count(struct apple_epic_service *service,
void *reply_, size_t reply_size)
{
struct dptxport_apcall_lane_count *reply = reply_;
if (reply_size < sizeof(*reply))
return -EINVAL;
reply->retcode = cpu_to_le32(0);
reply->lane_count = cpu_to_le64(4);
return 0;
}
static int dptxport_call_set_active_lane_count(struct apple_epic_service *service,
const void *data, size_t data_size,
void *reply_, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
const struct dptxport_apcall_set_active_lane_count *request = data;
struct dptxport_apcall_set_active_lane_count *reply = reply_;
int ret = 0;
int retcode = 0;
if (reply_size < sizeof(*reply))
return -1;
if (data_size < sizeof(*request))
return -1;
u64 lane_count = cpu_to_le64(request->lane_count);
switch (lane_count) {
case 0 ... 2:
case 4:
dptx->phy_ops.dp.lanes = lane_count;
dptx->phy_ops.dp.set_lanes = 1;
break;
default:
dev_err(service->ep->dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count);
retcode = 1;
lane_count = 0;
break;
}
if (dptx->phy_ops.dp.set_lanes) {
if (dptx->atcphy) {
ret = phy_configure(dptx->atcphy, &dptx->phy_ops);
if (ret)
return ret;
}
dptx->phy_ops.dp.set_lanes = 0;
}
dptx->lane_count = lane_count;
reply->retcode = cpu_to_le32(retcode);
reply->lane_count = cpu_to_le64(lane_count);
if (dptx->lane_count > 0)
complete(&dptx->linkcfg_completion);
return ret;
}
static int dptxport_call_get_link_rate(struct apple_epic_service *service,
void *reply_, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
struct dptxport_apcall_link_rate *reply = reply_;
if (reply_size < sizeof(*reply))
return -EINVAL;
reply->retcode = cpu_to_le32(0);
reply->link_rate = cpu_to_le32(dptx->link_rate);
return 0;
}
static int
dptxport_call_will_change_link_config(struct apple_epic_service *service)
{
struct dptx_port *dptx = service->cookie;
dptx->phy_ops.dp.set_lanes = 0;
dptx->phy_ops.dp.set_rate = 0;
dptx->phy_ops.dp.set_voltages = 0;
return 0;
}
static int
dptxport_call_did_change_link_config(struct apple_epic_service *service)
{
/* assume the link config did change and wait a little bit */
mdelay(10);
return 0;
}
static int dptxport_call_set_link_rate(struct apple_epic_service *service,
const void *data, size_t data_size,
void *reply_, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
const struct dptxport_apcall_link_rate *request = data;
struct dptxport_apcall_link_rate *reply = reply_;
u32 link_rate, phy_link_rate;
bool phy_set_rate = false;
int ret;
if (reply_size < sizeof(*reply))
return -EINVAL;
if (data_size < sizeof(*request))
return -EINVAL;
link_rate = le32_to_cpu(request->link_rate);
trace_dptxport_call_set_link_rate(dptx, link_rate);
switch (link_rate) {
case LINK_RATE_RBR:
phy_link_rate = 1620;
phy_set_rate = true;
break;
case LINK_RATE_HBR:
phy_link_rate = 2700;
phy_set_rate = true;
break;
case LINK_RATE_HBR2:
phy_link_rate = 5400;
phy_set_rate = true;
break;
case LINK_RATE_HBR3:
phy_link_rate = 8100;
phy_set_rate = true;
break;
case 0:
phy_link_rate = 0;
phy_set_rate = true;
break;
default:
dev_err(service->ep->dcp->dev,
"DPTXPort: Unsupported link rate 0x%x requested\n",
link_rate);
link_rate = 0;
phy_set_rate = false;
break;
}
if (phy_set_rate) {
dptx->phy_ops.dp.link_rate = phy_link_rate;
dptx->phy_ops.dp.set_rate = 1;
if (dptx->atcphy) {
ret = phy_configure(dptx->atcphy, &dptx->phy_ops);
if (ret)
return ret;
}
//if (dptx->phy_ops.dp.set_rate)
dptx->link_rate = dptx->pending_link_rate = link_rate;
}
//dptx->pending_link_rate = link_rate;
reply->retcode = cpu_to_le32(0);
reply->link_rate = cpu_to_le32(link_rate);
return 0;
}
static int dptxport_call_get_supports_hpd(struct apple_epic_service *service,
void *reply_, size_t reply_size)
{
struct dptxport_apcall_get_support *reply = reply_;
if (reply_size < sizeof(*reply))
return -EINVAL;
reply->retcode = cpu_to_le32(0);
reply->supported = cpu_to_le32(0);
return 0;
}
static int
dptxport_call_get_supports_downspread(struct apple_epic_service *service,
void *reply_, size_t reply_size)
{
struct dptxport_apcall_get_support *reply = reply_;
if (reply_size < sizeof(*reply))
return -EINVAL;
reply->retcode = cpu_to_le32(0);
reply->supported = cpu_to_le32(0);
return 0;
}
static int
dptxport_call_activate(struct apple_epic_service *service,
const void *data, size_t data_size,
void *reply, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
const struct apple_dcp *dcp = service->ep->dcp;
// TODO: hack, use phy_set_mode to select the correct DCP(EXT) input
phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index);
memcpy(reply, data, min(reply_size, data_size));
if (reply_size >= 4)
memset(reply, 0, 4);
return 0;
}
static int
dptxport_call_deactivate(struct apple_epic_service *service,
const void *data, size_t data_size,
void *reply, size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
/* deactivate phy */
phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0);
memcpy(reply, data, min(reply_size, data_size));
if (reply_size >= 4)
memset(reply, 0, 4);
return 0;
}
static int dptxport_call(struct apple_epic_service *service, u32 idx,
const void *data, size_t data_size, void *reply,
size_t reply_size)
{
struct dptx_port *dptx = service->cookie;
trace_dptxport_apcall(dptx, idx, data_size);
switch (idx) {
case DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG:
return dptxport_call_will_change_link_config(service);
case DPTX_APCALL_DID_CHANGE_LINK_CONFIG:
return dptxport_call_did_change_link_config(service);
case DPTX_APCALL_GET_MAX_LINK_RATE:
return dptxport_call_get_max_link_rate(service, reply,
reply_size);
case DPTX_APCALL_GET_LINK_RATE:
return dptxport_call_get_link_rate(service, reply, reply_size);
case DPTX_APCALL_SET_LINK_RATE:
return dptxport_call_set_link_rate(service, data, data_size,
reply, reply_size);
case DPTX_APCALL_GET_MAX_LANE_COUNT:
return dptxport_call_get_max_lane_count(service, reply, reply_size);
case DPTX_APCALL_SET_ACTIVE_LANE_COUNT:
return dptxport_call_set_active_lane_count(service, data, data_size,
reply, reply_size);
case DPTX_APCALL_GET_SUPPORTS_HPD:
return dptxport_call_get_supports_hpd(service, reply,
reply_size);
case DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD:
return dptxport_call_get_supports_downspread(service, reply,
reply_size);
case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS:
return dptxport_call_get_max_drive_settings(service, reply,
reply_size);
case DPTX_APCALL_GET_DRIVE_SETTINGS:
return dptxport_call_get_drive_settings(service, data, data_size,
reply, reply_size);
case DPTX_APCALL_SET_DRIVE_SETTINGS:
return dptxport_call_set_drive_settings(service, data, data_size,
reply, reply_size);
case DPTX_APCALL_ACTIVATE:
return dptxport_call_activate(service, data, data_size,
reply, reply_size);
case DPTX_APCALL_DEACTIVATE:
return dptxport_call_deactivate(service, data, data_size,
reply, reply_size);
default:
/* just try to ACK and hope for the best... */
dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n",
idx);
memcpy(reply, data, min(reply_size, data_size));
if (reply_size >= 4)
memset(reply, 0, 4);
return 0;
}
}
static void dptxport_init(struct apple_epic_service *service, const char *name,
const char *class, s64 unit)
{
if (strcmp(name, "dcpdptx-port-epic"))
return;
if (strcmp(class, "AppleDCPDPTXRemotePort"))
return;
trace_dptxport_init(service->ep->dcp, unit);
switch (unit) {
case 0:
case 1:
if (service->ep->dcp->dptxport[unit].enabled) {
dev_err(service->ep->dcp->dev,
"DPTXPort: unit %lld already exists\n", unit);
return;
}
service->ep->dcp->dptxport[unit].unit = unit;
service->ep->dcp->dptxport[unit].service = service;
service->ep->dcp->dptxport[unit].enabled = true;
service->cookie = (void *)&service->ep->dcp->dptxport[unit];
complete(&service->ep->dcp->dptxport[unit].enable_completion);
break;
default:
dev_err(service->ep->dcp->dev, "DPTXPort: invalid unit %lld\n",
unit);
}
}
static const struct apple_epic_service_ops dptxep_ops[] = {
{
.name = "AppleDCPDPTXRemotePort",
.init = dptxport_init,
.call = dptxport_call,
},
{}
};
int dptxep_init(struct apple_dcp *dcp)
{
int ret;
u32 port;
unsigned long timeout = msecs_to_jiffies(1000);
init_completion(&dcp->dptxport[0].enable_completion);
init_completion(&dcp->dptxport[1].enable_completion);
init_completion(&dcp->dptxport[0].linkcfg_completion);
init_completion(&dcp->dptxport[1].linkcfg_completion);
dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops);
if (IS_ERR(dcp->dptxep))
return PTR_ERR(dcp->dptxep);
ret = afk_start(dcp->dptxep);
if (ret)
return ret;
for (port = 0; port < dcp->hw.num_dptx_ports; port++) {
ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion,
timeout);
if (!ret)
return -ETIMEDOUT;
else if (ret < 0)
return ret;
timeout = ret;
}
return 0;
}

View file

@ -0,0 +1,70 @@
#ifndef __APPLE_DCP_DPTXEP_H__
#define __APPLE_DCP_DPTXEP_H__
#include <linux/phy/phy.h>
#include <linux/mux/consumer.h>
enum dptx_apcall {
DPTX_APCALL_ACTIVATE = 0,
DPTX_APCALL_DEACTIVATE = 1,
DPTX_APCALL_GET_MAX_DRIVE_SETTINGS = 2,
DPTX_APCALL_SET_DRIVE_SETTINGS = 3,
DPTX_APCALL_GET_DRIVE_SETTINGS = 4,
DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG = 5,
DPTX_APCALL_DID_CHANGE_LINK_CONFIG = 6,
DPTX_APCALL_GET_MAX_LINK_RATE = 7,
DPTX_APCALL_GET_LINK_RATE = 8,
DPTX_APCALL_SET_LINK_RATE = 9,
DPTX_APCALL_GET_MAX_LANE_COUNT = 10,
DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 11,
DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 12,
DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 13,
DPTX_APCALL_GET_DOWN_SPREAD = 14,
DPTX_APCALL_SET_DOWN_SPREAD = 15,
DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 16,
DPTX_APCALL_SET_LANE_MAP = 17,
DPTX_APCALL_GET_SUPPORTS_HPD = 18,
DPTX_APCALL_FORCE_HOTPLUG_DETECT = 19,
DPTX_APCALL_INACTIVE_SINK_DETECTED = 20,
DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 21,
DPTX_APCALL_DEVICE_NOT_RESPONDING = 22,
DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 23,
DPTX_APCALL_DEVICE_NOT_STARTED = 24,
};
#define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0)
#define DCPDPTX_REMOTE_PORT_ATC GENMASK(7, 4)
#define DCPDPTX_REMOTE_PORT_DIE GENMASK(11, 8)
#define DCPDPTX_REMOTE_PORT_CONNECTED BIT(15)
enum dptx_link_rate {
LINK_RATE_RBR = 0x06,
LINK_RATE_HBR = 0x0a,
LINK_RATE_HBR2 = 0x14,
LINK_RATE_HBR3 = 0x1e,
};
struct apple_epic_service;
struct dptx_port {
bool enabled, connected;
struct completion enable_completion;
struct completion linkcfg_completion;
u32 unit;
struct apple_epic_service *service;
union phy_configure_opts phy_ops;
struct phy *atcphy;
struct mux_control *mux;
u32 lane_count;
u32 link_rate, pending_link_rate;
u32 drive_settings[2];
};
int dptxport_validate_connection(struct apple_epic_service *service, u8 core,
u8 atc, u8 die);
int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
u8 die);
int dptxport_request_display(struct apple_epic_service *service);
int dptxport_release_display(struct apple_epic_service *service);
int dptxport_set_hpd(struct apple_epic_service *service, bool hpd);
#endif

View file

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2023 */
#include <linux/completion.h>
#include "afk.h"
#include "dcp.h"
static void disp_service_init(struct apple_epic_service *service, const char *name,
const char *class, s64 unit)
{
}
static const struct apple_epic_service_ops ibootep_ops[] = {
{
.name = "disp0-service",
.init = disp_service_init,
},
{}
};
int ibootep_init(struct apple_dcp *dcp)
{
dcp->ibootep = afk_init(dcp, DISP0_ENDPOINT, ibootep_ops);
afk_start(dcp->ibootep);
return 0;
}

View file

@ -0,0 +1,571 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
#include <linux/align.h>
#include <linux/bitmap.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/iommu.h>
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/soc/apple/rtkit.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "dcp.h"
#include "dcp-internal.h"
#include "iomfb.h"
#include "iomfb_internal.h"
#include "parser.h"
#include "trace.h"
static int dcp_tx_offset(enum dcp_context_id id)
{
switch (id) {
case DCP_CONTEXT_CB:
case DCP_CONTEXT_CMD:
return 0x00000;
case DCP_CONTEXT_OOBCB:
case DCP_CONTEXT_OOBCMD:
return 0x08000;
default:
return -EINVAL;
}
}
static int dcp_channel_offset(enum dcp_context_id id)
{
switch (id) {
case DCP_CONTEXT_ASYNC:
return 0x40000;
case DCP_CONTEXT_OOBASYNC:
return 0x48000;
case DCP_CONTEXT_CB:
return 0x60000;
case DCP_CONTEXT_OOBCB:
return 0x68000;
default:
return dcp_tx_offset(id);
}
}
static inline u64 dcpep_set_shmem(u64 dart_va)
{
return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_SET_SHMEM) |
FIELD_PREP(IOMFB_SHMEM_FLAG, IOMFB_SHMEM_FLAG_VALUE) |
FIELD_PREP(IOMFB_SHMEM_DVA, dart_va);
}
static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset)
{
return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_MSG) |
FIELD_PREP(IOMFB_MSG_CONTEXT, id) |
FIELD_PREP(IOMFB_MSG_OFFSET, offset) |
FIELD_PREP(IOMFB_MSG_LENGTH, length);
}
static inline u64 dcpep_ack(enum dcp_context_id id)
{
return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK;
}
/*
* A channel is busy if we have sent a message that has yet to be
* acked. The driver must not sent a message to a busy channel.
*/
static bool dcp_channel_busy(struct dcp_channel *ch)
{
return (ch->depth != 0);
}
/*
* Get the context ID passed to the DCP for a command we push. The rule is
* simple: callback contexts are used when replying to the DCP, command
* contexts are used otherwise. That corresponds to a non/zero call stack
* depth. This rule frees the caller from tracking the call context manually.
*/
static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob)
{
u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth;
if (depth)
return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB;
else
return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD;
}
/* Get a channel for a context */
static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp,
enum dcp_context_id context)
{
switch (context) {
case DCP_CONTEXT_CB:
return &dcp->ch_cb;
case DCP_CONTEXT_CMD:
return &dcp->ch_cmd;
case DCP_CONTEXT_OOBCB:
return &dcp->ch_oobcb;
case DCP_CONTEXT_OOBCMD:
return &dcp->ch_oobcmd;
case DCP_CONTEXT_ASYNC:
return &dcp->ch_async;
case DCP_CONTEXT_OOBASYNC:
return &dcp->ch_oobasync;
default:
return NULL;
}
}
/* Get the start of a packet: after the end of the previous packet */
static u16 dcp_packet_start(struct dcp_channel *ch, u8 depth)
{
if (depth > 0)
return ch->end[depth - 1];
else
return 0;
}
/* Pushes and pops the depth of the call stack with safety checks */
static u8 dcp_push_depth(u8 *depth)
{
u8 ret = (*depth)++;
WARN_ON(ret >= DCP_MAX_CALL_DEPTH);
return ret;
}
static u8 dcp_pop_depth(u8 *depth)
{
WARN_ON((*depth) == 0);
return --(*depth);
}
/* Call a DCP function given by a tag */
void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call,
u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
void *cookie)
{
enum dcp_context_id context = dcp_call_context(dcp, oob);
struct dcp_channel *ch = dcp_get_channel(dcp, context);
struct dcp_packet_header header = {
.in_len = in_len,
.out_len = out_len,
/* Tag is reversed due to endianness of the fourcc */
.tag[0] = call->tag[3],
.tag[1] = call->tag[2],
.tag[2] = call->tag[1],
.tag[3] = call->tag[0],
};
u8 depth = dcp_push_depth(&ch->depth);
u16 offset = dcp_packet_start(ch, depth);
void *out = dcp->shmem + dcp_tx_offset(context) + offset;
void *out_data = out + sizeof(header);
size_t data_len = sizeof(header) + in_len + out_len;
memcpy(out, &header, sizeof(header));
if (in_len > 0)
memcpy(out_data, data, in_len);
trace_iomfb_push(dcp, call, context, offset, depth);
ch->callbacks[depth] = cb;
ch->cookies[depth] = cookie;
ch->output[depth] = out + sizeof(header) + in_len;
ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT);
dcp_send_message(dcp, IOMFB_ENDPOINT,
dcpep_msg(context, data_len, offset));
}
/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
int dcp_parse_tag(char tag[4])
{
u32 d[3];
int i;
if (tag[3] != 'D')
return -EINVAL;
for (i = 0; i < 3; ++i) {
d[i] = (u32)(tag[i] - '0');
if (d[i] > 9)
return -EINVAL;
}
return d[0] + (d[1] * 10) + (d[2] * 100);
}
/* Ack a callback from the DCP */
void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
{
struct dcp_channel *ch = dcp_get_channel(dcp, context);
dcp_pop_depth(&ch->depth);
dcp_send_message(dcp, IOMFB_ENDPOINT,
dcpep_ack(context));
}
/*
* Helper to send a DRM hotplug event. The DCP is accessed from a single
* (RTKit) thread. To handle hotplug callbacks, we need to call
* drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and
* waits for vblank (a DCP callback). That means we deadlock if we call from
* the RTKit thread! Instead, move the call to another thread via a workqueue.
*/
void dcp_hotplug(struct work_struct *work)
{
struct apple_connector *connector;
struct apple_dcp *dcp;
connector = container_of(work, struct apple_connector, hotplug_wq);
dcp = platform_get_drvdata(connector->dcp);
dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__,
connector->connected, dcp->valid_mode, dcp->nr_modes);
/*
* DCP defers link training until we set a display mode. But we set
* display modes from atomic_flush, so userspace needs to trigger a
* flush, or the CRTC gets no signal.
*/
if (connector->base.state && !dcp->valid_mode && connector->connected)
drm_connector_set_link_status_property(&connector->base,
DRM_MODE_LINK_STATUS_BAD);
drm_kms_helper_connector_hotplug_event(&connector->base);
}
EXPORT_SYMBOL_GPL(dcp_hotplug);
static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
void *data, u32 length, u16 offset)
{
struct device *dev = dcp->dev;
struct dcp_packet_header *hdr = data;
void *in, *out;
int tag = dcp_parse_tag(hdr->tag);
struct dcp_channel *ch = dcp_get_channel(dcp, context);
u8 depth;
if (tag < 0 || tag >= IOMFB_MAX_CB || !dcp->cb_handlers || !dcp->cb_handlers[tag]) {
dev_warn(dev, "received unknown callback %c%c%c%c\n",
hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]);
return;
}
in = data + sizeof(*hdr);
out = in + hdr->in_len;
// TODO: verify that in_len and out_len match our prototypes
// for now just clear the out data to have at least consistant results
if (hdr->out_len)
memset(out, 0, hdr->out_len);
depth = dcp_push_depth(&ch->depth);
ch->output[depth] = out;
ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT);
if (dcp->cb_handlers[tag](dcp, tag, out, in))
dcp_ack(dcp, context);
}
static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
void *data, u32 length)
{
struct dcp_packet_header *header = data;
struct dcp_channel *ch = dcp_get_channel(dcp, context);
void *cookie;
dcp_callback_t cb;
if (!ch) {
dev_warn(dcp->dev, "ignoring ack on context %X\n", context);
return;
}
dcp_pop_depth(&ch->depth);
cb = ch->callbacks[ch->depth];
cookie = ch->cookies[ch->depth];
ch->callbacks[ch->depth] = NULL;
ch->cookies[ch->depth] = NULL;
if (cb)
cb(dcp, data + sizeof(*header) + header->in_len, cookie);
}
static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
{
enum dcp_context_id ctx_id;
u16 offset;
u32 length;
int channel_offset;
void *data;
ctx_id = FIELD_GET(IOMFB_MSG_CONTEXT, message);
offset = FIELD_GET(IOMFB_MSG_OFFSET, message);
length = FIELD_GET(IOMFB_MSG_LENGTH, message);
channel_offset = dcp_channel_offset(ctx_id);
if (channel_offset < 0) {
dev_warn(dcp->dev, "invalid context received %u", ctx_id);
return;
}
data = dcp->shmem + channel_offset + offset;
if (FIELD_GET(IOMFB_MSG_ACK, message))
dcpep_handle_ack(dcp, ctx_id, data, length);
else
dcpep_handle_cb(dcp, ctx_id, data, length, offset);
}
/*
* DRM specifies rectangles as start and end coordinates. DCP specifies
* rectangles as a start coordinate and a width/height. Convert a DRM rectangle
* to a DCP rectangle.
*/
struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
{
return (struct dcp_rect){ .x = rect->x1,
.y = rect->y1,
.w = drm_rect_width(rect),
.h = drm_rect_height(rect) };
}
u32 drm_format_to_dcp(u32 drm)
{
switch (drm) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
return fourcc_code('A', 'R', 'G', 'B');
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
return fourcc_code('A', 'B', 'G', 'R');
case DRM_FORMAT_XRGB2101010:
return fourcc_code('r', '0', '3', 'w');
}
pr_warn("DRM format %X not supported in DCP\n", drm);
return 0;
}
int dcp_get_modes(struct drm_connector *connector)
{
struct apple_connector *apple_connector = to_apple_connector(connector);
struct platform_device *pdev = apple_connector->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
struct drm_device *dev = connector->dev;
struct drm_display_mode *mode;
int i;
for (i = 0; i < dcp->nr_modes; ++i) {
mode = drm_mode_duplicate(dev, &dcp->modes[i].mode);
if (!mode) {
dev_err(dev->dev, "Failed to duplicate display mode\n");
return 0;
}
drm_mode_probed_add(connector, mode);
}
return dcp->nr_modes;
}
EXPORT_SYMBOL_GPL(dcp_get_modes);
/* The user may own drm_display_mode, so we need to search for our copy */
struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
const struct drm_display_mode *mode)
{
int i;
for (i = 0; i < dcp->nr_modes; ++i) {
if (drm_mode_match(mode, &dcp->modes[i].mode,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK))
return &dcp->modes[i];
}
return NULL;
}
int dcp_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct apple_connector *apple_connector = to_apple_connector(connector);
struct platform_device *pdev = apple_connector->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD;
}
EXPORT_SYMBOL_GPL(dcp_mode_valid);
int dcp_crtc_atomic_modeset(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
struct apple_dcp *dcp = platform_get_drvdata(apple_crtc->dcp);
struct drm_crtc_state *crtc_state;
int ret = -EIO;
bool modeset;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (!crtc_state)
return 0;
modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
if (!modeset)
return 0;
/* ignore no mode, poweroff is handled elsewhere */
if (crtc_state->mode.hdisplay == 0 && crtc_state->mode.vdisplay == 0)
return 0;
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
ret = iomfb_modeset_v12_3(dcp, crtc_state);
break;
case DCP_FIRMWARE_V_13_5:
ret = iomfb_modeset_v13_3(dcp, crtc_state);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n",
dcp->fw_compat);
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(dcp_crtc_atomic_modeset);
bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
struct platform_device *pdev = apple_crtc->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
/* TODO: support synthesized modes through scaling */
return lookup_mode(dcp, mode) != NULL;
}
EXPORT_SYMBOL(dcp_crtc_mode_fixup);
void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
if (dcp_channel_busy(&dcp->ch_cmd))
{
dev_err(dcp->dev, "unexpected busy command channel");
/* HACK: issue a delayed vblank event to avoid timeouts in
* drm_atomic_helper_wait_for_vblanks().
*/
schedule_work(&dcp->vblank_wq);
return;
}
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_flush_v12_3(dcp, crtc, state);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_flush_v13_3(dcp, crtc, state);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}
EXPORT_SYMBOL_GPL(dcp_flush);
static void iomfb_start(struct apple_dcp *dcp)
{
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_start_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_start_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}
bool dcp_is_initialized(struct platform_device *pdev)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
return dcp->active;
}
EXPORT_SYMBOL_GPL(dcp_is_initialized);
void iomfb_recv_msg(struct apple_dcp *dcp, u64 message)
{
enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message);
if (type == IOMFB_MESSAGE_TYPE_INITIALIZED)
iomfb_start(dcp);
else if (type == IOMFB_MESSAGE_TYPE_MSG)
dcpep_got_msg(dcp, message);
else
dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message);
}
int iomfb_start_rtkit(struct apple_dcp *dcp)
{
dma_addr_t shmem_iova;
apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT);
dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova,
GFP_KERNEL);
dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova));
return 0;
}
void iomfb_shutdown(struct apple_dcp *dcp)
{
/* We're going down */
dcp->active = false;
dcp->valid_mode = false;
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_shutdown_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_shutdown_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}

View file

@ -0,0 +1,432 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
#ifndef __APPLE_DCPEP_H__
#define __APPLE_DCPEP_H__
#include <linux/types.h>
#include "version_utils.h"
/* Fixed size of shared memory between DCP and AP */
#define DCP_SHMEM_SIZE 0x100000
/* DCP message contexts */
enum dcp_context_id {
/* Callback */
DCP_CONTEXT_CB = 0,
/* Command */
DCP_CONTEXT_CMD = 2,
/* Asynchronous */
DCP_CONTEXT_ASYNC = 3,
/* Out-of-band callback */
DCP_CONTEXT_OOBCB = 4,
/* Out-of-band command */
DCP_CONTEXT_OOBCMD = 6,
/* Out-of-band Asynchronous */
DCP_CONTEXT_OOBASYNC = 7,
DCP_NUM_CONTEXTS
};
/* RTKit endpoint message types */
enum dcpep_type {
/* Set shared memory */
IOMFB_MESSAGE_TYPE_SET_SHMEM = 0,
/* DCP is initialized */
IOMFB_MESSAGE_TYPE_INITIALIZED = 1,
/* Remote procedure call */
IOMFB_MESSAGE_TYPE_MSG = 2,
};
#define IOMFB_MESSAGE_TYPE GENMASK_ULL( 3, 0)
/* Message */
#define IOMFB_MSG_LENGTH GENMASK_ULL(63, 32)
#define IOMFB_MSG_OFFSET GENMASK_ULL(31, 16)
#define IOMFB_MSG_CONTEXT GENMASK_ULL(11, 8)
#define IOMFB_MSG_ACK BIT_ULL(6)
/* Set shmem */
#define IOMFB_SHMEM_DVA GENMASK_ULL(63, 16)
#define IOMFB_SHMEM_FLAG GENMASK_ULL( 7, 4)
#define IOMFB_SHMEM_FLAG_VALUE 4
struct dcp_packet_header {
char tag[4];
u32 in_len;
u32 out_len;
} __packed;
#define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0)
#define DCP_PACKET_ALIGNMENT (0x40)
enum iomfb_property_id {
IOMFB_PROPERTY_NITS = 15, // divide by Brightness_Scale
};
#define IOMFB_BRIGHTNESS_MIN 0x10000000
/* Structures used in v12.0 firmware */
#define SWAP_SURFACES 4
#define MAX_PLANES 3
enum dcp_colorspace {
DCP_COLORSPACE_BG_SRGB = 0,
DCP_COLORSPACE_BG_BT2020 = 9,
DCP_COLORSPACE_NATIVE = 12,
};
enum dcp_xfer_func {
DCP_XFER_FUNC_SDR = 13,
DCP_XFER_FUNC_HDR = 16,
};
struct dcp_iouserclient {
/* Handle for the IOUserClient. macOS sets this to a kernel VA. */
u64 handle;
u32 unk;
u8 flag1;
u8 flag2;
u8 padding[2];
} __packed;
struct dcp_rect {
u32 x;
u32 y;
u32 w;
u32 h;
} __packed;
/*
* Update background color to struct dcp_swap.bg_color
*/
#define IOMFB_SET_BACKGROUND BIT(31)
/* Information describing a plane of a planar compressed surface */
struct dcp_plane_info {
u32 width;
u32 height;
u32 base;
u32 offset;
u32 stride;
u32 size;
u16 tile_size;
u8 tile_w;
u8 tile_h;
u32 unk[13];
} __packed;
struct dcp_component_types {
u8 count;
u8 types[7];
} __packed;
struct dcp_allocate_bandwidth_req {
u64 unk1;
u64 unk2;
u64 unk3;
u8 unk1_null;
u8 unk2_null;
u8 padding[8];
} __packed;
struct dcp_allocate_bandwidth_resp {
u64 unk1;
u64 unk2;
u32 ret;
} __packed;
struct dcp_rt_bandwidth {
u64 unk1;
u64 reg_scratch;
u64 reg_doorbell;
u32 unk2;
u32 doorbell_bit;
u32 padding[7];
} __packed;
struct frame_sync_props {
u8 unk[28];
};
struct dcp_set_frame_sync_props_req {
struct frame_sync_props props;
u8 frame_sync_props_null;
u8 padding[3];
} __packed;
struct dcp_set_frame_sync_props_resp {
struct frame_sync_props props;
} __packed;
/* Method calls */
enum dcpep_method {
dcpep_late_init_signal,
dcpep_setup_video_limits,
dcpep_set_create_dfb,
dcpep_start_signal,
dcpep_swap_start,
dcpep_swap_submit,
dcpep_set_display_device,
dcpep_set_digital_out_mode,
dcpep_create_default_fb,
dcpep_set_display_refresh_properties,
dcpep_flush_supports_power,
dcpep_set_power_state,
dcpep_first_client_open,
dcpep_set_parameter_dcp,
dcpep_enable_disable_video_power_savings,
dcpep_is_main_display,
iomfbep_a131_pmu_service_matched,
iomfbep_a132_backlight_service_matched,
iomfbep_a358_vi_set_temperature_hint,
iomfbep_get_color_remap_mode,
iomfbep_last_client_close,
iomfbep_abort_swaps_dcp,
iomfbep_set_matrix,
dcpep_num_methods
};
#define IOMFB_METHOD(tag, name) [name] = { #name, tag }
struct dcp_method_entry {
const char *name;
char tag[4];
};
#define IOMFB_MAX_CB (1000)
struct apple_dcp;
typedef bool (*iomfb_cb_handler)(struct apple_dcp *, int, void *, void *);
/* Prototypes */
struct dcp_set_digital_out_mode_req {
u32 color_mode_id;
u32 timing_mode_id;
} __packed;
struct dcp_map_buf_req {
u64 buffer;
u8 unk;
u8 buf_null;
u8 vaddr_null;
u8 dva_null;
} __packed;
struct dcp_map_buf_resp {
u64 vaddr;
u64 dva;
u32 ret;
} __packed;
struct dcp_unmap_buf_resp {
u64 buffer;
u64 vaddr;
u64 dva;
u8 unk;
u8 buf_null;
} __packed;
struct dcp_allocate_buffer_req {
u32 unk0;
u64 size;
u32 unk2;
u8 paddr_null;
u8 dva_null;
u8 dva_size_null;
u8 padding;
} __packed;
struct dcp_allocate_buffer_resp {
u64 paddr;
u64 dva;
u64 dva_size;
u32 mem_desc_id;
} __packed;
struct dcp_map_physical_req {
u64 paddr;
u64 size;
u32 flags;
u8 dva_null;
u8 dva_size_null;
u8 padding[2];
} __packed;
struct dcp_map_physical_resp {
u64 dva;
u64 dva_size;
u32 mem_desc_id;
} __packed;
struct dcp_swap_start_req {
u32 swap_id;
struct dcp_iouserclient client;
u8 swap_id_null;
u8 client_null;
u8 padding[2];
} __packed;
struct dcp_swap_start_resp {
u32 swap_id;
struct dcp_iouserclient client;
u32 ret;
} __packed;
struct dcp_get_uint_prop_req {
char obj[4];
char key[0x40];
u64 value;
u8 value_null;
u8 padding[3];
} __packed;
struct dcp_get_uint_prop_resp {
u64 value;
u8 ret;
u8 padding[3];
} __packed;
struct iomfb_sr_set_property_int_req {
char obj[4];
char key[0x40];
u64 value;
u8 value_null;
u8 padding[3];
} __packed;
struct iomfb_set_fx_prop_req {
char obj[4];
char key[0x40];
u32 value;
} __packed;
struct dcp_set_power_state_req {
u64 unklong;
u8 unkbool;
u8 unkint_null;
u8 padding[2];
} __packed;
struct dcp_set_power_state_resp {
u32 unkint;
u32 ret;
} __packed;
struct dcp_set_dcpav_prop_chunk_req {
char data[0x1000];
u32 offset;
u32 length;
} __packed;
struct dcp_set_dcpav_prop_end_req {
char key[0x40];
} __packed;
struct dcp_set_parameter_dcp {
u32 param;
u32 value[8];
u32 count;
} __packed;
struct dcp_swap_complete_intent_gated {
u32 swap_id;
u8 unkBool;
u32 unkInt;
u32 width;
u32 height;
} __packed;
struct dcp_read_edt_data_req {
char key[0x40];
u32 count;
u32 value[8];
} __packed;
struct dcp_read_edt_data_resp {
u32 value[8];
u8 ret;
} __packed;
struct iomfb_property {
u32 id;
u32 value;
} __packed;
struct iomfb_get_color_remap_mode_req {
u32 mode;
u8 mode_null;
u8 padding[3];
} __packed;
struct iomfb_get_color_remap_mode_resp {
u32 mode;
u32 ret;
} __packed;
struct iomfb_last_client_close_req {
u8 unkint_null;
u8 padding[3];
} __packed;
struct iomfb_last_client_close_resp {
u32 unkint;
} __packed;
struct io_user_client {
u64 addr;
u32 unk;
u8 flag1;
u8 flag2;
u8 pad[2];
} __packed;
struct iomfb_abort_swaps_dcp_req {
struct io_user_client client;
u8 client_null;
u8 pad[3];
} __packed;
struct iomfb_abort_swaps_dcp_resp {
struct io_user_client client;
u32 ret;
} __packed;
struct iomfb_set_matrix_req {
u32 unk_u32; // maybe length?
u64 r[3];
u64 g[3];
u64 b[3];
u8 matrix_null;
u8 padding[3];
} __packed;
struct iomfb_set_matrix_resp {
u32 ret;
} __packed;
struct dcpep_get_tiling_state_req {
u32 event;
u32 param;
u32 value;
u8 value_null;
u8 padding[3];
} __packed;
struct dcpep_get_tiling_state_resp {
u32 value;
u32 ret;
} __packed;
#endif

View file

@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright The Asahi Linux Contributors */
#include <drm/drm_modes.h>
#include <drm/drm_rect.h>
#include "dcp-internal.h"
struct apple_dcp;
typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
#define DCP_THUNK_VOID(func, handle) \
static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
void *cookie) \
{ \
dcp_push(dcp, oob, &dcp_methods[handle], 0, 0, NULL, cb, cookie); \
}
#define DCP_THUNK_OUT(func, handle, T) \
static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
void *cookie) \
{ \
dcp_push(dcp, oob, &dcp_methods[handle], 0, sizeof(T), NULL, cb, cookie); \
}
#define DCP_THUNK_IN(func, handle, T) \
static void func(struct apple_dcp *dcp, bool oob, T *data, \
dcp_callback_t cb, void *cookie) \
{ \
dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T), 0, data, cb, cookie); \
}
#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \
static void func(struct apple_dcp *dcp, bool oob, T_in *data, \
dcp_callback_t cb, void *cookie) \
{ \
dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T_in), sizeof(T_out), data, \
cb, cookie); \
}
#define IOMFB_THUNK_INOUT(name) \
static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \
struct iomfb_ ## name ## _req *data, \
dcp_callback_t cb, void *cookie) \
{ \
dcp_push(dcp, oob, &dcp_methods[iomfbep_ ## name], \
sizeof(struct iomfb_ ## name ## _req), \
sizeof(struct iomfb_ ## name ## _resp), \
data, cb, cookie); \
}
/*
* Define type-safe trampolines. Define typedefs to enforce type-safety on the
* input data (so if the types don't match, gcc errors out).
*/
#define TRAMPOLINE_VOID(func, handler) \
static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
{ \
trace_iomfb_callback(dcp, tag, #handler); \
handler(dcp); \
return true; \
}
#define TRAMPOLINE_IN(func, handler, T_in) \
typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \
\
static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
{ \
callback_##handler cb = handler; \
\
trace_iomfb_callback(dcp, tag, #handler); \
cb(dcp, in); \
return true; \
}
#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \
typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \
\
static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
{ \
T_out *typed_out = out; \
callback_##handler cb = handler; \
\
trace_iomfb_callback(dcp, tag, #handler); \
*typed_out = cb(dcp, in); \
return true; \
}
#define TRAMPOLINE_OUT(func, handler, T_out) \
static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
{ \
T_out *typed_out = out; \
\
trace_iomfb_callback(dcp, tag, #handler); \
*typed_out = handler(dcp); \
return true; \
}
/* Call a DCP function given by a tag */
void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call,
u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
void *cookie);
/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
int dcp_parse_tag(char tag[4]);
void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context);
/*
* DRM specifies rectangles as start and end coordinates. DCP specifies
* rectangles as a start coordinate and a width/height. Convert a DRM rectangle
* to a DCP rectangle.
*/
struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect);
u32 drm_format_to_dcp(u32 drm);
/* The user may own drm_display_mode, so we need to search for our copy */
struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
const struct drm_display_mode *mode);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,184 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
/*
* This file is intended to be included multiple times with IOMFB_VER
* defined to declare DCP firmware version dependent structs.
*/
#ifdef DCP_FW_VER
#include <drm/drm_crtc.h>
#include <linux/types.h>
#include "iomfb.h"
#include "version_utils.h"
struct DCP_FW_NAME(dcp_swap) {
u64 ts1;
u64 ts2;
u64 unk_10[6];
u64 flags1;
u64 flags2;
u32 swap_id;
u32 surf_ids[SWAP_SURFACES];
struct dcp_rect src_rect[SWAP_SURFACES];
u32 surf_flags[SWAP_SURFACES];
u32 surf_unk[SWAP_SURFACES];
struct dcp_rect dst_rect[SWAP_SURFACES];
u32 swap_enabled;
u32 swap_completed;
u32 bg_color;
u8 unk_110[0x1b8];
u32 unk_2c8;
u8 unk_2cc[0x14];
u32 unk_2e0;
#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0)
u16 unk_2e2;
#else
u8 unk_2e2[3];
#endif
u64 bl_unk;
u32 bl_value; // min value is 0x10000000
u8 bl_power; // constant 0x40 for on
u8 unk_2f3[0x2d];
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u8 unk_320[0x13f];
u64 unk_1;
#endif
} __packed;
/* Information describing a surface */
struct DCP_FW_NAME(dcp_surface) {
u8 is_tiled;
u8 is_tearing_allowed;
u8 is_premultiplied;
u32 plane_cnt;
u32 plane_cnt2;
u32 format; /* DCP fourcc */
u32 ycbcr_matrix;
u8 xfer_func;
u8 colorspace;
u32 stride;
u16 pix_size;
u8 pel_w;
u8 pel_h;
u32 offset;
u32 width;
u32 height;
u32 buf_size;
u64 protection_opts;
u32 surface_id;
struct dcp_component_types comp_types[MAX_PLANES];
u64 has_comp;
struct dcp_plane_info planes[MAX_PLANES];
u64 has_planes;
u32 compression_info[MAX_PLANES][13];
u64 has_compr_info;
u32 unk_num;
u32 unk_denom;
#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0)
u8 padding[7];
#else
u8 padding[47];
#endif
} __packed;
/* Prototypes */
struct DCP_FW_NAME(dcp_swap_submit_req) {
struct DCP_FW_NAME(dcp_swap) swap;
struct DCP_FW_NAME(dcp_surface) surf[SWAP_SURFACES];
u64 surf_iova[SWAP_SURFACES];
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u64 unk_u64_a[SWAP_SURFACES];
struct DCP_FW_NAME(dcp_surface) surf2[5];
u64 surf2_iova[5];
#endif
u8 unkbool;
u64 unkdouble;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u64 unkU64;
u8 unkbool2;
#endif
u32 clear; // or maybe switch to default fb?
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u32 unkU32Ptr;
#endif
u8 swap_null;
u8 surf_null[SWAP_SURFACES];
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u8 surf2_null[5];
#endif
u8 unkoutbool_null;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u8 unkU32Ptr_null;
u8 unkU32out_null;
#endif
u8 padding[1];
} __packed;
struct DCP_FW_NAME(dcp_swap_submit_resp) {
u8 unkoutbool;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u32 unkU32out;
#endif
u32 ret;
u8 padding[3];
} __packed;
struct DCP_FW_NAME(dc_swap_complete_resp) {
u32 swap_id;
u8 unkbool;
u64 swap_data;
#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0)
u8 swap_info[0x6c4];
#else
u8 swap_info[0x6c5];
#endif
u32 unkint;
u8 swap_info_null;
} __packed;
struct DCP_FW_NAME(dcp_map_reg_req) {
char obj[4];
u32 index;
u32 flags;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u8 unk_u64_null;
#endif
u8 addr_null;
u8 length_null;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u8 padding[1];
#else
u8 padding[2];
#endif
} __packed;
struct DCP_FW_NAME(dcp_map_reg_resp) {
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u64 dva;
#endif
u64 addr;
u64 length;
u32 ret;
} __packed;
struct apple_dcp;
int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
struct drm_crtc_state *crtc_state);
void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state);
void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp);
void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp);
void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp);
void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp);
void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp);
#endif

View file

@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright The Asahi Linux Contributors */
#include "iomfb_v12_3.h"
#include "iomfb_v13_3.h"
#include "version_utils.h"
static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
IOMFB_METHOD("A000", dcpep_late_init_signal),
IOMFB_METHOD("A029", dcpep_setup_video_limits),
IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched),
IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched),
IOMFB_METHOD("A357", dcpep_set_create_dfb),
IOMFB_METHOD("A358", iomfbep_a358_vi_set_temperature_hint),
IOMFB_METHOD("A401", dcpep_start_signal),
IOMFB_METHOD("A407", dcpep_swap_start),
IOMFB_METHOD("A408", dcpep_swap_submit),
IOMFB_METHOD("A410", dcpep_set_display_device),
IOMFB_METHOD("A411", dcpep_is_main_display),
IOMFB_METHOD("A412", dcpep_set_digital_out_mode),
IOMFB_METHOD("A422", iomfbep_set_matrix),
IOMFB_METHOD("A426", iomfbep_get_color_remap_mode),
IOMFB_METHOD("A439", dcpep_set_parameter_dcp),
IOMFB_METHOD("A443", dcpep_create_default_fb),
IOMFB_METHOD("A447", dcpep_enable_disable_video_power_savings),
IOMFB_METHOD("A454", dcpep_first_client_open),
IOMFB_METHOD("A455", iomfbep_last_client_close),
IOMFB_METHOD("A460", dcpep_set_display_refresh_properties),
IOMFB_METHOD("A463", dcpep_flush_supports_power),
IOMFB_METHOD("A464", iomfbep_abort_swaps_dcp),
IOMFB_METHOD("A468", dcpep_set_power_state),
};
#define DCP_FW v12_3
#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0)
#include "iomfb_template.c"
static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
[0] = trampoline_true, /* did_boot_signal */
[1] = trampoline_true, /* did_power_on_signal */
[2] = trampoline_nop, /* will_power_off_signal */
[3] = trampoline_rt_bandwidth,
[100] = iomfbep_cb_match_pmu_service,
[101] = trampoline_zero, /* get_display_default_stride */
[102] = trampoline_nop, /* set_number_property */
[103] = trampoline_nop, /* set_boolean_property */
[106] = trampoline_nop, /* remove_property */
[107] = trampoline_true, /* create_provider_service */
[108] = trampoline_true, /* create_product_service */
[109] = trampoline_true, /* create_pmu_service */
[110] = trampoline_true, /* create_iomfb_service */
[111] = trampoline_create_backlight_service,
[116] = dcpep_cb_boot_1,
[117] = trampoline_false, /* is_dark_boot */
[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
[120] = trampoline_read_edt_data,
[122] = trampoline_prop_start,
[123] = trampoline_prop_chunk,
[124] = trampoline_prop_end,
[201] = trampoline_map_piodma,
[202] = trampoline_unmap_piodma,
[206] = iomfbep_cb_match_pmu_service_2,
[207] = iomfbep_cb_match_backlight_service,
[208] = trampoline_get_time,
[211] = trampoline_nop, /* update_backlight_factor_prop */
[300] = trampoline_pr_publish,
[401] = trampoline_get_uint_prop,
[404] = trampoline_nop, /* sr_set_uint_prop */
[406] = trampoline_set_fx_prop,
[408] = trampoline_get_frequency,
[411] = trampoline_map_reg,
[413] = trampoline_true, /* sr_set_property_dict */
[414] = trampoline_sr_set_property_int,
[415] = trampoline_true, /* sr_set_property_bool */
[451] = trampoline_allocate_buffer,
[452] = trampoline_map_physical,
[456] = trampoline_release_mem_desc,
[552] = trampoline_true, /* set_property_dict_0 */
[561] = trampoline_true, /* set_property_dict */
[563] = trampoline_true, /* set_property_int */
[565] = trampoline_true, /* set_property_bool */
[567] = trampoline_true, /* set_property_str */
[574] = trampoline_zero, /* power_up_dart */
[576] = trampoline_hotplug,
[577] = trampoline_nop, /* powerstate_notify */
[582] = trampoline_true, /* create_default_fb_surface */
[584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */
[588] = trampoline_nop, /* resize_default_fb_surface_gated */
[589] = trampoline_swap_complete,
[591] = trampoline_swap_complete_intent_gated,
[592] = trampoline_abort_swap_ap_gated,
[593] = trampoline_enable_backlight_message_ap_gated,
[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
[597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */
[598] = trampoline_nop, /* find_swap_function_gated */
};
void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp)
{
dcp->cb_handlers = cb_handlers;
dcp_start_signal(dcp, false, dcp_started, NULL);
}
#undef DCP_FW_VER
#undef DCP_FW

View file

@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright The Asahi Linux Contributors */
#ifndef __APPLE_IOMFB_V12_3_H__
#define __APPLE_IOMFB_V12_3_H__
#include "version_utils.h"
#define DCP_FW v12_3
#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0)
#include "iomfb_template.h"
#undef DCP_FW_VER
#undef DCP_FW
#endif /* __APPLE_IOMFB_V12_3_H__ */

View file

@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright The Asahi Linux Contributors */
#include "iomfb_v12_3.h"
#include "iomfb_v13_3.h"
#include "version_utils.h"
static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
IOMFB_METHOD("A000", dcpep_late_init_signal),
IOMFB_METHOD("A029", dcpep_setup_video_limits),
IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched),
IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched),
IOMFB_METHOD("A373", dcpep_set_create_dfb),
IOMFB_METHOD("A374", iomfbep_a358_vi_set_temperature_hint),
IOMFB_METHOD("A401", dcpep_start_signal),
IOMFB_METHOD("A407", dcpep_swap_start),
IOMFB_METHOD("A408", dcpep_swap_submit),
IOMFB_METHOD("A410", dcpep_set_display_device),
IOMFB_METHOD("A411", dcpep_is_main_display),
IOMFB_METHOD("A412", dcpep_set_digital_out_mode),
IOMFB_METHOD("A422", iomfbep_set_matrix),
IOMFB_METHOD("A426", iomfbep_get_color_remap_mode),
IOMFB_METHOD("A441", dcpep_set_parameter_dcp),
IOMFB_METHOD("A445", dcpep_create_default_fb),
IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings),
IOMFB_METHOD("A456", dcpep_first_client_open),
IOMFB_METHOD("A457", iomfbep_last_client_close),
IOMFB_METHOD("A463", dcpep_set_display_refresh_properties),
IOMFB_METHOD("A466", dcpep_flush_supports_power),
IOMFB_METHOD("A467", iomfbep_abort_swaps_dcp),
IOMFB_METHOD("A472", dcpep_set_power_state),
};
#define DCP_FW v13_3
#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0)
#include "iomfb_template.c"
static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
[0] = trampoline_true, /* did_boot_signal */
[1] = trampoline_true, /* did_power_on_signal */
[2] = trampoline_nop, /* will_power_off_signal */
[3] = trampoline_rt_bandwidth,
[6] = trampoline_set_frame_sync_props,
[100] = iomfbep_cb_match_pmu_service,
[101] = trampoline_zero, /* get_display_default_stride */
[102] = trampoline_nop, /* set_number_property */
[103] = trampoline_nop, /* trigger_user_cal_loader */
[104] = trampoline_nop, /* set_boolean_property */
[107] = trampoline_nop, /* remove_property */
[108] = trampoline_true, /* create_provider_service */
[109] = trampoline_true, /* create_product_service */
[110] = trampoline_true, /* create_pmu_service */
[111] = trampoline_true, /* create_iomfb_service */
[112] = trampoline_create_backlight_service,
[113] = trampoline_true, /* create_nvram_servce? */
[114] = trampoline_get_tiling_state,
[115] = trampoline_false, /* set_tiling_state */
[120] = dcpep_cb_boot_1,
[121] = trampoline_false, /* is_dark_boot */
[122] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
[124] = trampoline_read_edt_data,
[126] = trampoline_prop_start,
[127] = trampoline_prop_chunk,
[128] = trampoline_prop_end,
[129] = trampoline_allocate_bandwidth,
[201] = trampoline_map_piodma,
[202] = trampoline_unmap_piodma,
[206] = iomfbep_cb_match_pmu_service_2,
[207] = iomfbep_cb_match_backlight_service,
[208] = trampoline_nop, /* update_backlight_factor_prop */
[209] = trampoline_get_time,
[300] = trampoline_pr_publish,
[401] = trampoline_get_uint_prop,
[404] = trampoline_nop, /* sr_set_uint_prop */
[406] = trampoline_set_fx_prop,
[408] = trampoline_get_frequency,
[411] = trampoline_map_reg,
[413] = trampoline_true, /* sr_set_property_dict */
[414] = trampoline_sr_set_property_int,
[415] = trampoline_true, /* sr_set_property_bool */
[451] = trampoline_allocate_buffer,
[452] = trampoline_map_physical,
[456] = trampoline_release_mem_desc,
[552] = trampoline_true, /* set_property_dict_0 */
[561] = trampoline_true, /* set_property_dict */
[563] = trampoline_true, /* set_property_int */
[565] = trampoline_true, /* set_property_bool */
[567] = trampoline_true, /* set_property_str */
[574] = trampoline_zero, /* power_up_dart */
[576] = trampoline_hotplug,
[577] = trampoline_nop, /* powerstate_notify */
[582] = trampoline_true, /* create_default_fb_surface */
[584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */
[588] = trampoline_nop, /* resize_default_fb_surface_gated */
[589] = trampoline_swap_complete,
[591] = trampoline_swap_complete_intent_gated,
[592] = trampoline_abort_swap_ap_gated,
[593] = trampoline_enable_backlight_message_ap_gated,
[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
[597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */
[598] = trampoline_nop, /* find_swap_function_gated */
};
void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp)
{
dcp->cb_handlers = cb_handlers;
dcp_start_signal(dcp, false, dcp_started, NULL);
}
#undef DCP_FW_VER
#undef DCP_FW

View file

@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright The Asahi Linux Contributors */
#ifndef __APPLE_IOMFB_V13_3_H__
#define __APPLE_IOMFB_V13_3_H__
#include "version_utils.h"
#define DCP_FW v13_3
#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0)
#include "iomfb_template.h"
#undef DCP_FW_VER
#undef DCP_FW
#endif /* __APPLE_IOMFB_V13_3_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
#ifndef __APPLE_DCP_PARSER_H__
#define __APPLE_DCP_PARSER_H__
/* For mode parsing */
#include <drm/drm_modes.h>
struct apple_dcp;
struct dcp_parse_ctx {
struct apple_dcp *dcp;
const void *blob;
u32 pos, len;
};
enum dcp_color_eotf {
DCP_EOTF_SDR_GAMMA = 0, // "SDR gamma"
DCP_EOTF_HDR_GAMMA = 1, // "HDR gamma"
DCP_EOTF_ST_2084 = 2, // "ST 2084 (PQ)"
DCP_EOTF_BT_2100 = 3, // "BT.2100 (HLG)"
DCP_EOTF_COUNT
};
enum dcp_color_format {
DCP_COLOR_FORMAT_RGB = 0, // "RGB"
DCP_COLOR_FORMAT_YCBCR420 = 1, // "YUV 4:2:0"
DCP_COLOR_FORMAT_YCBCR422 = 3, // "YUV 4:2:2"
DCP_COLOR_FORMAT_YCBCR444 = 2, // "YUV 4:4:4"
DCP_COLOR_FORMAT_DV_NATIVE = 4, // "DolbyVision (native)"
DCP_COLOR_FORMAT_DV_HDMI = 5, // "DolbyVision (HDMI)"
DCP_COLOR_FORMAT_YCBCR422_DP = 6, // "YCbCr 4:2:2 (DP tunnel)"
DCP_COLOR_FORMAT_YCBCR422_HDMI = 7, // "YCbCr 4:2:2 (HDMI tunnel)"
DCP_COLOR_FORMAT_DV_LL_YCBCR422 = 8, // "DolbyVision LL YCbCr 4:2:2"
DCP_COLOR_FORMAT_DV_LL_YCBCR422_DP = 9, // "DolbyVision LL YCbCr 4:2:2 (DP)"
DCP_COLOR_FORMAT_DV_LL_YCBCR422_HDMI = 10, // "DolbyVision LL YCbCr 4:2:2 (HDMI)"
DCP_COLOR_FORMAT_DV_LL_YCBCR444 = 11, // "DolbyVision LL YCbCr 4:4:4"
DCP_COLOR_FORMAT_DV_LL_RGB422 = 12, // "DolbyVision LL RGB 4:2:2"
DCP_COLOR_FORMAT_GRGB_BLUE_422 = 13, // "GRGB as YCbCr422 (Even line blue)"
DCP_COLOR_FORMAT_GRGB_RED_422 = 14, // "GRGB as YCbCr422 (Even line red)"
DCP_COLOR_FORMAT_COUNT
};
enum dcp_colorimetry {
DCP_COLORIMETRY_BT601 = 0, // "SMPTE 170M/BT.601"
DCP_COLORIMETRY_BT709 = 1, // "BT.701"
DCP_COLORIMETRY_XVYCC_601 = 2, // "xvYCC601"
DCP_COLORIMETRY_XVYCC_709 = 3, // "xvYCC709"
DCP_COLORIMETRY_SYCC_601 = 4, // "sYCC601"
DCP_COLORIMETRY_ADOBE_YCC_601 = 5, // "AdobeYCC601"
DCP_COLORIMETRY_BT2020_CYCC = 6, // "BT.2020 (c)"
DCP_COLORIMETRY_BT2020_YCC = 7, // "BT.2020 (nc)"
DCP_COLORIMETRY_VSVDB = 8, // "DolbyVision VSVDB"
DCP_COLORIMETRY_BT2020_RGB = 9, // "BT.2020 (RGB)"
DCP_COLORIMETRY_SRGB = 10, // "sRGB"
DCP_COLORIMETRY_SCRGB = 11, // "scRGB"
DCP_COLORIMETRY_SCRGB_FIXED = 12, // "scRGBfixed"
DCP_COLORIMETRY_ADOBE_RGB = 13, // "AdobeRGB"
DCP_COLORIMETRY_DCI_P3_RGB_D65 = 14, // "DCI-P3 (D65)"
DCP_COLORIMETRY_DCI_P3_RGB_THEATER = 15, // "DCI-P3 (Theater)"
DCP_COLORIMETRY_RGB = 16, // "Default RGB"
DCP_COLORIMETRY_COUNT
};
enum dcp_color_range {
DCP_COLOR_YCBCR_RANGE_FULL = 0,
DCP_COLOR_YCBCR_RANGE_LIMITED = 1,
DCP_COLOR_YCBCR_RANGE_COUNT
};
struct dcp_color_mode {
s64 score;
u32 id;
enum dcp_color_eotf eotf;
enum dcp_color_format format;
enum dcp_colorimetry colorimetry;
enum dcp_color_range range;
u8 depth;
};
/*
* Represents a single display mode. These mode objects are populated at
* runtime based on the TimingElements dictionary sent by the DCP.
*/
struct dcp_display_mode {
struct drm_display_mode mode;
u32 color_mode_id;
u32 timing_mode_id;
struct dcp_color_mode sdr_rgb;
struct dcp_color_mode sdr_444;
struct dcp_color_mode sdr;
struct dcp_color_mode best;
};
struct dimension {
s64 total, front_porch, sync_width, active;
s64 precise_sync_rate;
};
int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx);
struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
unsigned int *count, int width_mm,
int height_mm, unsigned notch_height);
int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
int *height_mm);
int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name,
const char **class, s64 *unit);
struct dcp_sound_format_mask {
u64 formats; /* SNDRV_PCM_FMTBIT_* */
unsigned int rates; /* SNDRV_PCM_RATE_* */
unsigned int nchans;
};
struct dcp_sound_cookie {
u8 data[24];
};
struct snd_pcm_chmap_elem;
int parse_sound_constraints(struct dcp_parse_ctx *handle,
struct dcp_sound_format_mask *sieve,
struct dcp_sound_format_mask *hits);
int parse_sound_mode(struct dcp_parse_ctx *handle,
struct dcp_sound_format_mask *sieve,
struct snd_pcm_chmap_elem *chmap,
struct dcp_sound_cookie *cookie);
struct dcp_system_ev_mnits {
u32 timestamp;
u32 millinits;
u32 idac;
};
int parse_system_log_mnits(struct dcp_parse_ctx *handle,
struct dcp_system_ev_mnits *entry);
#endif

View file

@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
#include <linux/completion.h>
#include "afk.h"
#include "dcp.h"
#include "parser.h"
static bool enable_verbose_logging;
module_param(enable_verbose_logging, bool, 0644);
MODULE_PARM_DESC(enable_verbose_logging, "Enable DCP firmware verbose logging");
/*
* Serialized setProperty("gAFKConfigLogMask", 0xffff) IPC call which
* will set the DCP firmware log level to the most verbose setting
*/
#define SYSTEM_SET_PROPERTY 0x43
static const u8 setprop_gAFKConfigLogMask_ffff[] = {
0x14, 0x00, 0x00, 0x00, 0x67, 0x41, 0x46, 0x4b, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x73,
0x6b, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x84, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
struct systemep_work {
struct apple_epic_service *service;
struct work_struct work;
};
static void system_log_work(struct work_struct *work_)
{
struct systemep_work *work =
container_of(work_, struct systemep_work, work);
afk_send_command(work->service, SYSTEM_SET_PROPERTY,
setprop_gAFKConfigLogMask_ffff,
sizeof(setprop_gAFKConfigLogMask_ffff), NULL,
sizeof(setprop_gAFKConfigLogMask_ffff), NULL);
complete(&work->service->ep->dcp->systemep_done);
kfree(work);
}
static void system_init(struct apple_epic_service *service, const char *name,
const char *class, s64 unit)
{
struct systemep_work *work;
if (!enable_verbose_logging)
return;
/*
* We're called from the service message handler thread and can't
* dispatch blocking message from there.
*/
work = kzalloc(sizeof(*work), GFP_KERNEL);
if (!work)
return;
work->service = service;
INIT_WORK(&work->work, system_log_work);
schedule_work(&work->work);
}
static void powerlog_init(struct apple_epic_service *service, const char *name,
const char *class, s64 unit)
{
}
static int powerlog_report(struct apple_epic_service *service, enum epic_subtype type,
const void *data, size_t data_size)
{
struct dcp_system_ev_mnits mnits;
struct dcp_parse_ctx parse_ctx;
struct apple_dcp *dcp = service->ep->dcp;
int ret;
dev_dbg(dcp->dev, "systemep[ch:%u]: report type:%02x len:%zu\n",
service->channel, type, data_size);
if (type != EPIC_SUBTYPE_STD_SERVICE)
return 0;
ret = parse(data, data_size, &parse_ctx);
if (ret) {
dev_warn(service->ep->dcp->dev, "systemep: failed to parse report: %d\n", ret);
return ret;
}
ret = parse_system_log_mnits(&parse_ctx, &mnits);
if (ret) {
/* ignore parse errors in the case dcp sends unknown log events */
dev_dbg(dcp->dev, "systemep: failed to parse mNits event: %d\n", ret);
return 0;
}
dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n",
mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac);
dcp->brightness.nits = mnits.millinits / 1000;
return 0;
}
static const struct apple_epic_service_ops systemep_ops[] = {
{
.name = "system",
.init = system_init,
},
{
.name = "powerlog-service",
.init = powerlog_init,
.report = powerlog_report,
},
{}
};
int systemep_init(struct apple_dcp *dcp)
{
init_completion(&dcp->systemep_done);
dcp->systemep = afk_init(dcp, SYSTEM_ENDPOINT, systemep_ops);
afk_start(dcp->systemep);
if (!enable_verbose_logging)
return 0;
/*
* Timeouts aren't really fatal here: in the worst case we just weren't
* able to enable additional debug prints inside DCP
*/
if (!wait_for_completion_timeout(&dcp->systemep_done,
msecs_to_jiffies(MSEC_PER_SEC)))
dev_err(dcp->dev, "systemep: couldn't enable verbose logs\n");
return 0;
}

View file

@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0+ OR MIT
/*
* Tracepoints for Apple DCP driver
*
* Copyright (C) The Asahi Linux Contributors
*/
#define CREATE_TRACE_POINTS
#include "trace.h"

View file

@ -0,0 +1,596 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright (C) The Asahi Linux Contributors */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM dcp
#if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_DCP_H
#include "afk.h"
#include "dptxep.h"
#include "dcp-internal.h"
#include "parser.h"
#include <linux/stringify.h>
#include <linux/types.h>
#include <linux/tracepoint.h>
#define show_dcp_endpoint(ep) \
__print_symbolic(ep, { SYSTEM_ENDPOINT, "system" }, \
{ TEST_ENDPOINT, "test" }, \
{ DCP_EXPERT_ENDPOINT, "dcpexpert" }, \
{ DISP0_ENDPOINT, "disp0" }, \
{ DPTX_ENDPOINT, "dptxport" }, \
{ HDCP_ENDPOINT, "hdcp" }, \
{ REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \
{ IOMFB_ENDPOINT, "iomfb" })
#define print_epic_type(etype) \
__print_symbolic(etype, { EPIC_TYPE_NOTIFY, "notify" }, \
{ EPIC_TYPE_COMMAND, "command" }, \
{ EPIC_TYPE_REPLY, "reply" }, \
{ EPIC_TYPE_NOTIFY_ACK, "notify-ack" })
#define print_epic_category(ecat) \
__print_symbolic(ecat, { EPIC_CAT_REPORT, "report" }, \
{ EPIC_CAT_NOTIFY, "notify" }, \
{ EPIC_CAT_REPLY, "reply" }, \
{ EPIC_CAT_COMMAND, "command" })
#define show_dptxport_apcall(idx) \
__print_symbolic( \
idx, { DPTX_APCALL_ACTIVATE, "activate" }, \
{ DPTX_APCALL_DEACTIVATE, "deactivate" }, \
{ DPTX_APCALL_GET_MAX_DRIVE_SETTINGS, \
"get_max_drive_settings" }, \
{ DPTX_APCALL_SET_DRIVE_SETTINGS, "set_drive_settings" }, \
{ DPTX_APCALL_GET_DRIVE_SETTINGS, "get_drive_settings" }, \
{ DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG, \
"will_change_link_config" }, \
{ DPTX_APCALL_DID_CHANGE_LINK_CONFIG, \
"did_change_link_config" }, \
{ DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" }, \
{ DPTX_APCALL_GET_LINK_RATE, "get_link_rate" }, \
{ DPTX_APCALL_SET_LINK_RATE, "set_link_rate" }, \
{ DPTX_APCALL_GET_MAX_LANE_COUNT, \
"get_max_lane_count" }, \
{ DPTX_APCALL_GET_ACTIVE_LANE_COUNT, \
"get_active_lane_count" }, \
{ DPTX_APCALL_SET_ACTIVE_LANE_COUNT, \
"set_active_lane_count" }, \
{ DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD, \
"get_supports_downspread" }, \
{ DPTX_APCALL_GET_DOWN_SPREAD, "get_downspread" }, \
{ DPTX_APCALL_SET_DOWN_SPREAD, "set_downspread" }, \
{ DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING, \
"get_supports_lane_mapping" }, \
{ DPTX_APCALL_SET_LANE_MAP, "set_lane_map" }, \
{ DPTX_APCALL_GET_SUPPORTS_HPD, "get_supports_hpd" }, \
{ DPTX_APCALL_FORCE_HOTPLUG_DETECT, "force_hotplug_detect" }, \
{ DPTX_APCALL_INACTIVE_SINK_DETECTED, \
"inactive_sink_detected" }, \
{ DPTX_APCALL_SET_TILED_DISPLAY_HINTS, \
"set_tiled_display_hints" }, \
{ DPTX_APCALL_DEVICE_NOT_RESPONDING, \
"device_not_responding" }, \
{ DPTX_APCALL_DEVICE_BUSY_TIMEOUT, "device_busy_timeout" }, \
{ DPTX_APCALL_DEVICE_NOT_STARTED, "device_not_started" })
TRACE_EVENT(dcp_recv_msg,
TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message),
TP_ARGS(dcp, endpoint, message),
TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
__field(u8, endpoint)
__field(u64, message)),
TP_fast_assign(__assign_str(devname, dev_name(dcp->dev));
__entry->endpoint = endpoint;
__entry->message = message;),
TP_printk("%s: endpoint 0x%x (%s): received message 0x%016llx",
__get_str(devname), __entry->endpoint,
show_dcp_endpoint(__entry->endpoint), __entry->message));
TRACE_EVENT(dcp_send_msg,
TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message),
TP_ARGS(dcp, endpoint, message),
TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
__field(u8, endpoint)
__field(u64, message)),
TP_fast_assign(__assign_str(devname, dev_name(dcp->dev));
__entry->endpoint = endpoint;
__entry->message = message;),
TP_printk("%s: endpoint 0x%x (%s): will send message 0x%016llx",
__get_str(devname), __entry->endpoint,
show_dcp_endpoint(__entry->endpoint), __entry->message));
TRACE_EVENT(
afk_getbuf, TP_PROTO(struct apple_dcp_afkep *ep, u16 size, u16 tag),
TP_ARGS(ep, size, tag),
TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev))
__field(u8, endpoint) __field(u16, size)
__field(u16, tag)),
TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev));
__entry->endpoint = ep->endpoint; __entry->size = size;
__entry->tag = tag;),
TP_printk(
"%s: endpoint 0x%x (%s): get buffer with size 0x%x and tag 0x%x",
__get_str(devname), __entry->endpoint,
show_dcp_endpoint(__entry->endpoint), __entry->size,
__entry->tag));
DECLARE_EVENT_CLASS(afk_rwptr_template,
TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
TP_ARGS(ep, rptr, wptr),
TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev))
__field(u8, endpoint) __field(u32, rptr)
__field(u32, wptr)),
TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev));
__entry->endpoint = ep->endpoint;
__entry->rptr = rptr; __entry->wptr = wptr;),
TP_printk("%s: endpoint 0x%x (%s): rptr 0x%x, wptr 0x%x",
__get_str(devname), __entry->endpoint,
show_dcp_endpoint(__entry->endpoint), __entry->rptr,
__entry->wptr));
DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_pre,
TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
TP_ARGS(ep, rptr, wptr));
DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_post,
TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
TP_ARGS(ep, rptr, wptr));
DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_pre,
TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
TP_ARGS(ep, rptr, wptr));
DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_post,
TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
TP_ARGS(ep, rptr, wptr));
TRACE_EVENT(
afk_recv_qe,
TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 magic, u32 size),
TP_ARGS(ep, rptr, magic, size),
TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev))
__field(u8, endpoint) __field(u32, rptr)
__field(u32, magic)
__field(u32, size)),
TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev));
__entry->endpoint = ep->endpoint; __entry->rptr = rptr;
__entry->magic = magic; __entry->size = size;),
TP_printk("%s: endpoint 0x%x (%s): QE rptr 0x%x, magic 0x%x, size 0x%x",
__get_str(devname), __entry->endpoint,
show_dcp_endpoint(__entry->endpoint), __entry->rptr,
__entry->magic, __entry->size));
TRACE_EVENT(
afk_recv_handle,
TP_PROTO(struct apple_dcp_afkep *ep, u32 channel, u32 type,
u32 data_size, struct epic_hdr *ehdr,
struct epic_sub_hdr *eshdr),
TP_ARGS(ep, channel, type, data_size, ehdr, eshdr),
TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) __field(
u8, endpoint) __field(u32, channel) __field(u32, type)
__field(u32, data_size) __field(u8, category)
__field(u16, subtype)
__field(u16, tag)),
TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev));
__entry->endpoint = ep->endpoint;
__entry->channel = channel; __entry->type = type;
__entry->data_size = data_size;
__entry->category = eshdr->category,
__entry->subtype = le16_to_cpu(eshdr->type),
__entry->tag = le16_to_cpu(eshdr->tag)),
TP_printk(
"%s: endpoint 0x%x (%s): channel 0x%x, type 0x%x (%s), data_size 0x%x, category: 0x%x (%s), subtype: 0x%x, seq: 0x%x",
__get_str(devname), __entry->endpoint,
show_dcp_endpoint(__entry->endpoint), __entry->channel,
__entry->type, print_epic_type(__entry->type),
__entry->data_size, __entry->category,
print_epic_category(__entry->category), __entry->subtype,
__entry->tag));
TRACE_EVENT(iomfb_callback,
TP_PROTO(struct apple_dcp *dcp, int tag, const char *name),
TP_ARGS(dcp, tag, name),
TP_STRUCT__entry(
__string(devname, dev_name(dcp->dev))
__field(int, tag)
__field(const char *, name)
),
TP_fast_assign(
__assign_str(devname, dev_name(dcp->dev));
__entry->tag = tag; __entry->name = name;
),
TP_printk("%s: Callback D%03d %s", __get_str(devname), __entry->tag,
__entry->name));
TRACE_EVENT(iomfb_push,
TP_PROTO(struct apple_dcp *dcp,
const struct dcp_method_entry *method, int context,
int offset, int depth),
TP_ARGS(dcp, method, context, offset, depth),
TP_STRUCT__entry(
__string(devname, dev_name(dcp->dev))
__string(name, method->name)
__field(int, context)
__field(int, offset)
__field(int, depth)),
TP_fast_assign(
__assign_str(devname, dev_name(dcp->dev));
__assign_str(name, method->name);
__entry->context = context; __entry->offset = offset;
__entry->depth = depth;
),
TP_printk("%s: Method %s: context %u, offset %u, depth %u",
__get_str(devname), __get_str(name), __entry->context,
__entry->offset, __entry->depth));
TRACE_EVENT(iomfb_swap_submit,
TP_PROTO(struct apple_dcp *dcp, u32 swap_id),
TP_ARGS(dcp, swap_id),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, swap_id)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->swap_id = swap_id;
),
TP_printk("dcp=%llx, swap_id=%d",
__entry->dcp,
__entry->swap_id)
);
TRACE_EVENT(iomfb_swap_complete,
TP_PROTO(struct apple_dcp *dcp, u32 swap_id),
TP_ARGS(dcp, swap_id),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, swap_id)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->swap_id = swap_id;
),
TP_printk("dcp=%llx, swap_id=%d",
__entry->dcp,
__entry->swap_id
)
);
TRACE_EVENT(iomfb_swap_complete_intent_gated,
TP_PROTO(struct apple_dcp *dcp, u32 swap_id, u32 width, u32 height),
TP_ARGS(dcp, swap_id, width, height),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, swap_id)
__field(u32, width)
__field(u32, height)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->swap_id = swap_id;
__entry->height = height;
__entry->width = width;
),
TP_printk("dcp=%llx, swap_id=%u %ux%u",
__entry->dcp,
__entry->swap_id,
__entry->width,
__entry->height
)
);
TRACE_EVENT(iomfb_abort_swap_ap_gated,
TP_PROTO(struct apple_dcp *dcp, u32 swap_id),
TP_ARGS(dcp, swap_id),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, swap_id)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->swap_id = swap_id;
),
TP_printk("dcp=%llx, swap_id=%u",
__entry->dcp,
__entry->swap_id
)
);
DECLARE_EVENT_CLASS(iomfb_parse_mode_template,
TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score),
TP_STRUCT__entry(__field(s64, id)
__field_struct(struct dimension, horiz)
__field_struct(struct dimension, vert)
__field(s64, best_color_mode)
__field(bool, is_virtual)
__field(s64, score)),
TP_fast_assign(__entry->id = id;
__entry->horiz = *horiz;
__entry->vert = *vert;
__entry->best_color_mode = best_color_mode;
__entry->is_virtual = is_virtual;
__entry->score = score;),
TP_printk("id: %lld, best_color_mode: %lld, resolution:%lldx%lld virtual: %d, score: %lld",
__entry->id, __entry->best_color_mode,
__entry->horiz.active, __entry->vert.active,
__entry->is_virtual, __entry->score));
DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_success,
TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score));
DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_fail,
TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score));
TRACE_EVENT(dptxport_init, TP_PROTO(struct apple_dcp *dcp, u64 unit),
TP_ARGS(dcp, unit),
TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
__field(u64, unit)),
TP_fast_assign(__assign_str(devname, dev_name(dcp->dev));
__entry->unit = unit;),
TP_printk("%s: dptxport unit %lld initialized", __get_str(devname),
__entry->unit));
TRACE_EVENT(
dptxport_apcall,
TP_PROTO(struct dptx_port *dptx, int idx, size_t len),
TP_ARGS(dptx, idx, len),
TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
__field(u32, unit) __field(int, idx) __field(size_t, len)),
TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev));
__entry->unit = dptx->unit; __entry->idx = idx; __entry->len = len;),
TP_printk("%s: dptx%d: AP Call %d (%s) with len %lu", __get_str(devname),
__entry->unit,
__entry->idx, show_dptxport_apcall(__entry->idx), __entry->len));
TRACE_EVENT(
dptxport_validate_connection,
TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die),
TP_ARGS(dptx, core, atc, die),
TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
__field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)),
TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev));
__entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;),
TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname),
__entry->unit, __entry->core, __entry->atc, __entry->die));
TRACE_EVENT(
dptxport_connect,
TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die),
TP_ARGS(dptx, core, atc, die),
TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
__field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)),
TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev));
__entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;),
TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname),
__entry->unit, __entry->core, __entry->atc, __entry->die));
TRACE_EVENT(
dptxport_call_set_link_rate,
TP_PROTO(struct dptx_port *dptx, u32 link_rate),
TP_ARGS(dptx, link_rate),
TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
__field(u32, unit)
__field(u32, link_rate)),
TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev));
__entry->unit = dptx->unit;
__entry->link_rate = link_rate;),
TP_printk("%s: dptx%d: link rate 0x%x", __get_str(devname), __entry->unit,
__entry->link_rate));
TRACE_EVENT(iomfb_brightness,
TP_PROTO(struct apple_dcp *dcp, u32 nits),
TP_ARGS(dcp, nits),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, nits)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->nits = nits;
),
TP_printk("dcp=%llx, nits=%u (raw=0x%05x)",
__entry->dcp,
__entry->nits >> 16,
__entry->nits
)
);
#define show_eotf(eotf) \
__print_symbolic(eotf, { 0, "SDR gamma"}, \
{ 1, "HDR gamma"}, \
{ 2, "ST 2084 (PQ)"}, \
{ 3, "BT.2100 (HLG)"}, \
{ 4, "unexpected"})
#define show_encoding(enc) \
__print_symbolic(enc, { 0, "RGB"}, \
{ 1, "YUV 4:2:0"}, \
{ 3, "YUV 4:2:2"}, \
{ 2, "YUV 4:4:4"}, \
{ 4, "DolbyVision (native)"}, \
{ 5, "DolbyVision (HDMI)"}, \
{ 6, "YCbCr 4:2:2 (DP tunnel)"}, \
{ 7, "YCbCr 4:2:2 (HDMI tunnel)"}, \
{ 8, "DolbyVision LL YCbCr 4:2:2"}, \
{ 9, "DolbyVision LL YCbCr 4:2:2 (DP)"}, \
{10, "DolbyVision LL YCbCr 4:2:2 (HDMI)"}, \
{11, "DolbyVision LL YCbCr 4:4:4"}, \
{12, "DolbyVision LL RGB 4:2:2"}, \
{13, "GRGB as YCbCr422 (Even line blue)"}, \
{14, "GRGB as YCbCr422 (Even line red)"}, \
{15, "unexpected"})
#define show_colorimetry(col) \
__print_symbolic(col, { 0, "SMPTE 170M/BT.601"}, \
{ 1, "BT.701"}, \
{ 2, "xvYCC601"}, \
{ 3, "xvYCC709"}, \
{ 4, "sYCC601"}, \
{ 5, "AdobeYCC601"}, \
{ 6, "BT.2020 (c)"}, \
{ 7, "BT.2020 (nc)"}, \
{ 8, "DolbyVision VSVDB"}, \
{ 9, "BT.2020 (RGB)"}, \
{10, "sRGB"}, \
{11, "scRGB"}, \
{12, "scRGBfixed"}, \
{13, "AdobeRGB"}, \
{14, "DCI-P3 (D65)"}, \
{15, "DCI-P3 (Theater)"}, \
{16, "Default RGB"}, \
{17, "unexpected"})
#define show_range(range) \
__print_symbolic(range, { 0, "Full"}, \
{ 1, "Limited"}, \
{ 2, "unexpected"})
TRACE_EVENT(iomfb_color_mode,
TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 depth,
u32 colorimetry, u32 eotf, u32 range, u32 pixel_enc),
TP_ARGS(dcp, id, score, depth, colorimetry, eotf, range, pixel_enc),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, id)
__field(u32, score)
__field(u32, depth)
__field(u32, colorimetry)
__field(u32, eotf)
__field(u32, range)
__field(u32, pixel_enc)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->id = id;
__entry->score = score;
__entry->depth = depth;
__entry->colorimetry = min_t(u32, colorimetry, 17U);
__entry->eotf = min_t(u32, eotf, 4U);
__entry->range = min_t(u32, range, 2U);
__entry->pixel_enc = min_t(u32, pixel_enc, 15U);
),
TP_printk("dcp=%llx, id=%u, score=%u, depth=%u, colorimetry=%s, eotf=%s, range=%s, pixel_enc=%s",
__entry->dcp,
__entry->id,
__entry->score,
__entry->depth,
show_colorimetry(__entry->colorimetry),
show_eotf(__entry->eotf),
show_range(__entry->range),
show_encoding(__entry->pixel_enc)
)
);
TRACE_EVENT(iomfb_timing_mode,
TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 width,
u32 height, u32 clock, u32 color_mode),
TP_ARGS(dcp, id, score, width, height, clock, color_mode),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, id)
__field(u32, score)
__field(u32, width)
__field(u32, height)
__field(u32, clock)
__field(u32, color_mode)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->id = id;
__entry->score = score;
__entry->width = width;
__entry->height = height;
__entry->clock = clock;
__entry->color_mode = color_mode;
),
TP_printk("dcp=%llx, id=%u, score=%u, %ux%u@%u.%u, color_mode=%u",
__entry->dcp,
__entry->id,
__entry->score,
__entry->width,
__entry->height,
__entry->clock >> 16,
((__entry->clock & 0xffff) * 1000) >> 16,
__entry->color_mode
)
);
TRACE_EVENT(avep_sound_mode,
TP_PROTO(struct apple_dcp *dcp, u32 rates, u64 formats, unsigned int nchans),
TP_ARGS(dcp, rates, formats, nchans),
TP_STRUCT__entry(
__field(u64, dcp)
__field(u32, rates)
__field(u64, formats)
__field(unsigned int, nchans)
),
TP_fast_assign(
__entry->dcp = (u64)dcp;
__entry->rates = rates;
__entry->formats = formats;
__entry->nchans = nchans;
),
TP_printk("dcp=%llx, rates=%#x, formats=%#llx, nchans=%#x",
__entry->dcp,
__entry->rates,
__entry->formats,
__entry->nchans
)
);
#endif /* _TRACE_DCP_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#include <trace/define_trace.h>

View file

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright The Asahi Linux Contributors */
#ifndef __APPLE_VERSION_UTILS_H__
#define __APPLE_VERSION_UTILS_H__
#include <linux/kernel.h>
#include <linux/args.h>
#define DCP_FW_UNION(u) (u).DCP_FW
#define DCP_FW_SUFFIX CONCATENATE(_, DCP_FW)
#define DCP_FW_NAME(name) CONCATENATE(name, DCP_FW_SUFFIX)
#define DCP_FW_VERSION(x, y, z) ( ((x) << 16) | ((y) << 8) | (z) )
#endif /*__APPLE_VERSION_UTILS_H__*/

View file

@ -1,4 +1,4 @@
# $OpenBSD: files.drm,v 1.61 2024/01/16 23:37:51 jsg Exp $
# $OpenBSD: files.drm,v 1.62 2024/01/22 18:54:01 kettenis Exp $
#file dev/pci/drm/aperture.c drm
file dev/pci/drm/dma-resv.c drm
@ -32,6 +32,7 @@ file dev/pci/drm/drm_encoder.c drm
file dev/pci/drm/drm_encoder_slave.c drm
file dev/pci/drm/drm_exec.c drm
file dev/pci/drm/drm_fb_helper.c drm
file dev/pci/drm/drm_fb_dma_helper.c drm
file dev/pci/drm/drm_fbdev_dma.c drm
file dev/pci/drm/drm_fbdev_generic.c drm
file dev/pci/drm/drm_file.c drm
@ -1354,3 +1355,20 @@ file dev/pci/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c amdgpu
file dev/pci/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c amdgpu
file dev/pci/drm/amd/pm/swsmu/smu13/yellow_carp_ppt.c amdgpu
file dev/pci/drm/amd/pm/swsmu/smu_cmn.c amdgpu
device apldcp
attach apldcp at fdt
file dev/pci/drm/apple/apldcp.c apldcp
file dev/pci/drm/apple/afk.c apldcp
file dev/pci/drm/apple/dcp_backlight.c apldcp
file dev/pci/drm/apple/dptxep.c apldcp
file dev/pci/drm/apple/ibootep.c apldcp
file dev/pci/drm/apple/iomfb.c apldcp
file dev/pci/drm/apple/iomfb_v12_3.c apldcp
file dev/pci/drm/apple/iomfb_v13_3.c apldcp
file dev/pci/drm/apple/parser.c apldcp
file dev/pci/drm/apple/systemep.c apldcp
device apldrm: drmbase, wsemuldisplaydev, rasops32
attach apldrm at fdt
file dev/pci/drm/apple/apldrm.c apldrm

View file

@ -90,3 +90,7 @@
#ifdef __LP64__
#define CONFIG_64BIT 1
#endif
#if defined(SUSPEND) || defined(HIBERNATE)
#define CONFIG_PM_SLEEP
#endif

View file

View file

@ -0,0 +1,32 @@
/* Public domain. */
#ifndef _LINUX_SOC_APPLE_RTKIT_H
#define _LINUX_SOC_APPLE_RTKIT_H
#include <linux/bitfield.h>
struct apple_rtkit;
struct apple_rtkit_shmem {
dma_addr_t iova;
void *buffer;
size_t size;
int is_mapped;
};
struct apple_rtkit_ops {
void (*crashed)(void *);
void (*recv_message)(void *, uint8_t, uint64_t);
int (*shmem_setup)(void *, struct apple_rtkit_shmem *);
void (*shmem_destroy)(void *, struct apple_rtkit_shmem *);
};
struct apple_rtkit *devm_apple_rtkit_init(struct device *, void *,
const char *, int, const struct apple_rtkit_ops *);
int apple_rtkit_send_message(struct apple_rtkit *, uint8_t, uint64_t,
struct completion *, int);
int apple_rtkit_start_ep(struct apple_rtkit *, uint8_t);
int apple_rtkit_wake(struct apple_rtkit *);
#endif