1/*
2 * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6/*
7 * DesignWare eMMC
8 */
9
10#include <stdlib.h>
11#include <string.h>
12
13#include <sel4vm/guest_vm.h>
14#include <sel4vm/guest_vcpu_fault.h>
15
16#include <sel4vmmplatsupport/guest_memory_util.h>
17#include <sel4vmmplatsupport/plat/vsdhc.h>
18#include <sel4vmmplatsupport/device.h>
19#include <sel4vmmplatsupport/plat/devices.h>
20
21#define DWEMMC_DBADDR_OFFSET    0x088
22#define DWEMMC_DSCADDR_OFFSET   0x094
23#define DWEMMC_BUFADDR_OFFSET   0x098
24
25struct sdhc_priv {
26    /* The VM associated with this device */
27    vm_t *vm;
28    /* Physical registers of the SDHC */
29    void *regs;
30    /* Residual for 64 bit atomic access to FIFO */
31    uint32_t a64;
32};
33
34static memory_fault_result_t handle_sdhc_fault(vm_t *vm, vm_vcpu_t *vcpu, uintptr_t fault_addr, size_t fault_length,
35                                               void *cookie)
36{
37    struct device *d = (struct device *)cookie;
38    struct sdhc_priv *sdhc_data = (struct sdhc_priv *)d->priv;
39    volatile uint32_t *reg;
40    int offset;
41
42    /* Gather fault information */
43    offset = fault_addr - d->pstart;
44    reg = (uint32_t *)(sdhc_data->regs + offset);
45    /* Handle the fault */
46    reg = (volatile uint32_t *)(sdhc_data->regs + offset);
47    if (is_vcpu_read_fault(vcpu)) {
48        if (fault_length == sizeof(uint64_t)) {
49            if (offset & 0x4) {
50                /* Unaligned access: report residual */
51                set_vcpu_fault_data(vcpu, sdhc_data->a64);
52            } else {
53                /* Aligned access: Read in and store residual */
54                uint64_t v;
55                v = *(volatile uint64_t *)reg;
56                set_vcpu_fault_data(vcpu, v);
57                sdhc_data->a64 = v >> 32;
58            }
59        } else {
60            assert(fault_length == sizeof(seL4_Word));
61            set_vcpu_fault_data(vcpu, *reg);
62        }
63        ZF_LOGD("[%s] pc0x%x| r0x%x:0x%x\n", d->name, get_vcpu_fault_ip(vcpu),
64                fault_addr, get_vcpu_fault_data(vcpu));
65    } else {
66        switch (offset & ~0x3) {
67        case DWEMMC_DBADDR_OFFSET:
68        case DWEMMC_DSCADDR_OFFSET:
69        case DWEMMC_BUFADDR_OFFSET:
70            printf("[%s] Restricting DMA access offset 0x%x\n", d->name, offset);
71            break;
72        default:
73            if (fault_length == sizeof(uint64_t)) {
74                if (offset & 0x4) {
75                    /* Unaligned acces: store data and residual */
76                    uint64_t v;
77                    v = ((uint64_t)get_vcpu_fault_data(vcpu) << 32) | sdhc_data->a64;
78                    *(volatile uint64_t *)reg = v;
79                } else {
80                    /* Aligned access: record residual */
81                    sdhc_data->a64 = get_vcpu_fault_data(vcpu);
82                }
83            } else {
84                assert(fault_length == sizeof(seL4_Word));
85                *reg = get_vcpu_fault_data(vcpu);
86            }
87        }
88
89        ZF_LOGD("[%s] pc0x%x| w0x%x:0x%x\n", d->name, get_vcpu_fault_ip(vcpu),
90                fault_addr, get_vcpu_fault_data(vcpu));
91    }
92    advance_vcpu_fault(vcpu);
93    return FAULT_HANDLED;
94}
95
96
97const struct device dev_msh0 = {
98    .name = "MSH0",
99    .pstart = MSH0_PADDR,
100    .size = 0x1000,
101    .priv = NULL
102};
103
104const struct device dev_msh2 = {
105    .name = "MSH2",
106    .pstart = MSH2_PADDR,
107    .size = 0x1000,
108    .priv = NULL
109};
110
111static int vm_install_nodma_sdhc(vm_t *vm, int idx)
112{
113    struct sdhc_priv *sdhc_data;
114    struct device *d;
115    int err;
116    d = calloc(1, sizeof(struct device));
117    if (!d) {
118        return -1;
119    }
120    switch (idx) {
121    case 0:
122        *d = dev_msh0;
123        break;
124    case 2:
125        *d = dev_msh2;
126        break;
127    default:
128        assert(0);
129        return -1;
130    }
131
132    /* Initialise the virtual device */
133    sdhc_data = calloc(1, sizeof(struct sdhc_priv));
134    if (sdhc_data == NULL) {
135        assert(sdhc_data);
136        return -1;
137    }
138    sdhc_data->vm = vm;
139    sdhc_data->regs = create_device_reservation_frame(vm, d->pstart, seL4_CanRead,
140                                                      handle_sdhc_fault, (void *)d);
141    if (sdhc_data->regs == NULL) {
142        assert(sdhc_data->regs);
143        return -1;
144    }
145    d->priv = sdhc_data;
146    return 0;
147}
148
149int vm_install_nodma_sdhc0(vm_t *vm)
150{
151    return vm_install_nodma_sdhc(vm, 0);
152}
153
154int vm_install_nodma_sdhc2(vm_t *vm)
155{
156    return vm_install_nodma_sdhc(vm, 2);
157}
158