Skip to content
Closed
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
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
modname := apple-bce
obj-m += $(modname).o
obj-m += apple-bce.o

apple-bce-objs := apple_bce.o mailbox.o queue.o queue_dma.o vhci/vhci.o vhci/queue.o vhci/transfer.o audio/audio.o audio/protocol.o audio/protocol_bce.o audio/pcm.o
apple-bce-objs := apple_bce.o mailbox.o queue.o queue_dma.o vhci/vhci.o vhci/queue.o vhci/transfer.o audio/audio.o audio/protocol.o audio/protocol_bce.o audio/pcm.o video/video.o video/protocol.o video/encoder.o

MY_CFLAGS += -DWITHOUT_NVME_PATCH
#MY_CFLAGS += -g -DDEBUG
Expand Down
66 changes: 60 additions & 6 deletions apple_bce.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static int apple_bce_probe(struct pci_dev *dev, const struct pci_device_id *id)
bce->devt = bce_chrdev;
bce->dev = device_create(bce_class, &dev->dev, bce->devt, NULL, "apple-bce");
if (IS_ERR_OR_NULL(bce->dev)) {
status = PTR_ERR(bce_class);
status = PTR_ERR(bce->dev);
goto fail;
}

Expand Down Expand Up @@ -101,10 +101,26 @@ static int apple_bce_probe(struct pci_dev *dev, const struct pci_device_id *id)

global_bce = bce;

bce_vhci_create(bce, &bce->vhci);
if ((status = bce_vhci_create(bce, &bce->vhci))) {
pr_err("apple-bce: VHCI creation failed\n");
goto fail_vhci;
}

if ((status = bce_ave_create(bce)))
pr_warn("apple-bce: AVE encoder init failed (%d), continuing without video\n", status);

/* The T2 chip requires function 0 (NVMe) to be a bus master for DMA
* on our function. Create a device link for runtime PM ordering.
* (System S3 ordering is already handled by PCI function numbering.) */
bce->pci0_link = device_link_add(&dev->dev, &bce->pci0->dev,
DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME);
if (!bce->pci0_link)
dev_warn(&dev->dev, "apple-bce: failed to create device link to function 0\n");

return 0;

fail_vhci:
bce_free_command_queues(bce);
fail_ts:
bce_timestamp_stop(&bce->timestamp);
#ifndef WITHOUT_NVME_PATCH
Expand Down Expand Up @@ -158,14 +174,16 @@ static int bce_create_command_queues(struct apple_bce_device *bce)
}
bce_get_cq_memcfg(bce->cmd_cq, cfg);
if ((status = bce_register_command_queue(bce, cfg, false)))
goto err;
goto err_cfg;
bce_get_sq_memcfg(bce->cmd_cmdq->sq, bce->cmd_cq, cfg);
if ((status = bce_register_command_queue(bce, cfg, true)))
goto err;
goto err_cfg;
kfree(cfg);

return 0;

err_cfg:
kfree(cfg);
err:
if (bce->cmd_cq)
bce_free_cq(bce, bce->cmd_cq);
Expand All @@ -180,6 +198,7 @@ static void bce_free_command_queues(struct apple_bce_device *bce)
bce_free_cmdq(bce, bce->cmd_cmdq);
bce->cmd_cq = NULL;
bce->queues[0] = NULL;
bce->queues[1] = NULL;
}

static irqreturn_t bce_handle_mb_irq(int irq, void *dev)
Expand Down Expand Up @@ -241,8 +260,12 @@ static void apple_bce_remove(struct pci_dev *dev)
struct apple_bce_device *bce = pci_get_drvdata(dev);
bce->is_being_removed = true;

bce_ave_destroy();
bce_vhci_destroy(&bce->vhci);

if (bce->pci0_link)
device_link_del(bce->pci0_link);

bce_timestamp_stop(&bce->timestamp);
#ifndef WITHOUT_NVME_PATCH
pci_disable_device(bce->pci0);
Expand Down Expand Up @@ -349,22 +372,54 @@ static int apple_bce_suspend(struct device *dev)
if ((status = bce_save_state_and_sleep(bce)))
return status;

/* Disable DMA IRQ after T2 is asleep. On resume, PCI core powers the
* device back on before apple_bce_resume() runs — the disabled IRQ
* prevents stale completion processing during that transition window. */
disable_irq(pci_irq_vector(bce->pci, 4));

return 0;
}

