Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions drivers/gpu/drm/apple/afk.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ enum rbep_msg_type {

#define SEND_WPTR GENMASK(31, 0)

/* retcode of a reply too short to carry a nonzero IOReturn */
#define AFK_RETCODE_SHORT_REPLY 0xffffffff

static void afk_service_worker(struct work_struct *work);
static void afk_discard_pending_open(struct apple_epic_service *service);

Expand Down Expand Up @@ -475,6 +478,7 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
struct apple_epic_service *service = NULL, *previous;
unsigned long flags;
bool retired = false;
bool reused = false;
u32 ch_idx;
const char *service_name = name;
const char *epic_name = NULL, *epic_class = NULL;
Expand Down Expand Up @@ -560,6 +564,11 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
for (ch_idx = 0; ch_idx < AFK_MAX_CHANNEL; ch_idx++) {
struct apple_epic_service *candidate = &ep->services[ch_idx];

/*
* ops change only on ep->wq, where this runs, or once it is
* gone: no lock needed for the log
*/
reused = !!candidate->ops;
if (afk_service_reinit(candidate, ep, ops, channel)) {
service = candidate;
break;
Expand Down Expand Up @@ -619,8 +628,9 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
afk_service_publish(service);
if (ops->init)
ops->init(service, 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);
dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d, slot %u%s\n",
ep->endpoint, service_name, channel, ch_idx,
reused ? " (reused)" : "");

afk_populate_service_debugfs(service);

Expand Down Expand Up @@ -676,6 +686,7 @@ static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel,
struct apple_epic_service *service;
unsigned long flags;
u8 idx = tag & (ep->wire_v2 ? 0xf : 0xff);
u32 retcode;
void *rxbuf, *txbuf;
dma_addr_t rxbuf_dma, txbuf_dma;
size_t rxlen, txlen;
Expand All @@ -687,11 +698,21 @@ static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel,
return;
}

/*
* A reply shorter than the command header completes the command as
* failed, so that its caller returns and a timed-out command's
* buffers and cmd_map bit are freed. The 4-byte replies seen look
* like an IOReturn: a nonzero one is passed on.
*/
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;
retcode = payload_size >= sizeof(cmd->retcode) ?
le32_to_cpu(cmd->retcode) : 0;
retcode = retcode ?: AFK_RETCODE_SHORT_REPLY;
} else {
retcode = le32_to_cpu(cmd->retcode);
}

if (idx >= MAX_PENDING_CMDS) {
Expand Down Expand Up @@ -719,9 +740,12 @@ static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel,
}

service->cmds[idx].done = true;
service->cmds[idx].retcode = le32_to_cpu(cmd->retcode);
service->cmds[idx].reply_len = le32_to_cpu(cmd->rxlen);
service->cmds[idx].reply_len_valid = true;
service->cmds[idx].retcode = retcode;
/* a short reply has no rxlen: its length stays invalid */
if (payload_size >= sizeof(*cmd)) {
service->cmds[idx].reply_len = le32_to_cpu(cmd->rxlen);
service->cmds[idx].reply_len_valid = true;
}
if (service->cmds[idx].free_on_ack) {
/* defer freeing until we're no longer in atomic context */
rxbuf = service->cmds[idx].rxbuf;
Expand Down Expand Up @@ -1250,12 +1274,15 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
* 14.7 returns a command result as a NOTIFY whose category is REPLY
* and whose body is a normal epic_cmd. The tag check inside the
* reply handler ignores anything that is not one of our commands.
* A short reply from 14.7 goes to the std service handler; one from
* any other firmware to the reply handler, which completes it as failed.
*/
if ((type == EPIC_TYPE_REPLY ||
(type == EPIC_TYPE_NOTIFY &&
dcp_fw_is_14_7(ep->dcp))) &&
eshdr->category == EPIC_CAT_REPLY &&
payload_size >= sizeof(struct epic_cmd))
(payload_size >= sizeof(struct epic_cmd) ||
!dcp_fw_is_14_7(ep->dcp)))
return afk_recv_handle_reply(ep, channel,
le16_to_cpu(eshdr->tag), payload,
payload_size);
Expand Down Expand Up @@ -1995,7 +2022,7 @@ static int afk_service_call_full(struct apple_epic_service *service, u16 group,
&bfr_len) || bfr_len > U32_MAX)
return -EOVERFLOW;

bfr = kzalloc(bfr_len, GFP_KERNEL);
bfr = kvzalloc(bfr_len, GFP_KERNEL);
if (!bfr)
return -ENOMEM;

