aboutsummaryrefslogtreecommitdiff
path: root/hw/misc/aspeed_peci.c
blob: 9025b35f8366e16ca4fd0efd837fa55fc8c3ab8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * Aspeed PECI Controller
 *
 * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
 *
 * This code is licensed under the GPL version 2 or later. See the COPYING
 * file in the top-level directory.
 */

#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/irq.h"
#include "hw/misc/aspeed_peci.h"
#include "hw/registerfields.h"
#include "trace.h"

#define ASPEED_PECI_CC_RSP_SUCCESS (0x40U)

/* Command Register */
REG32(PECI_CMD, 0x08)
    FIELD(PECI_CMD, FIRE, 0, 1)

/* Interrupt Control Register */
REG32(PECI_INT_CTRL, 0x18)

/* Interrupt Status Register */
REG32(PECI_INT_STS, 0x1C)
    FIELD(PECI_INT_STS, CMD_DONE, 0, 1)

/* Rx/Tx Data Buffer Registers */
REG32(PECI_WR_DATA0, 0x20)
REG32(PECI_RD_DATA0, 0x30)

static void aspeed_peci_raise_interrupt(AspeedPECIState *s, uint32_t status)
{
    trace_aspeed_peci_raise_interrupt(s->regs[R_PECI_INT_CTRL], status);

    s->regs[R_PECI_INT_STS] = s->regs[R_PECI_INT_CTRL] & status;
    if (!s->regs[R_PECI_INT_STS]) {
        return;
    }
    qemu_irq_raise(s->irq);
}

static uint64_t aspeed_peci_read(void *opaque, hwaddr offset, unsigned size)
{
    AspeedPECIState *s = ASPEED_PECI(opaque);
    uint64_t data;

    if (offset >= ASPEED_PECI_NR_REGS << 2) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
                      __func__, offset);
        return 0;
    }
    data = s->regs[offset >> 2];

    trace_aspeed_peci_read(offset, data);
    return data;
}

static void aspeed_peci_write(void *opaque, hwaddr offset, uint64_t data,
                              unsigned size)
{
    AspeedPECIState *s = ASPEED_PECI(opaque);

    trace_aspeed_peci_write(offset, data);

    if (offset >= ASPEED_PECI_NR_REGS << 2) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
                      __func__, offset);
        return;
    }

    switch (offset) {
    case A_PECI_INT_STS:
        s->regs[R_PECI_INT_STS] &= ~data;
        if (!s->regs[R_PECI_INT_STS]) {
            qemu_irq_lower(s->irq);
        }
        break;
    case A_PECI_CMD:
        /*
         * Only the FIRE bit is writable. Once the command is complete, it
         * should be cleared. Since we complete the command immediately, the
         * value is not stored in the register array.
         */
        if (!FIELD_EX32(data, PECI_CMD, FIRE)) {
            break;
        }
        if (s->regs[R_PECI_INT_STS]) {
            qemu_log_mask(LOG_GUEST_ERROR, "%s: Interrupt status must be "
                          "cleared before firing another command: 0x%08x\n",
                          __func__, s->regs[R_PECI_INT_STS]);
            break;
        }
        s->regs[R_PECI_RD_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS;
        s->regs[R_PECI_WR_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS;
        aspeed_peci_raise_interrupt(s,
                                    FIELD_DP32(0, PECI_INT_STS, CMD_DONE, 1));
        break;
    default:
        s->regs[offset / sizeof(s->regs[0])] = data;
        break;
    }
}

static const MemoryRegionOps aspeed_peci_ops = {
    .read = aspeed_peci_read,
    .write = aspeed_peci_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

static void aspeed_peci_realize(DeviceState *dev, Error **errp)
{
    AspeedPECIState *s = ASPEED_PECI(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

    memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_peci_ops, s,
                          TYPE_ASPEED_PECI, 0x1000);
    sysbus_init_mmio(sbd, &s->mmio);
    sysbus_init_irq(sbd, &s->irq);
}

static void aspeed_peci_reset(DeviceState *dev)
{
    AspeedPECIState *s = ASPEED_PECI(dev);

    memset(s->regs, 0, sizeof(s->regs));
}

static void aspeed_peci_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = aspeed_peci_realize;
    device_class_set_legacy_reset(dc, aspeed_peci_reset);
    dc->desc = "Aspeed PECI Controller";
}

static const TypeInfo aspeed_peci_types[] = {
    {
        .name = TYPE_ASPEED_PECI,
        .parent = TYPE_SYS_BUS_DEVICE,
        .instance_size = sizeof(AspeedPECIState),
        .class_init = aspeed_peci_class_init,
        .abstract = false,
    },
};

DEFINE_TYPES(aspeed_peci_types);