aboutsummaryrefslogtreecommitdiff
path: root/hw/ppc/pnv_pnor.c
blob: b061106d1c0cb2163231ae1bcbe44768dd162b80 (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
/*
 * QEMU PowerNV PNOR simple model
 *
 * Copyright (c) 2015-2019, IBM Corporation.
 *
 * 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 "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "sysemu/block-backend.h"
#include "sysemu/blockdev.h"
#include "hw/loader.h"
#include "hw/ppc/pnv_pnor.h"
#include "hw/qdev-properties.h"

static uint64_t pnv_pnor_read(void *opaque, hwaddr addr, unsigned size)
{
    PnvPnor *s = PNV_PNOR(opaque);
    uint64_t ret = 0;
    int i;

    for (i = 0; i < size; i++) {
        ret |= (uint64_t) s->storage[addr + i] << (8 * (size - i - 1));
    }

    return ret;
}

static void pnv_pnor_update(PnvPnor *s, int offset, int size)
{
    int offset_end;
    int ret;

    if (s->blk) {
        return;
    }

    offset_end = offset + size;
    offset = QEMU_ALIGN_DOWN(offset, BDRV_SECTOR_SIZE);
    offset_end = QEMU_ALIGN_UP(offset_end, BDRV_SECTOR_SIZE);

    ret = blk_pwrite(s->blk, offset, s->storage + offset,
                     offset_end - offset, 0);
    if (ret < 0) {
        error_report("Could not update PNOR: %s", strerror(-ret));
    }
}

static void pnv_pnor_write(void *opaque, hwaddr addr, uint64_t data,
                           unsigned size)
{
    PnvPnor *s = PNV_PNOR(opaque);
    int i;

    for (i = 0; i < size; i++) {
        s->storage[addr + i] = (data >> (8 * (size - i - 1))) & 0xFF;
    }
    pnv_pnor_update(s, addr, size);
}

/*
 * TODO: Check endianness: skiboot is BIG, Aspeed AHB is LITTLE, flash
 * is BIG.
 */
static const MemoryRegionOps pnv_pnor_ops = {
    .read = pnv_pnor_read,
    .write = pnv_pnor_write,
    .endianness = DEVICE_BIG_ENDIAN,
    .valid = {
        .min_access_size = 1,
        .max_access_size = 4,
    },
};

static void pnv_pnor_realize(DeviceState *dev, Error **errp)
{
    PnvPnor *s = PNV_PNOR(dev);
    int ret;

    if (s->blk) {
        uint64_t perm = BLK_PERM_CONSISTENT_READ |
                        (blk_is_read_only(s->blk) ? 0 : BLK_PERM_WRITE);
        ret = blk_set_perm(s->blk, perm, BLK_PERM_ALL, errp);
        if (ret < 0) {
            return;
        }

        s->size = blk_getlength(s->blk);
        if (s->size <= 0) {
            error_setg(errp, "failed to get flash size");
            return;
        }

        s->storage = blk_blockalign(s->blk, s->size);

        if (blk_pread(s->blk, 0, s->storage, s->size) != s->size) {
            error_setg(errp, "failed to read the initial flash content");
            return;
        }
    } else {
        s->storage = blk_blockalign(NULL, s->size);
        memset(s->storage, 0xFF, s->size);
    }

    memory_region_init_io(&s->mmio, OBJECT(s), &pnv_pnor_ops, s,
                          TYPE_PNV_PNOR, s->size);
}

static Property pnv_pnor_properties[] = {
    DEFINE_PROP_INT64("size", PnvPnor, size, 128 << 20),
    DEFINE_PROP_DRIVE("drive", PnvPnor, blk),
    DEFINE_PROP_END_OF_LIST(),
};

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

    dc->realize = pnv_pnor_realize;
    dc->props = pnv_pnor_properties;
}

static const TypeInfo pnv_pnor_info = {
    .name          = TYPE_PNV_PNOR,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(PnvPnor),
    .class_init    = pnv_pnor_class_init,
};

static void pnv_pnor_register_types(void)
{
    type_register_static(&pnv_pnor_info);
}

type_init(pnv_pnor_register_types)