Expand Down Expand Up @@ -2075,7 +2102,7 @@ static int afk_service_call_full(struct apple_epic_service *service, u16 group,
}

out:
kfree(bfr);
kvfree(bfr);
return ret;
}

Expand Down
53 changes: 45 additions & 8 deletions drivers/gpu/drm/apple/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
Expand All @@ -31,7 +33,11 @@
#include "audio.h"
#include "parser.h"

#define DCPAUD_ELEMENTS_MAXSIZE 16384
/*
* As macOS 13 asks for. Elements that do not fit come back cut off at the
* buffer's end, with a used size of 0.
*/
#define DCPAUD_ELEMENTS_MAXSIZE SZ_256K
#define DCPAUD_PRODUCTATTRS_MAXSIZE 1024

/*
Expand Down Expand Up @@ -65,6 +71,8 @@ struct dcp_audio {
ktime_t void_start;
u64 void_periods;
struct delayed_work void_work;
/* the stream holds a runtime PM reference, put in sync_stop */
bool pm_held;
void *elements;
void *productattrs;

Expand Down Expand Up @@ -504,7 +512,10 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
if (dcpaud->open_unplugged || !dcpaud_connection_up(dcpaud))
return dcpaud_void_start(substream);

WARN_ON(pm_runtime_get_sync(dcpaud->dev) < 0);
if (!dcpaud->pm_held) {
WARN_ON(pm_runtime_get_sync(dcpaud->dev) < 0);
dcpaud->pm_held = true;
}
ret = dcp_audiosrv_startlink(dcpaud->dcp_dev,
&dcpaud->selected_cookie);
if (ret < 0)
Expand Down Expand Up @@ -535,9 +546,8 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)

case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
/* the runtime PM reference goes in sync_stop */
ret = dcp_audiosrv_stoplink(dcpaud->dcp_dev);
pm_runtime_mark_last_busy(dcpaud->dev);
__pm_runtime_put_autosuspend(dcpaud->dev);
if (ret < 0)
return ret;
break;
Expand All @@ -547,14 +557,32 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
}

/*
* ALSA calls this once a stream has stopped, outside the stream lock, before
* the next hw_params, prepare or hw_free and at close.
* ALSA calls this once a stream has stopped (STOP, SUSPEND, or the STOP
* that undoes a failed START), before the next hw_params, prepare or
* hw_free, at close, at system suspend and at disconnect, outside the
* stream lock. The DMA terminate is asynchronous: putting the runtime PM
* reference in trigger STOP powered the DPA's domain (ps_dpa*) down while
* SIO still ran DMA towards it, and SIO crashed ("Shim DMA unavailable",
* the TERMINATE timing out after it). Wait for the terminate first.
*/
static int dcp_pcm_sync_stop(struct snd_pcm_substream *substream)
{
struct dcp_audio *dcpaud = substream->pcm->private_data;
bool held;

cancel_delayed_work_sync(&dcpaud->void_work);
snd_dmaengine_pcm_sync_stop(substream);

/* START sets it under the stream lock */
snd_pcm_stream_lock_irq(substream);
held = dcpaud->pm_held;
dcpaud->pm_held = false;
snd_pcm_stream_unlock_irq(substream);

if (held) {
pm_runtime_mark_last_busy(dcpaud->dev);
__pm_runtime_put_autosuspend(dcpaud->dev);
}

return 0;
}
Expand Down Expand Up @@ -842,18 +870,27 @@ static const struct component_ops dcpaud_comp_ops = {
.unbind = dcpaud_comp_unbind,
};

static void dcpaud_free_elements(void *elements)
{
kvfree(elements);
}

