Subversion Repositories svn.mios

Rev

Rev 693 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
628 adamjking 1
/*
2
 * @(#)HexFileUpload.java   beta8   2006/04/23
3
 *
4
 * Copyright (C) 2008    Adam King (adamjking@optusnet.com.au)
5
 *
6
 * This application is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This application is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this application; if not, write to the Free Software
18
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
 */
20
 
21
package org.midibox.mios;
22
 
23
import java.io.File;
24
 
25
import javax.sound.midi.InvalidMidiDataException;
26
import javax.sound.midi.Receiver;
27
import javax.sound.midi.SysexMessage;
28
 
29
import org.midibox.utils.Utils;
30
 
31
public class HexFileUpload extends MIOSSysexSendReceive {
32
 
33
    public final static Object FILE = new Object();
34
 
35
    public final static Object UPLOAD_MODE = new Object();
36
 
37
    public final static Object WAIT_FOR_UPLOAD = new Object();
38
 
39
    public final static Object DELAY_TIME = new Object();
693 adamjking 40
 
41
    public final static String REBOOT = "REBOOT";
628 adamjking 42
 
43
    public final static int SMART_MODE = 0;
44
 
45
    public final static int DUMB_MODE = 1;
46
 
47
    private File file;
48
 
721 adamjking 49
    //private int bankstickNo;
628 adamjking 50
 
51
    private int uploadMode;
52
 
53
    private boolean waitForUploadRequest;
54
 
55
    private int delayTime;
56
 
57
    private long fileLastModified;
58
 
59
    private boolean fileLastMIOS32_Mode;
60
 
61
    private HexFile hexFile;
62
 
63
    private int currentBlock;
64
 
65
    private int totalBlocks;
66
 
67
    private boolean expectUploadRequest = false;
68
 
69
    private boolean expectQueryReply = false;
70
 
71
    private byte expectQueryReplyNumber = 0;
72
 
73
    private boolean expectDummyError = false;
74
 
75
    private boolean gotGoodAck = false;
76
 
77
    private int uploadAttempt = 0;
78
 
79
    private int currentChecksum;
80
 
81
    private int nUploadCase;
82
 
83
    public HexFileUpload(Receiver receiver) {
84
        super(receiver);
85
 
86
        this.waitForUploadRequest = false;
87
 
88
        this.uploadMode = 0;
89
 
90
        this.delayTime = 300;
91
    }
92
 
93
    public File getFile() {
94
        return file;
95
    }
96
 
97
    public boolean fileChanged() {
98
        return file.lastModified() != fileLastModified;
99
    }
100
 
101
    public void setFile(File file) {
102
        this.file = file;
103
        fileLastModified = file.lastModified();
104
 
105
        readHexFile();
106
 
107
        setChanged();
108
        notifyObservers(FILE);
109
        clearChanged();
110
    }
111
 
112
    public void readHexFile() {
113
        hexFile = new HexFile();
721 adamjking 114
        fileLastMIOS32_Mode = isMIOS32Mode();
628 adamjking 115
        if (!hexFile.read(file.getPath(), fileLastMIOS32_Mode)) {
116
            // TODO
117
        }
118
    }
119
 
120
 
121
    public int getDelayTime() {
122
        return delayTime;
123
    }
124
 
125
    public void setDelayTime(int delayTime) {
126
        this.delayTime = delayTime;
127
 
128
        setChanged();
129
        notifyObservers(DELAY_TIME);
130
        clearChanged();
131
    }
132
 
133
    public int getUploadMode() {
134
        return uploadMode;
135
    }
136
 
137
    public void setUploadMode(int uploadMode) {
138
        this.uploadMode = uploadMode;
139
 
140
        setChanged();
141
        notifyObservers(UPLOAD_MODE);
142
        clearChanged();
143
    }
144
 
145
    public boolean isWaitForUploadRequest() {
146
        return waitForUploadRequest;
147
    }
148
 
149
    public void setWaitForUploadRequest(boolean waitForUploadRequest) {
150
        this.waitForUploadRequest = waitForUploadRequest;
151
 
152
        setChanged();
153
        notifyObservers(WAIT_FOR_UPLOAD);
154
        clearChanged();
155
    }
156
 
157
    public void createUpload() {
158
 
159
        expectQueryReply = false;
160
        cancelled = false;
161
        done = false;
162
 
163
        setChanged();
164
        notifyObservers(WORKER);
165
        clearChanged();
166
 
167
        nUploadCase = 0;
693 adamjking 168
 
628 adamjking 169
        synchronized (this) {
170
 
171
            addMessage("Starting upload of " + file.getName());
172
 
721 adamjking 173
            if (fileLastMIOS32_Mode != isMIOS32Mode()) {
628 adamjking 174
                addMessage("MIOS version has been changed - reloading file");
175
                readHexFile();
176
            }
177
 
178
            // TK: determine .hex file type, we have three cases:
179
            // (only relevant for PIC MIOS, not for MIOS32)
180
            // (1) .hex file contains code between 0x0400-0x2fff: MIOS
181
            // range, upload via bootloader
182
            // (0x000-0x3ff for downward compatibility with MIOS V1.8 and
183
            // lower)
184
            // (2) .hex file contains code between 0x3000 and 0x30ff: start
185
            // vector of application, upload via MIOS
186
            // (0) .hex file contains flash data >= 0x3100 (no special
187
            // measure), EEPROM or BankStick data
188
 
189
            // in case (1) we force a reboot and always wait for an upload
190
            // request
191
            // in case (1) we upload a stall code to 0x400 in order to
192
            // ensure, that the new code
193
            // won't be started before the upload is completely passed
194
            // in case (2) we upload a stall code to 0x3000
195
 
196
            // in case (1) and (2) we upload the first block (0x400 or
197
            // 0x3000) at the end!
198
 
199
            expectUploadRequest = false;
200
            expectDummyError = false;
201
            boolean forceRebootReq = false;
202
 
721 adamjking 203
            if (!isMIOS32Mode()) {
628 adamjking 204
                for (int i = 0; i < hexFile.getBlockCount(); ++i) {
205
                    Block block = hexFile.getBlock(i);
206
                    long lAddressMapped = block.lAddressMapped;
207
                    if (lAddressMapped >= 0x0000 && lAddressMapped <= 0x2fff) {
208
                        nUploadCase = 1;
209
                    } else if (nUploadCase != 1 && lAddressMapped >= 0x3000
210
                            && lAddressMapped <= 0x30ff) {
211
                        nUploadCase = 2;
212
                    }
213
                }
214
 
215
                if (uploadMode == 0 && nUploadCase == 1) {
216
                    expectUploadRequest = true;
217
                }
218
 
219
                if (nUploadCase == 1) {
220
                    addMessage("Hex file contains code in MIOS range, forcing reboot for upload via 1st level bootloader!\nThe reboot request will lead to an error acknowledge, please ignore!");
221
                    forceRebootReq = true;
222
                }
223
 
224
            } else {
225
                nUploadCase = 1; // MIOS32 - always request reset
226
 
227
                expectUploadRequest = true;
228
                forceRebootReq = true;
229
 
230
                addMessage("Requesting reboot...");
231
            }
232
 
233
            totalBlocks = hexFile.getBlockCount();
234
            if (totalBlocks == 0) {
235
                done = true;
236
                addMessage("Hex file is empty, or wrong MIOS version selected (e.g. check for MIOS32)");
237
                return;
238
            }
239
 
240
            // "wait until upload request" goes here.
241
            if (waitForUploadRequest) {
242
                expectUploadRequest = true;
243
            }
244
 
245
            if (expectUploadRequest) {
246
                addMessage("Waiting for upload request...");
247
            }
248
 
249
            while (expectUploadRequest && !cancelled) {
250
                if (forceRebootReq) { // TK: ensure that expectUploadRequest
251
                    // is set before message will be sent
252
                    // (race condition)
253
                    forceRebootReq = false;
254
                    forceReboot();
255
                }
256
 
257
                try {
258
                    wait();
259
 
260
                    // receivedRead routine sets expectUploadRequest to
261
                    // false once it has been received
262
                    if (!expectUploadRequest) {
263
                        // Thread.sleep(1500);
264
                    }
265
                } catch (Exception e) {
266
                    addMessage("Error: Cannot wait for upload request");
267
                }
268
            }
269
 
270
            // don't move the variable initialisation at this location!!!
271
            // it can happen, that a MIDIbox has been powered off during upload
272
            // - in this case,
273
            // the previous attempt to upload a code block can get stucked
274
            // setting currentBlock to 0 at this location ensures, that we are
275
            // starting with the first block!
276
            long timeUploadBegin = System.currentTimeMillis();
277
            currentBlock = 0;
278
            uploadAttempt = 0;
279
 
280
            while (!cancelled && !done) {
281
 
282
                // This ensures _bDone is set correctly.
283
                if (currentBlock >= totalBlocks) {
284
                    done = true;
285
                    long timeUploadEnd = System.currentTimeMillis();
286
                    float timeUpload = (float) (timeUploadEnd - timeUploadBegin) / 1000;
287
                    float transferRateKb = ((totalBlocks * 256) / timeUpload) / 1024;
693 adamjking 288
 
628 adamjking 289
                    addMessage("Upload of " + (totalBlocks * 256)
290
                            + " bytes completed after " + timeUpload + "s ("
291
                            + transferRateKb + " kb/s)");
292
 
293
                    // reboot BSL in MIOS32 mode
721 adamjking 294
                    if (isMIOS32Mode()) {
628 adamjking 295
                        forceReboot();
693 adamjking 296
                    } else {
297
 
298
                        setChanged();
299
                        notifyObservers(REBOOT);
300
                        clearChanged();    
301
                    }                  
302
 
628 adamjking 303
                    break;
304
                }
305
 
306
                Block block = hexFile.getBlock(currentBlock);
307
                long lAddress = block.lAddress;
308
                long lAddressMapped = block.lAddressMapped;
309
 
310
                byte[] axDumpData = Utils.convertTo7Bit(block.axData);
311
 
312
                long lAddressExtension = 0x00;
313
                byte[] axAddress;
314
 
721 adamjking 315
                if (!isMIOS32Mode()) {
628 adamjking 316
                    // build address/len field of MIOS SysEx message
317
 
318
                    if (lAddress >= HexFile.HEX_FILE_FLASH_ADDRESS_START
319
                            && lAddress <= HexFile.HEX_FILE_FLASH_ADDRESS_END) {
320
                        lAddressExtension = (lAddress >> 15) & 0x07;
321
                        lAddressMapped &= 0x7fff;
322
                        // System.out.println("Flash");
323
                    } else if (lAddress >= HexFile.HEX_FILE_EEPROM_ADDRESS_START
324
                            && lAddress <= HexFile.HEX_FILE_EEPROM_ADDRESS_END) {
325
                        // System.out.println("EEPROM");
326
                    } else if (lAddress >= HexFile.HEX_FILE_BANKSTICK_ADDRESS_START
327
                            && lAddress <= HexFile.HEX_FILE_BANKSTICK_ADDRESS_END) {
328
                        // System.out.println("Bankstick");
329
                        lAddressExtension = (lAddress >> 16) & 0x07;
330
                        lAddressMapped = (lAddressMapped & 0xffff) | 0x10000;
331
                    }
332
 
333
                    else {
334
                        // Invalid range, do nothing.
335
                        System.out.println("invalid");
336
                        currentBlock++;
337
                        continue;
338
                    }
339
 
340
                    axAddress = new byte[4];
341
                    axAddress[0] = (byte) ((lAddressMapped >> 10) & 0x7F);
342
                    axAddress[1] = (byte) ((lAddressMapped >> 3) & 0x7F);
343
                    axAddress[2] = (byte) ((block.axData.length >> 10) & 0x7F);
344
                    axAddress[3] = (byte) ((block.axData.length >> 3) & 0x7F);
345
 
346
                } else {
347
                    // build address/len field of MIOS32 SysEx message
348
 
349
                    axAddress = new byte[8];
350
                    axAddress[0] = (byte) ((lAddressMapped >> 25) & 0x7F);
351
                    axAddress[1] = (byte) ((lAddressMapped >> 18) & 0x7F);
352
                    axAddress[2] = (byte) ((lAddressMapped >> 11) & 0x7F);
353
                    axAddress[3] = (byte) ((lAddressMapped >> 4) & 0x7F);
354
                    axAddress[4] = (byte) ((block.axData.length >> 25) & 0x7F);
355
                    axAddress[5] = (byte) ((block.axData.length >> 18) & 0x7F);
356
                    axAddress[6] = (byte) ((block.axData.length >> 11) & 0x7F);
357
                    axAddress[7] = (byte) ((block.axData.length >> 4) & 0x7F);
358
                }
359
 
360
                currentChecksum = Utils
361
                        .calculateChecksum(axAddress, axDumpData);
362
 
363
                byte[] axSysExData = new byte[7 + axAddress.length
364
                        + axDumpData.length + 2];
365
                axSysExData[0] = (byte) 0xF0;
366
                axSysExData[1] = (byte) 0x00;
367
                axSysExData[2] = (byte) 0x00;
368
                axSysExData[3] = (byte) 0x7E;
369
                axSysExData[4] = getMIOS_SysExId();
370
                axSysExData[5] = (byte) (deviceID & 0x7F);
371
                axSysExData[6] = (byte) (0x02 | (lAddressExtension << 4)); // Write
372
 
373
                System
374
                        .arraycopy(axAddress, 0, axSysExData, 7,
375
                                axAddress.length); // insert address
376
                System.arraycopy(axDumpData, 0, axSysExData,
377
                        7 + axAddress.length, axDumpData.length); // insert
378
                // data
379
 
380
                axSysExData[axSysExData.length - 2] = (byte) currentChecksum; // Checksum
381
                // is
382
                // second-last
383
                axSysExData[axSysExData.length - 1] = (byte) 0xF7; // SysEx
384
                // status
385
                // byte is
386
                // last
387
 
388
                String s = "Sending block #"
389
                        + (currentBlock + 1)
390
                        + " "
391
                        + Utils.longToHex(block.lAddress)
392
                        + "-"
393
                        + Utils.longToHex(block.lAddress + block.axData.length
394
                                - 1);
395
                // + " (" + Utils.longToHex(lAddressExtension) + ":" +
396
                // Utils.longToHex(lAddressMapped) + ")";
397
 
398
                addMessage(s);
399
 
400
                try {
401
                    gotGoodAck = false;
402
                    SysexMessage sysExMessage = new SysexMessage();
403
                    sysExMessage.setMessage(axSysExData, axSysExData.length);
404
 
405
                    receiver.send(sysExMessage, -1);
406
                    uploadAttempt++;
407
                } catch (InvalidMidiDataException ex) {
408
                    cancelled = true;
409
                    addMessage("Error: " + ex.getMessage());
410
                    break;
411
                }
412
 
413
                try {
414
                    if (uploadMode == 0) {
415
                        wait();
416
                        // TK: ensure that block won't be incremented if we
417
                        // haven't got an acknowledge
418
                        if (gotGoodAck)
419
                            currentBlock++;
420
                    } else {
421
                        wait(delayTime);
422
                        currentBlock++;
423
                    }
424
 
425
                } catch (InterruptedException e) {
426
                    cancelled = true;
427
                    addMessage("Error: Upload task interrupted");
428
                    break;
429
                }
430
            }
431
        }
432
    }
433
 
434
    public void createQuery() {
435
 
721 adamjking 436
        if (!isMIOS32Mode()) {
628 adamjking 437
            addMessage("Query is only supported for MIOS32");
438
            return;
439
        }
440
 
441
        expectQueryReply = false;
442
        cancelled = false;
443
        done = false;
444
 
445
        setChanged();
446
        notifyObservers(WORKER);
447
        clearChanged();
448
 
449
        synchronized (this) {
450
 
451
            expectQueryReplyNumber = 0x01;
452
 
453
            while (!cancelled && !done) {
454
 
455
                // build query request
456
                byte[] queryCode = new byte[9];
457
                queryCode[0] = (byte) 0xF0;
458
                queryCode[1] = (byte) 0x00;
459
                queryCode[2] = (byte) 0x00;
460
                queryCode[3] = (byte) 0x7E;
461
                queryCode[4] = getMIOS_SysExId();
462
                queryCode[5] = (byte) (deviceID & 0x7F); // device-id
463
                queryCode[6] = (byte) 0x00; // query command
464
                queryCode[7] = (byte) expectQueryReplyNumber;
465
                queryCode[8] = (byte) 0xF7;
466
 
467
                try {
468
                    SysexMessage sysExMessage = new SysexMessage();
469
                    sysExMessage.setMessage(queryCode, queryCode.length);
470
                    receiver.send(sysExMessage, -1);
471
                } catch (InvalidMidiDataException ex) {
472
                    cancelled = true;
473
                    addMessage("Error: " + ex.getMessage());
474
                    break;
475
                }
476
 
477
                expectQueryReply = true;
478
                while (!cancelled && expectQueryReply) {
479
                    try {
480
                        wait();
481
                    } catch (Exception e) {
482
                        addMessage("Error: Cannot wait for query reply");
483
                    }
484
                }
485
 
486
                if (!cancelled) {
487
                    if (expectQueryReplyNumber < 0x09) {
488
                        ++expectQueryReplyNumber;
489
                    } else {
490
                        done = true;
491
                        addMessage("All queries done.");
492
                    }
493
                }
494
            }
495
        }
496
    }
497
 
498
    public void forceReboot() {
499
 
500
        byte[] forceRebootCode;
501
 
721 adamjking 502
        if (isMIOS32Mode()) {
628 adamjking 503
            forceRebootCode = new byte[9];
504
            forceRebootCode[0] = (byte) 0xF0;
505
            forceRebootCode[1] = (byte) 0x00;
506
            forceRebootCode[2] = (byte) 0x00;
507
            forceRebootCode[3] = (byte) 0x7E;
508
            forceRebootCode[4] = getMIOS_SysExId();
509
            forceRebootCode[5] = (byte) (deviceID & 0x7F); // device-id
510
            forceRebootCode[6] = (byte) 0x00; // query command
511
            forceRebootCode[7] = (byte) 0x7f; // enter BSL
512
            forceRebootCode[8] = (byte) 0xF7;
513
        } else {
514
            forceRebootCode = new byte[14];
515
 
516
            // dummy write operation, core will return error 01 "less bytes than
517
            // expected"
518
            expectDummyError = true;
519
 
520
            forceRebootCode[0] = (byte) 0xF0;
521
            forceRebootCode[1] = (byte) 0x00;
522
            forceRebootCode[2] = (byte) 0x00;
523
            forceRebootCode[3] = (byte) 0x7E;
524
            forceRebootCode[4] = getMIOS_SysExId();
525
            forceRebootCode[5] = (byte) (deviceID & 0x7F); // device-id
526
            forceRebootCode[6] = (byte) 0x02; // dummy write
527
            forceRebootCode[7] = (byte) 0x00; // dummy address
528
            forceRebootCode[8] = (byte) 0x00;
529
            forceRebootCode[9] = (byte) 0x00;
530
            forceRebootCode[10] = (byte) 0x00; // dummy count
531
            forceRebootCode[11] = (byte) 0x00;
532
            forceRebootCode[12] = (byte) 0x00; // dummy byte
533
            forceRebootCode[13] = (byte) 0xF7;
534
        }
535
 
536
        try {
537
            SysexMessage sysExMessage = new SysexMessage();
538
            sysExMessage.setMessage(forceRebootCode, forceRebootCode.length);
539
            receiver.send(sysExMessage, -1);
693 adamjking 540
 
541
            setChanged();
542
            notifyObservers(REBOOT);
543
            clearChanged();
544
 
628 adamjking 545
        } catch (InvalidMidiDataException ex) {
546
            cancelled = true;
547
            addMessage("Error: " + ex.getMessage());
548
            return;
549
        }
550
    }
551
 
552
    protected void retryBlock() {
553
        if (uploadAttempt > 15) { // TKNEW
554
            cancelled = true;
555
            addMessage("Aborting after 16 errors");
556
            uploadAttempt = 0; // reset counter
557
            gotGoodAck = true;
558
        }
559
    }
560
 
561
    protected void receivedErrorCode(byte[] data) {
562
        synchronized (this) {
563
            super.receivedErrorCode(data);
564
 
565
            int errorCode = data[6];
566
            if (expectDummyError) {
567
                expectDummyError = false;
568
                addMessage("This was an expected error - please ignore!");
569
            } else if (errorCode == 8) {
570
                addMessage("Skipping this code block due to invalid address range!");
571
                notify();
572
            } else if (expectQueryReply && errorCode == 13) {
573
                expectQueryReply = false; // query not supported - skip it
574
                addMessage("Query Request #" + expectQueryReplyNumber
575
                        + " not supported by core!");
576
                notify();
577
            } else if (uploadMode == 0) {
578
                retryBlock();
579
                notify();
580
            }
581
        }
582
    }
583
 
584
    protected void receivedReturnValues(byte[] data) {
585
        synchronized (this) {
586
            StringBuffer buffer = new StringBuffer();
587
 
588
            if (expectQueryReply) {
589
                int i;
590
                String str = "";
591
                for (i = 6; i < data.length - 1; ++i) {
592
                    str += (char) data[i];
593
                }
594
 
595
                switch (expectQueryReplyNumber) {
596
                case 0x01:
597
                    buffer.append("Operating System: " + str);
598
                    break;
599
                case 0x02:
600
                    buffer.append("Board: " + str);
601
                    break;
602
                case 0x03:
603
                    buffer.append("Core Family: " + str);
604
                    break;
605
                case 0x04:
606
                    buffer.append("Chip ID: 0x" + str);
607
                    break;
608
                case 0x05:
609
                    buffer.append("Serial Number: #" + str);
610
                    break;
611
                case 0x06:
612
                    buffer.append("Flash Memory Size: " + str + " bytes");
613
                    break;
614
                case 0x07:
615
                    buffer.append("RAM Size: " + str + " bytes");
616
                    break;
617
                case 0x08: // Application First Line
618
                case 0x09: // Application Second Line
619
                    buffer.append(str);
620
                    break;
621
                default:
622
                    buffer.append("Query #" + expectQueryReplyNumber + ": "
623
                            + str);
624
                }
625
                expectQueryReply = false;
626
 
627
            } else if (uploadMode == 0) {
628
 
629
                // TK: FIXME - checksum is located at data[6] ---
630
                // data[data.length - 2] can fail if more than one message
631
                // is returned (Java/M-Audio MIDI bug)
632
                // byte checkSum = (byte) data[data.length - 2];
633
                byte checkSum = (byte) data[6];
634
 
635
                buffer.append("Received Checksum: ");
636
                buffer.append(Utils.byteToHex(checkSum));
637
                if (checkSum == currentChecksum) {
638
                    buffer.append(" - OK");
639
                    uploadAttempt = 0;
640
                    gotGoodAck = true;
641
                } else {
642
                    buffer.append(" - Error");
643
                    retryBlock();
644
                }
645
            } else {
646
                buffer.append("Received acknowledgement response");
647
            }
648
            addMessage(buffer.toString());
649
            if (uploadMode == 0) {
650
                notify();
651
            }
652
        }
653
    }
654
 
655
    protected void receivedRead(byte[] data) {
656
        synchronized (this) {
657
            if (expectUploadRequest) {
658
                addMessage("Received Upload Request");
659
                expectUploadRequest = false; // must be set to false, so
660
                // that wait routine
661
                // continues
662
            } else {
663
                addMessage("Error: Received unexpected Upload Request");
664
            }
665
            currentBlock = 0; // in any case: re-starting with first block
666
            notify(); // TK: notify *after* expectUploadRequest has been
667
            // changed!
668
        }
669
    }
670
}