/
usb.pm
1087 lines (845 loc) · 32.8 KB
/
usb.pm
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
use strict;
use constant HID_LED_NUM_LOCK => 0;
use constant HID_LED_CAPS_LOCK => 1;
use constant HID_LED_SCROLL_LOCK => 2;
use constant SETUP_HOST_TO_DEVICE => 0 << 7;
use constant SETUP_DEVICE_TO_HOST => 1 << 7;
use constant SETUP_TYPE_STANDARD => 0 << 5;
use constant SETUP_TYPE_CLASS => 1 << 5;
use constant SETUP_TYPE_VENDOR => 2 << 5;
use constant SETUP_RECIPIENT_DEVICE => 0;
use constant SETUP_RECIPIENT_INTERFACE => 1;
use constant SETUP_RECIPIENT_ENDPOINT => 2;
use constant SETUP_RECIPIENT_OTHER => 3;
use constant FEATURE_ENDPOINT_HALT => 0;
use constant FEATURE_DEVICE_REMOTE_WAKEUP => 1;
use constant FEATURE_TEST_MODE => 2;
sub usb_init {
#enable usb pad regulator and select usb device mode
_ldi r16, MASK(UIMOD) | MASK(UVREGE);
_sts UHWCON, r16;
#enable USB module, with clock frozen
_ldi r16, MASK(USBE) | MASK(FRZCLK);
_sts USBCON, r16;
#set USB PLL prescalar value
_ldi r16, PLL_8 | MASK(PLLE);
_sts PLLCSR, r16;
block {
_lds r16, PLLCSR;
_bst r16, PLOCK;
_brtc block_begin;
};
#enable VBUS pad
_ldi r16, MASK(USBE) | MASK(OTGPADE);
_sts USBCON, r16;
#attach usb
_ldi r16, 0;
_sts UDCON, r16;
#enable end of reset interrupt
_ldi r16, MASK(EORSTE) | MASK(SOFE);
_sts UDIEN, r16;
}
sub USB_WAIT_FOR_TXINI {
my($tempreg) = shift;
block {
_lds $tempreg, UEINTX;
_sbrs $tempreg, TXINI;
_rjmp block_begin;
};
}
sub USB_SEND_QUEUED_DATA {
my($tempreg) = shift;
_lds $tempreg, UEINTX;
_cbr $tempreg, MASK(TXINI);
_sts UEINTX, $tempreg;
}
sub USB_SEND_ZLP {
my($tempreg) = shift;
USB_WAIT_FOR_TXINI $tempreg;
_cbr $tempreg, MASK(TXINI);
_sts UEINTX, $tempreg;
}
emit_global_sub "usb_gen", sub {
_push r16;
_push r17;
_lds r16, SREG;
_push r16;
_lds r16, UENUM;
_push r16;
#check for End of Reset interrupt
_lds r16, UDINT;
_sbrc r16, EORSTI;
_rjmp "eor_int";
#check for Start of Frame interrupt
_lds r16, UDINT;
_sbrc r16, SOFI;
_call "sof_int";
#clear USB device interrupts
_ldi r16, 0;
_sts UDINT, r16;
_pop r16;
_sts UENUM, r16;
_pop r16;
_sts SREG, r16;
_pop r17;
_pop r16;
_reti;
};
#this interrupt occurs when the usb controller has finished reseting, and is ready to be used
emit_sub "eor_int", sub {
SELECT_EP r16, EP_0;
#enable ep0
_ldi r16, MASK(EPEN);
_sts UECONX, r16;
#configure ep0
_ldi r16, EPTYPE_CONTROL | EPDIR_OUT;
_sts UECFG0X, r16;
_ldi r16, EPSIZE_8 | EPBANK_SINGLE | MASK(ALLOC);
_sts UECFG1X, r16;
#enable setup packet interrupt
_ldi r16, MASK(RXSTPE);
_sts UEIENX, r16;
_call "reset";
#clear USB device interrupts
_ldi r16, 0;
_sts UDINT, r16;
#reset the stack pointer
_ldi r16, 0xFF;
_sts SPL, r16;
_ldi r16, 0x20;
_sts SPH, r16;
#jump back to the main loop on return
_ldi r16, lo8(pm("main_loop"));
_push r16;
_ldi r16, hi8(pm("main_loop"));
_push r16;
_reti;
};
#this occurs when we receiver a usb start of frame packet, which occurs reliably every 1ms
#we use this to time the hid idle period
emit_sub "sof_int", sub {
block {
_lds r16, "hid_idle_ms_remaining";
_lds r17, "hid_idle_ms_remaining + 1";
_cp r16, r15_zero;
_cpc r17, r15_zero;
_breq block_end;
_subi r16, 0x01;
_sbci r17, 0x00;
_sts "hid_idle_ms_remaining", r16;
_sts "hid_idle_ms_remaining + 1", r17;
};
_ret;
};
{
my($r10_max_packet_length) = "r10";
emit_global_sub "usb_enp", sub {
_push r10;
_push r16;
_push r17;
_push r18;
_push r19;
_push r20;
_push r21;
_push r22;
_push r23;
_push r24;
_push r25;
_push zl;
_push zh;
_lds r16, SREG;
_push r16;
_lds r16, UENUM;
_push r16;
#check for endpoints with interrupts
_lds r16, UEINT;
#check EP0
block {
_sbrs r16, EPINT0;
_rjmp block_end;
SELECT_EP r16, EP_0;
#setup max_packet_length shared register
_ldi r16, 0x08;
_mov $r10_max_packet_length, r16;
_rjmp "handle_setup_packet";
};
_rjmp "usb_stall";
emit_sub "usb_enp_end", sub {
_pop r16;
_sts UENUM, r16;
_pop r16;
_sts SREG, r16;
_pop zh;
_pop zl;
_pop r25;
_pop r24;
_pop r23;
_pop r22;
_pop r21;
_pop r20;
_pop r19;
_pop r18;
_pop r17;
_pop r16;
_pop r10;
_reti;
};
emit_sub "handle_setup_packet", sub {
#check if we got an interrupt for a setup packet
_lds r24, UEINTX;
_sbrs r24, RXSTPI;
_rjmp "usb_stall";
#setup some local register aliases, for clarity
my($r16_bmRequestType) = "r16";
my($r17_bRequest) = "r17";
my($r18_wValue_lo) = "r18";
my($r19_wValue_hi) = "r19";
my($r20_wIndex_lo) = "r20";
my($r21_wIndex_hi) = "r21";
my($r22_wLength_lo) = "r22";
my($r23_wLength_hi) = "r23";
#read in the setup packet
_lds $r16_bmRequestType, UEDATX;
_lds $r17_bRequest, UEDATX;
_lds $r18_wValue_lo, UEDATX;
_lds $r19_wValue_hi, UEDATX;
_lds $r20_wIndex_lo, UEDATX;
_lds $r21_wIndex_hi, UEDATX;
_lds $r22_wLength_lo, UEDATX;
_lds $r23_wLength_hi, UEDATX;
#clear the setup interrupt bit
_cbr r24, MASK(RXSTPI) | MASK(RXOUTI) | MASK(TXINI);
_sts UEINTX, r24;
if (BOOT_LOG_ENABLED) {
_mov r24, $r16_bmRequestType;
_call "write_boot_log";
_mov r24, $r17_bRequest;
_call "write_boot_log";
}
#is it a class request?
_sbrc $r16_bmRequestType, 5;
_rjmp "handle_hid_packet";
#is it a vendor request?
_sbrc $r16_bmRequestType, 6;
_rjmp "handle_vendor_packet";
jump_table(value=>$r17_bRequest, initial_index=>0, invalid_value_label=>"setup_unknown", table=>[
"setup_get_status", #0x00
"setup_clear_feature", #0x01
"setup_unknown", #0x02
"setup_set_feature", #0x03
"setup_unknown", #0x04
"setup_set_address", #0x05
"setup_get_descriptor", #0x06
"setup_set_descriptor", #0x07
"setup_get_configuration", #0x08
"setup_set_configuration", #0x09
"setup_get_interface", #0x0a
"setup_set_interface", #0x0b
"setup_synch_frame" #0x0c
]);
emit_sub "setup_unknown", sub {
_rjmp "usb_stall";
};
emit_sub "handle_hid_packet", sub {
jump_table(value=>$r17_bRequest, initial_index=>0, invalid_value_label=>"setup_unknown", table=>[
"setup_unknown", #0x00
"hid_get_report", #0x01
"hid_get_idle", #0x02
"hid_get_protocol", #0x03
"setup_unknown", #0x04
"setup_unknown", #0x05
"setup_unknown", #0x06
"setup_unknown", #0x07
"setup_unknown", #0x08
"hid_set_report", #0x09
"hid_set_idle", #0x0a
"hid_set_protocol" #0x0b
]);
};
emit_sub "handle_vendor_packet", sub {
block {
_cpi $r17_bRequest, 0x01;
_brne block_end;
_rjmp "vendor_get_memory";
};
block {
_cpi $r17_bRequest, 0x02;
_brne block_end;
_rjmp "vendor_start_bootloader";
};
_rjmp "usb_stall";
};
emit_sub "setup_get_status", sub {
block {
block {
_cpi $r16_bmRequestType, SETUP_DEVICE_TO_HOST | SETUP_TYPE_STANDARD | SETUP_RECIPIENT_DEVICE;
_brne block_end;
_sts UEDATX, r15_zero;
_sts UEDATX, r15_zero;
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
block {
_cpi $r16_bmRequestType, SETUP_DEVICE_TO_HOST | SETUP_TYPE_STANDARD | SETUP_RECIPIENT_INTERFACE;
_brne block_end;
_cp $r20_wIndex_lo, r15_zero;
_cpc $r21_wIndex_hi, r15_zero;
_brne block_end parent;
_lds r16, "current_configuration";
_cpi r16, 0;
_breq block_end parent;
_sts UEDATX, r15_zero;
_sts UEDATX, r15_zero;
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
block {
_cpi $r16_bmRequestType, SETUP_DEVICE_TO_HOST | SETUP_TYPE_STANDARD | SETUP_RECIPIENT_ENDPOINT;
_brne block_end;
block {
#is it endpoint 1?
_cpi $r20_wIndex_lo, 0x81;
_cpc $r21_wIndex_hi, r15_zero;
_brne block_end;
_lds r16, "current_configuration";
_cpi r16, 0;
_breq block_end parent;
SELECT_EP r16, EP_1;
_lds r16, UECONX;
_bst r16, STALLRQ;
_clr r17;
_bld r17, 0;
SELECT_EP r16, EP_0;
_sts UEDATX, r17;
_sts UEDATX, r15_zero;
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
block {
#is it endpoint 0?
_cp $r20_wIndex_lo, r15_zero;
_cpc $r21_wIndex_hi, r15_zero;
_brne block_end;
_sts UEDATX, r15_zero;
_sts UEDATX, r15_zero;
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
};
};
_rjmp "usb_stall";
};
emit_sub "setup_clear_feature", sub {
block {
_cpi $r16_bmRequestType, SETUP_HOST_TO_DEVICE | SETUP_TYPE_STANDARD | SETUP_RECIPIENT_ENDPOINT;
_brne block_end;
_lds r16, "current_configuration";
_cpi r16, 0;
_breq block_end;
_cpi $r20_wIndex_lo, 0x81;
_cpc $r21_wIndex_hi, r15_zero;
_brne block_end;
_cpi $r18_wValue_lo, FEATURE_ENDPOINT_HALT;
_cpc $r19_wValue_hi, r15_zero;
_brne block_end;
SELECT_EP r16, EP_1;
_lds r16, UECONX;
_sbr r16, MASK(STALLRQC) | MASK(RSTDT);
_sts UECONX, r16;
SELECT_EP r16, EP_0;
_rjmp "usb_send_zlp";
};
_rjmp "usb_stall";
};
emit_sub "setup_set_feature", sub {
block {
_cpi $r16_bmRequestType, SETUP_HOST_TO_DEVICE | SETUP_TYPE_STANDARD | SETUP_RECIPIENT_ENDPOINT;
_brne block_end;
_lds r16, "current_configuration";
_cpi r16, 0;
_breq block_end;
_cpi $r20_wIndex_lo, 0x81;
_cpc $r21_wIndex_hi, r15_zero;
_brne block_end;
_cpi $r18_wValue_lo, FEATURE_ENDPOINT_HALT;
_cpc $r19_wValue_hi, r15_zero;
_brne block_end;
SELECT_EP r16, EP_1;
_lds r16, UECONX;
_sbr r16, MASK(STALLRQ);
_sts UECONX, r16;
SELECT_EP r16, EP_0;
_rjmp "usb_send_zlp";
};
_rjmp "usb_stall";
};
emit_sub "setup_set_address", sub {
block {
_cpi $r16_bmRequestType, 0b00000000;
_brne block_end;
_lds r16, "current_configuration";
_cpi r16, 0;
_brne block_end;
_cpi $r19_wValue_hi, 0;
_brne block_end;
_cpi $r18_wValue_lo, 0x80;
_brsh block_end;
#store the new address, but don't enable it yet
_sts UDADDR, $r18_wValue_lo;
USB_SEND_ZLP r24;
USB_WAIT_FOR_TXINI r24;
#enable the new address
_sbr $r18_wValue_lo, MASK(ADDEN);
_sts UDADDR, $r18_wValue_lo;
_rjmp "usb_enp_end";
};
_rjmp "usb_stall";
};
emit_sub "setup_get_descriptor", sub {
#if more than 255 bytes are requested, round down to 255
#(i.e. set the low byte to 255 - the high byte is otherwise ignored)
_cpse $r23_wLength_hi, r15_zero;
_ldi $r22_wLength_lo, 0xff;
#check for normal descriptor request
block {
_cpi $r16_bmRequestType, 0b10000000;
_brne block_end;
jump_table(value=>$r19_wValue_hi, initial_index=>0, invalid_value_label=>"setup_get_descriptor_end", table=>[
"setup_get_descriptor_end", #0x00
"setup_get_device_descriptor", #0x01
"setup_get_configuration_descriptor", #0x02
"setup_get_string_descriptor", #0x03
]);
};
#check for HID class descriptor request
block {
_cpi $r16_bmRequestType, 0b10000001;
_brne block_end;
_cpi $r19_wValue_hi, DESC_HID;
_breq "setup_get_hid_descriptor";
_cpi $r19_wValue_hi, DESC_HID_REPORT;
_breq "setup_get_hid_report_descriptor";
};
#otherwise, unsupported
emit "setup_get_descriptor_end:\n";
_rjmp "usb_stall";
emit_sub "setup_get_device_descriptor", sub {
my($descriptor) = get_descriptor("DEVICE_DESCRIPTOR");
_ldi zl, lo8($descriptor->{name});
_ldi zh, hi8($descriptor->{name});
#check if the requested number of bytes is less than the descriptor length
block {
_cpi $r22_wLength_lo, $descriptor->{size};
_brlo block_end;
_ldi $r22_wLength_lo, $descriptor->{size};
};
_rjmp "usb_send_program_data_short";
};
emit_sub "setup_get_configuration_descriptor", sub {
my($descriptor) = get_descriptor("CONFIGURATION_DESCRIPTORS");
_ldi zl, lo8($descriptor->{name});
_ldi zh, hi8($descriptor->{name});
#check if the requested number of bytes is less than the descriptor length
block {
_cpi $r22_wLength_lo, $descriptor->{size};
_brlo block_end;
_ldi $r22_wLength_lo, $descriptor->{size};
};
_rjmp "usb_send_program_data_short";
};
emit_sub "setup_get_string_descriptor", sub {
block {
my($descriptor) = get_descriptor("STRING_DESCRIPTOR_TABLE");
_cpi $r18_wValue_lo, $descriptor->{count};
_brsh block_end;
_ldi zl, lo8($descriptor->{name});
_ldi zh, hi8($descriptor->{name});
_lsl $r18_wValue_lo;
_add zl, $r18_wValue_lo;
_add zh, r15_zero;
#load the address of the string descriptor
_lpm r24, "z+";
_lpm r25, "z";
_mov zl, r24;
_mov zh, r25;
#load the descriptor length
_lpm r23, "z";
#check if the requested number of bytes is less than the descriptor length
block {
_cp $r22_wLength_lo, r23;
_brlo block_end;
_mov $r22_wLength_lo, r23;
};
_rjmp "usb_send_program_data_short";
};
_rjmp "usb_stall";
};
emit_sub "setup_get_hid_descriptor", sub {
my($descriptor) = get_descriptor("HID_DESCRIPTOR");
_ldi zl, lo8($descriptor->{name});
_ldi zh, hi8($descriptor->{name});
#check if the requested number of bytes is less than the descriptor length
block {
_cpi $r22_wLength_lo, $descriptor->{size};
_brlo block_end;
_ldi $r22_wLength_lo, $descriptor->{size};
};
_rjmp "usb_send_program_data_short";
};
emit_sub "setup_get_hid_report_descriptor", sub {
my($descriptor) = get_descriptor("REPORT_DESCRIPTOR");
_ldi zl, lo8($descriptor->{name});
_ldi zh, hi8($descriptor->{name});
#check if the requested number of bytes is less than the descriptor length
block {
_cpi $r22_wLength_lo, $descriptor->{size};
_brlo block_end;
_ldi $r22_wLength_lo, $descriptor->{size};
};
_rjmp "usb_send_program_data_short";
}
};
emit_sub "setup_set_descriptor", sub {
_rjmp "usb_stall";
};
emit_sub "setup_get_configuration", sub {
block {
USB_WAIT_FOR_TXINI r24;
_cpi $r22_wLength_lo, 0;
_breq block_end;
_lds r16, "current_configuration";
_sts UEDATX, r16;
};
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
emit_sub "setup_set_configuration", sub {
block {
_cpi $r16_bmRequestType, SETUP_HOST_TO_DEVICE | SETUP_TYPE_STANDARD | SETUP_RECIPIENT_DEVICE;
_brne block_end;
_lds r16, UDADDR;
_sbrs r16, ADDEN;
_rjmp block_end;
block {
_cpi $r18_wValue_lo, 0;
_brne block_end;
#if we're already configured, and are going back to a non-configured state, we need to
#reset our state and disable EP1
block {
_lds r16, "current_configuration";
_cpi r16, 0;
_brne block_end;
#nothing to do
_rjmp "usb_send_zlp";
};
_call "reset";
SELECT_EP r16, EP_1;
_sts UECONX, r15_zero;
#reset the stack pointer
_ldi r16, 0xFF;
_sts SPL, r16;
_ldi r16, 0x20;
_sts SPH, r16;
#jump back to the main loop on return
_ldi r16, lo8(pm("main_loop"));
_push r16;
_ldi r16, hi8(pm("main_loop"));
_push r16;
SELECT_EP r16, EP_0;
USB_SEND_ZLP r24;
_reti;
};
_cpi $r18_wValue_lo, 1;
_brne block_end;
SELECT_EP r16, EP_1;
_sts "current_configuration", $r18_wValue_lo;
#enable ep1
_ldi r16, MASK(EPEN) | MASK(STALLRQC) | MASK(RSTDT);
_sts UECONX, r16;
#configure ep1
_ldi r16, EPTYPE_INT | EPDIR_IN;
_sts UECFG0X, r16;
_ldi r16, EPSIZE_32 | EPBANK_SINGLE | MASK(ALLOC);
_sts UECFG1X, r16;
#initialize LEDs
_ldi r16, INVERSE_MASK(LED_NORMAL);
_out IO(PORTC), r16;
_sts "persistent_mode_leds", r16;
#re-select ep0
SELECT_EP r16, EP_0;
_rjmp "usb_send_zlp";
};
_rjmp "usb_stall";
};
emit_sub "setup_get_interface", sub {
_rjmp "usb_stall";
};
emit_sub "setup_set_interface", sub {
_rjmp "usb_stall";
};
emit_sub "setup_synch_frame", sub {
_rjmp "usb_stall";
};
emit_sub "hid_get_report", sub {
#if more than 255 bytes are requested, round down to 255
#(i.e. set the low byte to 255 - the high byte is otherwise ignored)
_cpse $r23_wLength_hi, r15_zero;
_ldi $r22_wLength_lo, 0xff;
#check if the requested number of bytes is less than the report length
block {
_cpi $r22_wLength_lo, 0x15;
_brlo block_end;
_ldi $r22_wLength_lo, 0x15;
};
#TODO: we don't currently protect current_report when writing
_ldi zl, lo8("current_report");
_ldi zh, hi8("current_report");
_rjmp "usb_send_memory_data_short";
};
emit_sub "hid_get_idle", sub {
block {
USB_WAIT_FOR_TXINI r24;
_cpi $r22_wLength_lo, 0;
_breq block_end;
_lds r16, "hid_idle_period";
_lds r17, "hid_idle_period + 1";
_lsr r17;
_ror r16;
_lsr r17;
_ror r16;
_sts UEDATX, r16;
};
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
emit_sub "hid_get_protocol", sub {
block {
USB_WAIT_FOR_TXINI r24;
_cpi $r22_wLength_lo, 0;
_breq block_end;
_lds r16, "current_protocol";
_sts UEDATX, r16;
};
USB_SEND_QUEUED_DATA r16;
_rjmp "usb_enp_end";
};
emit_sub "hid_set_report", sub {
block {
_lds r16, UEINTX;
_sbrs r16, RXOUTI;
_rjmp block_begin;
};
_in r16, IO(PORTC);
_ori r16, HOST_LED_MASK;
_lds r17, UEDATX;
#invert the values, to match the "0 is on" logic of the LEDs
_com r17;
#translate the hid led bits to the corresponding bits in PORTC
_bst r17, HID_LED_NUM_LOCK;
_bld r16, LED_NUM_LOCK;
_bst r17, HID_LED_CAPS_LOCK;
_bld r16, LED_CAPS_LOCK;
_bst r17, HID_LED_SCROLL_LOCK;
_bld r16, LED_SCROLL_LOCK;
_out IO(PORTC), r16;
#acknowledge receipt of data
_cbr r16, MASK(RXOUTI);
_sts UEINTX, r16;
#send zlp
_cbr r16, MASK(TXINI);
_sts UEINTX, r16;
_rjmp "usb_enp_end";
};
emit_sub "hid_set_idle", sub {
#the high byte of wValue contains the new idle period, in 4ms increments
_mov r23, $r19_wValue_hi;
_clr r24;
_lsl r23;
_rol r24;
_lsl r23;
_rol r24;
_sts "hid_idle_period", r23;
_sts "hid_idle_ms_remaining", r23;
_sts "hid_idle_period + 1", r24;
_sts "hid_idle_ms_remaining + 1", r24;
_rjmp "usb_send_zlp";
};
emit_sub "hid_set_protocol", sub {
#the low byte of wValue contains the protocol. 0=boot, 1=report
block {
_cpi $r18_wValue_lo, 0;
_brne block_end;
#it's the boot protocol
_sts "current_protocol", $r18_wValue_lo;
_ldi r16, 0x06;
_sts "key_array_length", r16;
_rjmp "usb_send_zlp";
};
block {
_cpi $r18_wValue_lo, 1;
_brne block_end;
#it's the report protocol
_sts "current_protocol", $r18_wValue_lo;
_ldi r16, 0x14;
_sts "key_array_length", r16;
_rjmp "usb_send_zlp";
};
_rjmp "usb_stall";
};
emit_sub "vendor_get_memory", sub {
my($r23_current_packet_len) = "r23";
my($r24_temp_reg) = "r24";
my($r22_data_len) = "r22";
block {
_cpi $r16_bmRequestType, 0b11000000;
_brne block_end;
#if more than 255 bytes are requested, round down to 255
#(i.e. set the low byte to 255 - the high byte is otherwise ignored)
_cpse $r23_wLength_hi, r15_zero;
_ldi $r22_wLength_lo, 0xff;
_mov zl, $r18_wValue_lo;
_mov zh, $r19_wValue_hi;
_rjmp "usb_send_memory_data_short";
};
_rjmp "usb_enp_end";
};
emit_sub "vendor_start_bootloader", sub {
_cli;
#disable usb
_ldi r16, MASK(DETACH);
_sts UDCON, r16;
_ldi r16, MASK(FRZCLK);
_sts USBCON, r16;
#wait for 5ms after usb reset
_ldi r24, 0x13;
_ldi r25, 0x13;
block {
_sbiw r24, 1;
_brne block_begin;
};
#disable timer 1
_sts TCCR1B, r15_zero;
_sts TIMSK1, r15_zero;
#disable timer 3
_sts TCCR3B, r15_zero;
_sts TIMSK3, r15_zero;
#clear out port configurations
_sts DDRA, r15_zero;
_sts DDRB, r15_zero;
_sts DDRC, r15_zero;
_sts DDRD, r15_zero;
_sts DDRE, r15_zero;
_sts DDRF, r15_zero;
_sts PORTA, r15_zero;
_sts PORTB, r15_zero;
_sts PORTC, r15_zero;
_sts PORTD, r15_zero;
_sts PORTE, r15_zero;
_sts PORTF, r15_zero;
#jump to the bootloader
_jmp 0xF800;
};
};
};
emit_sub "usb_stall", sub {
_lds r16, UECONX;
_sbr r16, MASK(STALLRQ);
_sts UECONX, r16;
_rjmp "usb_enp_end";
};
emit_sub "usb_send_zlp", sub {
USB_SEND_ZLP r24;
_rjmp "usb_enp_end";
};
#Sends up to 255 bytes of program memory to the currently selected usb endpoint
#zh:zl should point to the data to send
#r22 should contain the amount of data to send
#r10 should contain the maximum packet length for this endpoint
#r22, r23 and r24 will be clobbered on exit
emit_sub "usb_send_program_data_short", sub {
my($r22_data_len) = "r22";
my($r23_current_packet_len) = "r23";
my($r24_temp_reg) = "r24";
block {
#load the size of the next packet into r23
_mov $r23_current_packet_len, $r10_max_packet_length;
#if data_len <= current_packet_len
block {
_cp $r23_current_packet_len, $r22_data_len;
_brlo block_end;
_mov $r23_current_packet_len, $r22_data_len;
};
#txini must be set before we queue any data
USB_WAIT_FOR_TXINI r24;
#queue the data for the next packet
block {
_lpm $r24_temp_reg, "z+";
_sts UEDATX, $r24_temp_reg;
_dec r23;