summaryrefslogtreecommitdiff
path: root/cleopatre/devkit/plcdrv/src/firmware.c
blob: be7934163ea385e3d73ddbeb8e40e582a68595e8 (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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* Cleopatre project {{{
 *
 * Copyright (C) 2012 Spidcom
 *
 * <<<Licence>>>
 *
 * }}} */
/**
 * \file    src/firmware.c
 * \brief   Firmware loading support.
 * \ingroup plcdrv
 */
#include <asm/arch/platform.h>
#include <linux/bug.h>
#include <linux/firmware.h>

#include "firmware.h"
#include "plcdrv.h"
#include "boot_params.h"

/** Define plc.rom informations */
#define ROM_INFO_DELIMITER '\n'
#define ROM_INFO_KEY_DELIMITER ':'
#define ROM_INFO_MAX_SIZE 1024
#define ROM_VERSION_KEY "version"

/**
 * Find the start and size of the ROM info area.
 * \param  file_end  end of the file
 * \param[out]  rom_info_start  start of the ROM info area
 * \param[out]  rom_info_size  size of the ROM info area
 */
static inline void
firmware_find_rom_info (uint8_t *file_end, uint8_t **rom_info_start,
                        size_t *rom_info_size)
{
    uint8_t *infos;
    uint32_t infos_limit;
    int infos_size;

    /* Check parameters. */
    BUG_ON (!file_end);
    BUG_ON (!rom_info_start);
    BUG_ON (!rom_info_size);

    infos = file_end;
    infos_limit = (uint32_t) infos - ROM_INFO_MAX_SIZE;
    infos_size = 0;

    /* No informations area. */
    if (*infos != ROM_INFO_DELIMITER)
        goto not_found;

    /* Find start of informations area
     * We are at the end. We go back until we find a delimiter of the
     * beginning of the area. */
    while (((uint32_t) infos > (infos_limit - 1))
          && ((*infos != ROM_INFO_DELIMITER)
              || (*(infos - 1) != ROM_INFO_DELIMITER)))
    {
        infos--;
        infos_size++;
    }

    /* Informations not found. */
    if ((uint32_t) infos <= infos_limit)
        goto not_found;

    /* There may be more than one delimiter at the beginning of the area.
     * As we need to kwow the exact start of the area, we go up until we see
     * all the delimiters. */
    BUG_ON (*infos != ROM_INFO_DELIMITER);
    while (((uint32_t) infos > infos_limit - 1)
           && (*infos == ROM_INFO_DELIMITER))
    {
        infos--;
        infos_size++;
    }

    *rom_info_start = infos + 1;
    *rom_info_size = infos_size;

    return;

not_found:
    *rom_info_start = NULL;
    *rom_info_size = 0;

    return;
}

/**
 * Get plc.rom version number.
 * \param  rom_info_start  start of the ROM info area.
 * \param  rom_info_size  size of the ROM info area.
 * \param[out]  version  version result buffer.
 */
static inline void
firmware_get_rom_version (uint8_t *rom_info_start, size_t rom_info_size,
                          uint8_t *version)
{
    uint8_t *infos, *p;

    /* Check parameters. */
    BUG_ON (version == NULL);
    if (!rom_info_start || !rom_info_size)
        goto unknown;

    infos = rom_info_start;

    /* Skip delimiters at the beginning of the info area. */
    while ((infos < (rom_info_start + rom_info_size)
            && (*infos == ROM_INFO_DELIMITER)))
    {
        infos++;
    }

    if (infos >= (rom_info_start + rom_info_size))
        goto unknown;

    /* Split informations, format is "key: value\n". */
    for (p = infos; p < (rom_info_start + rom_info_size); p++)
    {
        if ((*p == ROM_INFO_DELIMITER) || (*p == ROM_INFO_KEY_DELIMITER))
            *p = '\0';
    }

    /* Find version key. */
    while (strcasecmp (infos, ROM_VERSION_KEY))
    {
        /* Skip the key. */
        infos += strlen (infos) + 1;
        /* Skip the associated value. */
        infos += strlen (infos) + 1;
    }

    /* Skip version key. */
    infos += strlen (infos) + 1;

    /* Copy version value without first space. */
    strncpy (version, infos + 1, ROM_VERSION_SIZE);

    return;

unknown:
    strcpy (version, "Unknown");

    return;
}

int
firmware_load (struct net_device *dev)
{
    plcdrv_t *priv = netdev_priv (dev);
    int ret;
    const struct firmware *fw;
    uint8_t *plc_fw_base = (uint8_t *) VIRT_PLCCODE_BASE;
    uint8_t *rom_info_start;
    size_t rom_info_size;

    ret = request_firmware (&fw, "plc", &dev->dev);
    if (ret != 0)
        return ret;

    /* Copy firmware. */
    memcpy (plc_fw_base, fw->data, fw->size);

    /* Get boundaries of firmware info region. */
    firmware_find_rom_info (plc_fw_base + fw->size - 1,
                            &rom_info_start, &rom_info_size);

    /* Get firmware version number. */
    firmware_get_rom_version (rom_info_start, rom_info_size,
                              priv->version);

    if (rom_info_start)
    {
        /* End of the .bin file that became the .rom file. */
        uint8_t *bin_eof = rom_info_start;

        /* Pass boot parameters.
         * As a consequence, the rom info that was written to Cesar memory
         * will be overwritten. But that is not a problem because the rom
         * info is not used on Cesar side. */
        plcdrv_pass_boot_params (bin_eof);
    }

    /* Release firmware memory. */
    release_firmware (fw);

    return 0;
}