static int dcpaud_probe(struct platform_device *pdev)
{
struct dcp_audio *dcpaud;
int ret;

dcpaud = devm_kzalloc(&pdev->dev, sizeof(*dcpaud), GFP_KERNEL);
if (!dcpaud)
return -ENOMEM;

dcpaud->elements = devm_kzalloc(&pdev->dev, DCPAUD_ELEMENTS_MAXSIZE,
GFP_KERNEL);
dcpaud->elements = kvzalloc(DCPAUD_ELEMENTS_MAXSIZE, GFP_KERNEL);
if (!dcpaud->elements)
return -ENOMEM;
ret = devm_add_action_or_reset(&pdev->dev, dcpaud_free_elements,
dcpaud->elements);
if (ret)
return ret;

dcpaud->productattrs = devm_kzalloc(&pdev->dev, DCPAUD_PRODUCTATTRS_MAXSIZE,
GFP_KERNEL);
Expand Down
41 changes: 30 additions & 11 deletions drivers/gpu/drm/apple/av.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ struct audiosrv_data {

bool warned_get_elements;
bool warned_get_product_attrs;
bool is_open;
/* generation of srv, and the one that was opened (0: none) */
u64 gen;
u64 open_gen;
/* serialises the open and the close; never taken on the AFK rx worker */
struct mutex open_lock;
};

static void av_interface_init(struct apple_epic_service *service, const char *name,
Expand Down Expand Up @@ -108,6 +112,7 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam
down_write(&asrv->srv_rwsem);
old = asrv->srv;
asrv->srv = service;
asrv->gen++;
up_write(&asrv->srv_rwsem);
if (old)
afk_service_put(old);
Expand All @@ -127,8 +132,11 @@ static void av_audiosrv_teardown(struct apple_epic_service *service)

down_write(&asrv->srv_rwsem);
owned = asrv->srv == service;
if (owned)
if (owned) {
asrv->srv = NULL;
/* an open in flight must not count as this one's */
asrv->gen++;
}
up_write(&asrv->srv_rwsem);

/* A stale generation must not unplug its successor. */
Expand Down Expand Up @@ -215,10 +223,10 @@ dcp_audiosrv_osobject_call(struct apple_epic_service *service, u16 group,
} __attribute__((packed)) *hdr;
static_assert(sizeof(*hdr) == 48);
size_t bfr_len = output_maxsize + sizeof(*hdr);
void *bfr __free(kfree) = NULL;
void *bfr __free(kvfree) = NULL;
int ret;

bfr = kzalloc(bfr_len, GFP_KERNEL);
bfr = kvzalloc(bfr_len, GFP_KERNEL);
if (!bfr)
return -ENOMEM;

Expand Down Expand Up @@ -315,17 +323,20 @@ void av_service_connect(struct apple_dcp *dcp)
{
struct apple_epic_service *service;
struct audiosrv_data *asrv = dcp->audiosrv;
u64 gen;
int ret;

scoped_guard(rwsem_write, &asrv->srv_rwsem) {
service = afk_service_pin(asrv->srv);
if (!service)
return;
gen = asrv->gen;
}

/* open AV audio service */
dev_info(dcp->dev, "%s: starting audio service, plugged:%d\n", __func__, asrv->plugged);
if (asrv->is_open)
mutex_lock(&asrv->open_lock);
if (asrv->open_gen == gen)
goto out;

ret = afk_service_call(service, 0, asrv->cmds.open, NULL, 0, 32,
Expand All @@ -335,31 +346,38 @@ void av_service_connect(struct apple_dcp *dcp)
goto out;
}
mutex_lock(&asrv->plug_lock);
asrv->is_open = true;

if (asrv->audio_dev)
dcpaud_connect(asrv->audio_dev, asrv->plugged);
/* torn down during the open: the next generation is opened afresh */
if (asrv->gen == gen) {
asrv->open_gen = gen;
if (asrv->audio_dev)
dcpaud_connect(asrv->audio_dev, asrv->plugged);
}
mutex_unlock(&asrv->plug_lock);
out:
mutex_unlock(&asrv->open_lock);
afk_service_put(service);
}

void av_service_disconnect(struct apple_dcp *dcp)
{
struct apple_epic_service *service;
struct audiosrv_data *asrv = dcp->audiosrv;
u64 gen;
int ret;

scoped_guard(rwsem_write, &asrv->srv_rwsem) {
service = afk_service_pin(asrv->srv);
if (!service)
return;
gen = asrv->gen;
}

/* close AV audio service */
dev_info(dcp->dev, "%s: stopping audio service\n", __func__);
if (!asrv->is_open)
mutex_lock(&asrv->open_lock);
if (asrv->open_gen != gen)
goto out;
asrv->open_gen = 0;

mutex_lock(&asrv->plug_lock);

Expand All @@ -375,8 +393,8 @@ void av_service_disconnect(struct apple_dcp *dcp)
}
if (service->torndown)
afk_service_disable(service);
asrv->is_open = false;
out:
mutex_unlock(&asrv->open_lock);
afk_service_put(service);
}

Expand Down Expand Up @@ -412,6 +430,7 @@ int avep_init(struct apple_dcp *dcp)
return -ENOMEM;
init_rwsem(&audiosrv_data->srv_rwsem);
mutex_init(&audiosrv_data->plug_lock);
mutex_init(&audiosrv_data->open_lock);

switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
Expand Down
Loading