File: dfusedevice.cpp

package info (click to toggle)
qflipper 1.3.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 12,320 kB
  • sloc: cpp: 18,500; sh: 247; ansic: 191; xml: 38; python: 14; makefile: 5
file content (301 lines) | stat: -rw-r--r-- 9,886 bytes parent folder | download | duplicates (2)
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#include "dfusedevice.h"

#include <cmath>

#include <QThread>
#include <QBuffer>
#include <QByteArray>

#include "debug.h"
#include "dfumemorylayout.h"

#define REQUEST_OUT (USBRequest::ENDPOINT_OUT | USBRequest::REQUEST_TYPE_CLASS | USBRequest::RECIPIENT_INTERFACE)
#define REQUEST_IN (USBRequest::ENDPOINT_IN | USBRequest::REQUEST_TYPE_CLASS | USBRequest::RECIPIENT_INTERFACE)

#define DFU_DESCRIPTOR_LENGTH 9
#define DFU_DESCRIPTOR_TYPE 0x21

DfuseDevice::DfuseDevice(const USBDeviceInfo &info, QObject *parent):
    USBDevice(info, parent)
{}

bool DfuseDevice::beginTransaction()
{
    return open() && claimInterface(0);
}

bool DfuseDevice::endTransaction()
{
    const auto res = releaseInterface(0);
    close();
    return res;
}

uint32_t DfuseDevice::partitionOrigin(uint8_t alt)
{
    const auto desc = stringInterfaceDescriptor(alt);
    const auto layout = DFUMemoryLayout::fromStringDescriptor(desc);
    return layout.address();
}

bool DfuseDevice::erase(uint32_t addr, size_t maxSize)
{
    check_return_bool(prepare(), "Failed to prepare the device");

    const auto layout = DFUMemoryLayout::fromStringDescriptor(stringInterfaceDescriptor(0));
    const auto pageAddresses = layout.pageAddresses(addr, addr + (uint32_t)maxSize);

    check_return_bool(!pageAddresses.isEmpty(), "Address list is empty");

    size_t prevProgress = std::numeric_limits<size_t>::max();

    for(const auto pageAddress: pageAddresses) {
        check_return_bool(erasePage(pageAddress), "Failed to erase page");
        const auto progress = (pageAddress - addr) * 100.0 / maxSize;
        emit progressChanged(Operation::Erase, progress);

        if(floor(progress) != prevProgress) {
            prevProgress = progress;
            debug_msg(QString("Erasing memory: %1%").arg(prevProgress));
        }

    }

    debug_msg("Erase done.");

    return true;
}

bool DfuseDevice::download(DfuseFile *file)
{
    check_return_bool(file->isValid(), "DfuSe file is not valid");
    // TODO: check for vendor, device, etc.

    // Erase the required memory first
    for(auto &img : file->images()) {
        for(auto &elem : img.elements) {
            check_return_bool(erase(elem.dwElementAddress, elem.dwElementSize), "Failed to erase the memory");
        }
    }

    for(auto &img : file->images()) {
        for(auto &elem : img.elements) {
            QBuffer buf(&elem.data);
            buf.open(QIODevice::ReadOnly);
            check_return_bool(download(&buf, elem.dwElementAddress, img.prefix.bAlternateSetting), "Failed to download element");
        }
    }

    return true;
}

bool DfuseDevice::download(const QByteArray &data)
{
    check_return_bool(setInterfaceAltSetting(0, 0), "Failed to set interface alternate setting");
    check_return_bool(prepare(), "Failed to prepare the device");

    check_return_bool(controlTransfer(REQUEST_OUT, DFU_DNLOAD, 0, 0, data), "Failed to perform raw download request");

    StatusType status;

    do {
        status = getStatus();
        check_return_bool(status.bStatus == StatusType::OK, "Failed to raw download a buffer");
        QThread::msleep(status.bwPollTimeout);

    } while(status.bState == StatusType::DFU_DNBUSY);

    return true;
}

bool DfuseDevice::download(QIODevice *file, uint32_t addr, uint8_t alt)
{
    check_return_bool(setInterfaceAltSetting(0, alt), "Failed to set interface alternate setting");
    check_return_bool(prepare(), "Failed to prepare the device");
    check_return_bool(setAddressPointer(addr), "Failed to set address pointer");

    const auto extra = extraInterfaceDescriptor(0, DFU_DESCRIPTOR_TYPE, DFU_DESCRIPTOR_LENGTH);
    check_return_bool(extra.size() >= DFU_DESCRIPTOR_LENGTH, "No functional DFU descriptor");

    const auto maxTransferSize = *((uint16_t*)(extra.data() + 5));
    debug_msg(QString("Device reported transfer size: %1").arg(maxTransferSize));

    StatusType status;

    for(size_t totalSize = 0, transaction = 2, prevProgress = std::numeric_limits<size_t>::max(); !file->atEnd(); ++transaction) {
        const auto buf = file->read(maxTransferSize);
        check_return_bool(controlTransfer(REQUEST_OUT, DFU_DNLOAD, (uint16_t)transaction, 0, buf), "Failed to perform DFU_DNLOAD transfer");

        do {
            status = getStatus();

            check_return_bool(status.bStatus == StatusType::OK, "An error has occurred during download phase");
            QThread::msleep(status.bwPollTimeout);

        } while(status.bState != StatusType::DFU_DNLOAD_IDLE);

        totalSize += buf.size();

        const auto progress = totalSize * 100.0 / file->size();
        emit progressChanged(Operation::Download, progress);

        if(floor(progress) != prevProgress) {
            prevProgress = progress;
            debug_msg(QString("Bytes downloaded: %1 %2%").arg(totalSize).arg(prevProgress));
        }
    }

    debug_msg("Download has finished.");

    return true;
}

