1/*
2 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "los_debug.h"
17#include "los_arch_interrupt.h"
18#include "los_interrupt.h"
19#include "lwip/mem.h"
20#include "virtmmio.h"
21
22#define IRQF_SHARED 0
23
24static inline uint32_t VirtioGetStatus(const struct VirtmmioDev *dev)
25{
26    return GET_UINT32(dev->base + VIRTMMIO_REG_STATUS);
27}
28
29static inline void VirtioAddStatus(const struct VirtmmioDev *dev, uint32_t val)
30{
31    FENCE_WRITE_UINT32(VirtioGetStatus(dev) | val, dev->base + VIRTMMIO_REG_STATUS);
32}
33
34static inline void VirtioResetStatus(const struct VirtmmioDev *dev)
35{
36    FENCE_WRITE_UINT32(VIRTIO_STATUS_RESET, dev->base + VIRTMMIO_REG_STATUS);
37}
38
39bool VirtmmioDiscover(uint32_t devId, struct VirtmmioDev *dev)
40{
41    VADDR_T base;
42    int i;
43
44    base = IO_DEVICE_ADDR(VIRTMMIO_BASE_ADDR) + VIRTMMIO_BASE_SIZE * (NUM_VIRTIO_TRANSPORTS - 1);
45    for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) {
46        if ((GET_UINT32(base + VIRTMMIO_REG_MAGICVALUE) == VIRTMMIO_MAGIC) &&
47            (GET_UINT32(base + VIRTMMIO_REG_VERSION) == VIRTMMIO_VERSION) &&
48            (GET_UINT32(base + VIRTMMIO_REG_DEVICEID) == devId)) {
49            dev->base = base;
50            dev->irq = IRQ_SPI_BASE + VIRTMMIO_BASE_IRQ + i;
51            return true;
52        }
53
54        base -= VIRTMMIO_BASE_SIZE;
55    }
56
57    PRINT_ERR("virtio-mmio ID=%u device not found\n", devId);
58    return false;
59}
60
61unsigned VirtqSize(uint16_t qsz)
62{
63           /* pretend we do not have an aligned start address */
64    return VIRTQ_ALIGN_DESC - 1 +
65           ALIGN(sizeof(struct VirtqDesc) * qsz, VIRTQ_ALIGN_AVAIL) +
66           ALIGN(sizeof(struct VirtqAvail) + sizeof(uint16_t) * qsz, VIRTQ_ALIGN_USED) +
67           sizeof(struct VirtqUsed) + sizeof(struct VirtqUsedElem) * qsz;
68}
69
70void VirtmmioInitBegin(const struct VirtmmioDev *dev)
71{
72    VirtioResetStatus(dev);
73    VirtioAddStatus(dev, VIRTIO_STATUS_ACK);
74    VirtioAddStatus(dev, VIRTIO_STATUS_DRIVER);
75    while ((VirtioGetStatus(dev) & VIRTIO_STATUS_DRIVER) == 0) { }
76}
77
78void VritmmioInitEnd(const struct VirtmmioDev *dev)
79{
80    VirtioAddStatus(dev, VIRTIO_STATUS_DRIVER_OK);
81}
82
83void VirtmmioInitFailed(const struct VirtmmioDev *dev)
84{
85    VirtioAddStatus(dev, VIRTIO_STATUS_FAILED);
86}
87
88static bool Negotiate(struct VirtmmioDev *baseDev, uint32_t nth, VirtioFeatureFn fn, void *dev)
89{
90    uint32_t features, supported, before, after;
91
92    FENCE_WRITE_UINT32(nth, baseDev->base + VIRTMMIO_REG_DEVFEATURESEL);
93    features = GET_UINT32(baseDev->base + VIRTMMIO_REG_DEVFEATURE);
94
95    do {
96        before = GET_UINT32(baseDev->base + VIRTMMIO_REG_CONFIGGENERATION);
97
98        supported = 0;
99        if (!fn(features, &supported, dev)) {
100            return false;
101        }
102
103        after = GET_UINT32(baseDev->base + VIRTMMIO_REG_CONFIGGENERATION);
104    } while (before != after);
105
106    FENCE_WRITE_UINT32(nth, baseDev->base + VIRTMMIO_REG_DRVFEATURESEL);
107    FENCE_WRITE_UINT32(supported, baseDev->base + VIRTMMIO_REG_DRVFEATURE);
108    return true;
109}
110
111bool VirtmmioNegotiate(struct VirtmmioDev *baseDev, VirtioFeatureFn f0, VirtioFeatureFn f1, void *dev)
112{
113    if(!Negotiate(baseDev, VIRTIO_FEATURE_WORD0, f0, dev)) {
114        return false;
115    }
116
117    if(!Negotiate(baseDev, VIRTIO_FEATURE_WORD1, f1, dev)) {
118        return false;
119    }
120
121    VirtioAddStatus(baseDev, VIRTIO_STATUS_FEATURES_OK);
122    if ((VirtioGetStatus(baseDev) & VIRTIO_STATUS_FEATURES_OK) == 0) {
123        PRINT_ERR("negotiate features failed\n");
124        return false;
125    }
126
127    return true;
128}
129
130#define U32_MASK        0xFFFFFFFF
131#define U32_BYTES       4
132#define U64_32_SHIFT    32
133uint64_t u32_to_u64(uint32_t addr) {
134    uint64_t paddr = (uint64_t)addr & U32_MASK;
135    return paddr;
136}
137static void WriteQueueAddr(uint64_t addr, const struct VirtmmioDev *dev, uint32_t regLow)
138{
139    uint32_t paddr;
140
141    paddr = addr & U32_MASK;
142    FENCE_WRITE_UINT32(paddr, dev->base + regLow);
143    paddr = addr >> U64_32_SHIFT;
144    FENCE_WRITE_UINT32(paddr, dev->base + regLow + U32_BYTES);
145}
146
147static bool CompleteConfigQueue(uint32_t queue, const struct VirtmmioDev *dev)
148{
149    const struct Virtq *q = &dev->vq[queue];
150    uint32_t num;
151
152    FENCE_WRITE_UINT32(queue, dev->base + VIRTMMIO_REG_QUEUESEL);
153
154    num = GET_UINT32(dev->base + VIRTMMIO_REG_QUEUEREADY);
155    LOS_ASSERT(num == 0);
156    num = GET_UINT32(dev->base + VIRTMMIO_REG_QUEUENUMMAX);
157    if (num < q->qsz) {
158        PRINT_ERR("queue %u not available: max qsz=%u, requested=%u\n", queue, num, q->qsz);
159        return false;
160    }
161
162    FENCE_WRITE_UINT32(q->qsz, dev->base + VIRTMMIO_REG_QUEUENUM);
163    WriteQueueAddr(u32_to_u64(q->desc), dev, VIRTMMIO_REG_QUEUEDESCLOW);
164    WriteQueueAddr(u32_to_u64(q->avail), dev, VIRTMMIO_REG_QUEUEDRIVERLOW);
165    WriteQueueAddr(u32_to_u64(q->used), dev, VIRTMMIO_REG_QUEUEDEVICELOW);
166
167    FENCE_WRITE_UINT32(1, dev->base + VIRTMMIO_REG_QUEUEREADY);
168    return true;
169}
170
171static VADDR_T CalculateQueueAddr(VADDR_T base, uint16_t qsz, struct Virtq *q)
172{
173    base = ALIGN(base, VIRTQ_ALIGN_DESC);
174    q->desc = (struct VirtqDesc *)base;
175    q->qsz = qsz;
176    base = ALIGN(base + sizeof(struct VirtqDesc) * qsz, VIRTQ_ALIGN_AVAIL);
177    q->avail = (struct VirtqAvail *)base;
178    base = ALIGN(base + sizeof(struct VirtqAvail) + sizeof(uint16_t) * qsz, VIRTQ_ALIGN_USED);
179    q->used = (struct VirtqUsed *)base;
180
181    return base + sizeof(struct VirtqUsed) + sizeof(struct VirtqUsedElem) * qsz;
182}
183
184VADDR_T VirtmmioConfigQueue(struct VirtmmioDev *dev, VADDR_T base, uint16_t qsz[], int num)
185{
186    uint32_t i;
187
188    for (i = 0; i < num; i++) {
189        base = CalculateQueueAddr(base, qsz[i], &dev->vq[i]);
190        if (!CompleteConfigQueue(i, dev)) {
191            return 0;
192        }
193    }
194
195    return base;
196}
197
198bool VirtmmioRegisterIRQ(struct VirtmmioDev *dev, HWI_PROC_FUNC handle, void *argDev, const char *devName)
199{
200    uint32_t ret;
201    HwiIrqParam *param = mem_calloc(1, sizeof(HwiIrqParam));
202    param->swIrq = dev->irq;
203    param->pDevId = argDev;
204    param->pName = devName;
205
206    ret = LOS_HwiCreate(dev->irq, OS_HWI_PRIO_HIGHEST, IRQF_SHARED, handle, param);
207    if (ret != 0) {
208        PRINT_ERR("virtio-mmio %s IRQ register failed: %u\n", devName, ret);
209        return false;
210    }
211
212    HalIrqEnable(dev->irq);
213    return true;
214}
215