static int apple_bce_resume(struct device *dev)
{
struct apple_bce_device *bce = pci_get_drvdata(to_pci_dev(dev));
int status;
int i;
u16 vid;

/* Wait for T2 PCIe link to re-train after S3.
* MMIO to the T2 BARs will hang the CPU if the link is down.
* Config space reads go through the root port and return 0xFFFF safely.
* Poll aggressively first (link usually retrains in ~100-200ms),
* then back off to 50ms intervals. */
for (i = 0; i < 120; i++) {
pci_read_config_word(bce->pci, PCI_VENDOR_ID, &vid);
if (vid == PCI_VENDOR_ID_APPLE)
break;
if (i < 40)
usleep_range(2000, 3000);
else
msleep(50);
}
if (vid != PCI_VENDOR_ID_APPLE) {
pr_err("apple-bce: resume: T2 not accessible after timeout (vid=0x%04x)\n", vid);
enable_irq(pci_irq_vector(bce->pci, 4));
return -ENODEV;
}

pci_set_master(bce->pci);
pci_set_master(bce->pci0);

if ((status = bce_restore_state_and_wake(bce)))
if ((status = bce_restore_state_and_wake(bce))) {
enable_irq(pci_irq_vector(bce->pci, 4));
return status;
}

bce_timestamp_start(&bce->timestamp, false);

/* Re-enable DMA IRQ now that T2 state is restored and bus mastering is on. */
enable_irq(pci_irq_vector(bce->pci, 4));

return 0;
}

Expand Down Expand Up @@ -418,7 +473,6 @@ static int __init apple_bce_module_init(void)
return 0;

fail_drv:
pci_unregister_driver(&apple_bce_pci_driver);
fail_class:
class_destroy(bce_class);
fail_chrdev:
Expand Down
5 changes: 4 additions & 1 deletion apple_bce.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