bool DfuseDevice::upload(QIODevice *file, uint32_t addr, size_t maxSize, uint8_t alt)
{
    check_return_bool(setInterfaceAltSetting(0, alt), "Failed to set interface alternate setting");
    check_return_bool(prepare() && abort(), "Failed to prepare the device");
    check_return_bool(setAddressPointer(addr), "Failed to set address pointer");
    abort();

    const auto extra = extraInterfaceDescriptor(0, DFU_DESCRIPTOR_TYPE, DFU_DESCRIPTOR_LENGTH);
    check_return_bool(extra.size() >= DFU_DESCRIPTOR_LENGTH, "No functional DFU descriptor");

    const auto maxTransferSize = *((uint16_t*)(extra.data() + 5));

    debug_msg(QString("Device reported transfer size: %1").arg(maxTransferSize));

    for(size_t totalSize = 0, transaction = 2, prevProgress = std::numeric_limits<size_t>::max(); totalSize < maxSize; ++transaction) {

        const auto transferSize = qMin<size_t>(maxTransferSize, maxSize - totalSize);
        const auto buf = controlTransfer(REQUEST_IN, DFU_UPLOAD, (uint16_t)transaction, 0, (uint16_t)transferSize);

        const auto bytesWritten = file->write(buf);

        check_return_bool(bytesWritten >= 0, "Failed to write to output device");

        totalSize += bytesWritten;

        const auto progress = totalSize * 100.0 / maxSize;
        emit progressChanged(Operation::Upload, progress);

        if(floor(progress) != prevProgress) {
            prevProgress = progress;
            debug_msg(QString("Bytes uploaded: %1 %2%").arg(totalSize).arg(prevProgress));
        }

        // TODO: Better error checks
        // Correctly process the end of memory condition?
        if((size_t)buf.size() < transferSize) {
            debug_msg("End of transmission.");
            break;
        }
    }

    debug_msg("Upload has finished.");

    return true;
}

bool DfuseDevice::leave()
{
    check_return_bool(setInterfaceAltSetting(0, 0), "Failed to set interface alternate setting");
    check_return_bool(prepare() && abort(), "Failed to prepare the device");

    setAddressPointer(0x080FFFFFUL);
    check_return_bool(controlTransfer(REQUEST_OUT, DFU_DNLOAD, 0, 0, QByteArray()), "Failed to perform final DFU_DNLOAD transfer");

    begin_ignore_block();
    getStatus(); // It will return an error on WB55 anyway
    end_ignore_block();

    return true;
}

bool DfuseDevice::setAddressPointer(uint32_t addr)
{
    const auto requestData = QByteArray(1, 0x21) + QByteArray::fromRawData((char*)&addr, sizeof(uint32_t));
    check_return_bool(controlTransfer(REQUEST_OUT, DFU_DNLOAD, 0, 0, requestData), "Failed to perform set address request");

    StatusType status;

    do {
        status = getStatus();
        check_return_bool(status.bStatus == StatusType::OK, "Failed to set address pointer");
        QThread::msleep(status.bwPollTimeout);

    } while(status.bState == StatusType::DFU_DNBUSY);

    return true;
}

bool DfuseDevice::erasePage(uint32_t addr)
{
    const auto buf = QByteArray(1, 0x41) + QByteArray((const char*)&addr, sizeof(uint32_t));
    check_return_bool(controlTransfer(REQUEST_OUT, DFU_DNLOAD, 0, 0, buf), "Failed to perform DFU_DNLOAD transfer");

    StatusType status;

    do {
        status = getStatus();
        check_return_bool(status.bStatus == StatusType::OK, "An error has occurred during erase phase");
        QThread::msleep(status.bwPollTimeout);

    } while(status.bState == StatusType::DFU_DNBUSY);

    return true;
}

bool DfuseDevice::abort()
{
    check_return_bool(controlTransfer(REQUEST_OUT, DFU_ABORT, 0, 0, QByteArray()), "Unable to issue abort request");

    const auto status = getStatus();
    const auto res = (status.bStatus == StatusType::OK) && (status.bState == StatusType::DFU_IDLE);

    check_return_bool(res, "Unable to reset device to idle state");
    QThread::msleep(status.bwPollTimeout);

    return res;
}

bool DfuseDevice::clearStatus()
{
    return controlTransfer(REQUEST_OUT, DFU_CLRSTATUS, 0, 0, QByteArray());
}

DfuseDevice::StatusType DfuseDevice::getStatus()
{
    StatusType ret;

    const auto STATUS_LENGTH = 6;
    const auto buf = controlTransfer(REQUEST_IN, DFU_GETSTATUS, 0, 0, STATUS_LENGTH);

    check_return_val(buf.size() == STATUS_LENGTH, "Unable to get device status", ret);

    ret.bStatus = (StatusType::Status)buf[0];
    ret.bState = (StatusType::State)buf[4];

    ret.bwPollTimeout = ((uint32_t)buf[3] << 16) |
                        ((uint32_t)buf[2] << 8)  |
                        ((uint32_t)buf[1]);

    ret.iString = buf[5];

    return ret;
}

bool DfuseDevice::prepare()
{
    const auto status = getStatus();

    if(status.bStatus != StatusType::OK) {
        debug_msg("Device is in error state, resetting...");
        check_return_bool(clearStatus(), "Failed to clear device status");

    } else if(status.bState != StatusType::DFU_IDLE) {
        debug_msg("Device is not idle, resetting...");
        check_return_bool(abort(), "Failed to abort to idle");
    }

    return true;
}