struct apple_bce_device {
struct pci_dev *pci, *pci0;
struct device_link *pci0_link;
dev_t devt;
struct device *dev;
void __iomem *reg_mem_mb;
Expand All @@ -28,7 +29,6 @@ struct apple_bce_device {
struct bce_queue_cmdq *cmd_cmdq;
struct bce_queue_sq *int_sq_list[BCE_MAX_QUEUE_COUNT];
bool is_being_removed;

dma_addr_t saved_data_dma_addr;
void *saved_data_dma_ptr;
size_t saved_data_dma_size;
Expand All @@ -38,4 +38,7 @@ struct apple_bce_device {

extern struct apple_bce_device *global_bce;

int bce_ave_create(struct apple_bce_device *bce);
void bce_ave_destroy(void);

#endif //APPLE_BCE_H
79 changes: 61 additions & 18 deletions audio/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ static int aaudio_probe(struct pci_dev *dev, const struct pci_device_id *id)
aaudio->devt = aaudio_chrdev;
aaudio->dev = device_create(aaudio_class, &dev->dev, aaudio->devt, NULL, "aaudio");
if (IS_ERR_OR_NULL(aaudio->dev)) {
status = PTR_ERR(aaudio_class);
status = PTR_ERR(aaudio->dev);
goto fail;
}
device_link_add(aaudio->dev, aaudio->bce->dev, DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER);
Expand All @@ -73,7 +73,7 @@ static int aaudio_probe(struct pci_dev *dev, const struct pci_device_id *id)

dev_info(aaudio->dev, "aaudio: bs len = %llx\n", pci_resource_len(dev, 0));
aaudio->reg_mem_bs_dma = pci_resource_start(dev, 0);
aaudio->reg_mem_bs = pci_iomap(dev, 0, 0);
aaudio->reg_mem_bs = pci_iomap_wc(dev, 0, 0);
aaudio->reg_mem_cfg = pci_iomap(dev, 4, 0);

aaudio->reg_mem_gpr = (u32 __iomem *) ((u8 __iomem *) aaudio->reg_mem_cfg + 0xC000);
Expand All @@ -90,7 +90,7 @@ static int aaudio_probe(struct pci_dev *dev, const struct pci_device_id *id)

if (snd_card_new(aaudio->dev, aaudio_alsa_index, aaudio_alsa_id, THIS_MODULE, 0, &aaudio->card)) {
dev_err(&dev->dev, "aaudio: Failed to create ALSA card\n");
goto fail;
goto fail_bce;
}

strcpy(aaudio->card->shortname, "Apple T2 Audio");
Expand All @@ -111,7 +111,7 @@ static int aaudio_probe(struct pci_dev *dev, const struct pci_device_id *id)

if ((status = aaudio_cmd_set_remote_access(aaudio, AAUDIO_REMOTE_ACCESS_ON))) {
dev_err(&dev->dev, "Failed to set remote access\n");
return status;
goto fail_snd;
}

if (snd_card_register(aaudio->card)) {
Expand All @@ -133,15 +133,18 @@ static int aaudio_probe(struct pci_dev *dev, const struct pci_device_id *id)

fail_snd:
snd_card_free(aaudio->card);
fail_bce:
aaudio_bce_free(aaudio);
fail:
if (aaudio && aaudio->dev)
device_destroy(aaudio_class, aaudio->devt);
kfree(aaudio);

if (!IS_ERR_OR_NULL(aaudio->reg_mem_bs))
pci_iounmap(dev, aaudio->reg_mem_bs);
if (!IS_ERR_OR_NULL(aaudio->reg_mem_cfg))
pci_iounmap(dev, aaudio->reg_mem_cfg);
if (aaudio) {
if (!IS_ERR_OR_NULL(aaudio->reg_mem_bs))
pci_iounmap(dev, aaudio->reg_mem_bs);
if (!IS_ERR_OR_NULL(aaudio->reg_mem_cfg))
pci_iounmap(dev, aaudio->reg_mem_cfg);
if (aaudio->dev)
device_destroy(aaudio_class, aaudio->devt);
kfree(aaudio);
}

pci_release_regions(dev);
pci_disable_device(dev);
Expand All @@ -164,6 +167,7 @@ static void aaudio_remove(struct pci_dev *dev)
list_del(&sdev->list);
aaudio_free_dev(sdev);
}
aaudio_bce_free(aaudio);
pci_iounmap(dev, aaudio->reg_mem_bs);
pci_iounmap(dev, aaudio->reg_mem_cfg);
device_destroy(aaudio_class, aaudio->devt);
Expand All @@ -176,6 +180,32 @@ static void aaudio_remove(struct pci_dev *dev)
static int aaudio_suspend(struct device *dev)
{
struct aaudio_device *aaudio = pci_get_drvdata(to_pci_dev(dev));
struct aaudio_subdevice *sdev;
size_t i;

/* Stop running streams and tell ALSA they are suspended. Without this,
* ALSA keeps polling aaudio_pcm_pointer() after resume while
* remote_timestamp is stale, producing wildly wrong position values
* ("invalid position" errors). */
list_for_each_entry(sdev, &aaudio->subdevice_list, list) {
bool stopped_io = false;
for (i = 0; i < sdev->out_stream_cnt; i++) {
if (sdev->out_streams[i].started) {
stopped_io = true;
sdev->out_streams[i].started = 0;
}
}
for (i = 0; i < sdev->in_stream_cnt; i++) {
if (sdev->in_streams[i].started) {
stopped_io = true;
sdev->in_streams[i].started = 0;
}
}
if (stopped_io)
aaudio_cmd_stop_io(sdev->a, sdev->dev_id);
if (sdev->pcm)
snd_pcm_suspend_all(sdev->pcm);
}

if (aaudio_cmd_set_remote_access(aaudio, AAUDIO_REMOTE_ACCESS_OFF))
dev_warn(aaudio->dev, "Failed to reset remote access\n");
Expand Down Expand Up @@ -259,7 +289,7 @@ static void aaudio_init_dev(struct aaudio_device *a, aaudio_device_id_t dev_id)
sdev->dev_id = dev_id;
sdev->buf_id = AAUDIO_BUFFER_ID_NONE;
strncpy(sdev->uid, uid, uid_len);
sdev->uid[uid_len + 1] = '\0';
sdev->uid[uid_len] = '\0';

if (aaudio_cmd_get_primitive_property(a, dev_id, dev_id,
AAUDIO_PROP(AAUDIO_PROP_SCOPE_INPUT, AAUDIO_PROP_LATENCY, 0), NULL, 0, &sdev->in_latency, sizeof(u32)))
Expand Down Expand Up @@ -342,8 +372,14 @@ static void aaudio_free_dev(struct aaudio_subdevice *sdev)
for (i = 0; i < sdev->in_stream_cnt; i++) {
if (sdev->in_streams[i].alsa_hw_desc)
kfree(sdev->in_streams[i].alsa_hw_desc);
if (sdev->in_streams[i].buffers)
if (sdev->in_streams[i].buffers) {
if (sdev->in_streams[i].host_allocated)
dma_free_coherent(&sdev->a->pci->dev,
sdev->in_streams[i].buffers[0].size,
sdev->in_streams[i].buffers[0].ptr,
sdev->in_streams[i].buffers[0].dma_addr);
kfree(sdev->in_streams[i].buffers);
}
}
for (i = 0; i < sdev->out_stream_cnt; i++) {
if (sdev->out_streams[i].alsa_hw_desc)
Expand Down Expand Up @@ -426,9 +462,13 @@ static int aaudio_init_bs(struct aaudio_device *a)
list_for_each_entry(sdev, &a->subdevice_list, list) {
if (sdev->buf_id != AAUDIO_BUFFER_ID_NONE)
continue;
if (i >= ARRAY_SIZE(a->bs->devices)) {
dev_warn(a->dev, "aaudio: Too many devices, skipping %s\n", sdev->uid);
break;
}
sdev->buf_id = i;
dev_info(a->dev, "aaudio: Created device %i %s\n", i, sdev->uid);
strcpy(a->bs->devices[i].name, sdev->uid);
strscpy(a->bs->devices[i].name, sdev->uid, sizeof(a->bs->devices[i].name));
a->bs->devices[i].num_input_streams = 0;
a->bs->devices[i].num_output_streams = 0;
a->bs->num_devices = ++i;
Expand Down Expand Up @@ -487,7 +527,7 @@ static void aaudio_init_bs_stream_host(struct aaudio_device *a, struct aaudio_st
size_t size;
dma_addr_t dma_addr;
void *dma_ptr;
size = strm->desc.bytes_per_packet * 16640;
size = strm->desc.bytes_per_packet * 4096;
dma_ptr = dma_alloc_coherent(&a->pci->dev, size, &dma_addr, GFP_KERNEL);
if (!dma_ptr) {
dev_err(a->dev, "dma_alloc_coherent failed\n");
Expand All @@ -508,6 +548,7 @@ static void aaudio_init_bs_stream_host(struct aaudio_device *a, struct aaudio_st
strm->buffers[0].dma_addr = dma_addr;
strm->buffers[0].ptr = dma_ptr;
strm->buffers[0].size = size;
strm->host_allocated = true;

strm->alsa_hw_desc = kmalloc(sizeof(struct snd_pcm_hardware), GFP_KERNEL);
if (aaudio_create_hw_info(&strm->desc, strm->alsa_hw_desc, strm->buffers[0].size)) {
Expand Down Expand Up @@ -597,6 +638,8 @@ void aaudio_handle_prop_change(struct aaudio_device *a, struct aaudio_msg *msg)
* is not possible when we are in the reply parsing code's context. */
struct aaudio_prop_change_work_struct *work;
work = kmalloc(sizeof(struct aaudio_prop_change_work_struct), GFP_KERNEL);
if (!work)
return;
work->a = a;
INIT_WORK(&work->ws, aaudio_handle_prop_change_work);
aaudio_msg_read_property_changed(msg, &work->dev, &work->obj, &work->prop);
Expand All @@ -617,7 +660,8 @@ void aaudio_handle_cmd_timestamp(struct aaudio_device *a, struct aaudio_msg *msg
dev_dbg(a->dev, "Received timestamp update for dev=%llx ts=%llx seed=%llx\n", devid, timestamp, update_seed);

sdev = aaudio_find_dev_by_dev_id(a, devid);
aaudio_handle_timestamp(sdev, time_os, timestamp);
if (sdev)
aaudio_handle_timestamp(sdev, time_os, timestamp);

aaudio_send_cmd_response(a, &sctx, msg,
aaudio_msg_write_update_timestamp_response);
Expand Down Expand Up @@ -679,7 +723,6 @@ int aaudio_module_init(void)
return 0;

fail_drv:
pci_unregister_driver(&aaudio_pci_driver);
fail_class:
class_destroy(aaudio_class);
fail_chrdev:
Expand Down
2 changes: 2 additions & 0 deletions audio/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ struct aaudio_stream {
struct aaudio_apple_description desc;
struct snd_pcm_hardware *alsa_hw_desc;
u32 latency;
bool host_allocated;

bool waiting_for_first_ts;

ktime_t remote_timestamp;
snd_pcm_sframes_t frame_min;
int started;
unsigned int elapsed_count;
};
struct aaudio_subdevice {
struct aaudio_device *a;
Expand Down
Loading