IS1200 ösningar till övning CE_O9, 2015 CE_O9. Exceptions, interrupts, traps. Förberedelser för lab 3, nios2int 9.1 Hantering av Exceptions i Nios-2 Vektoriserad hantering av Exceptions. Tre tänkbara orsaker till exception är externa avbrott (asynchronous hardware external interrupts), TRAP-instruktioner och interna (synchronous internal exceptions) händelser som av hårdvaran betraktas som fel (error) till exempel division med noll, referens av externt minne med felaktig (udda) adress, ingen kvittens vid bussreferens (bus error) och en del andra. För Nios-2: Då Nios-2 processorns hårdvara gör ett exception utförs följande av hårdvaran 1. Aktuellt programräknarvärde (returadressen) sparas i register r29 som även kallas ea - Exception Return Address. 2. Statusregister ctl0 (status) kopieras till ctl1 (estatus). Eventuellt gammalt värdefullt innehåll i estatus skrivs sönder. 3. I statusregister ctl0 nollställs PIE-biten (och U-biten). 4. Programräknaren tilldelas ett nytt värde som är definierat av hårdvaran. I vår labutrustning är det värdet 0x800020. Programexekvering fortsätter därefter som vanligt dvs på adress 0x800020. För Nios-2: Externa avbrott kan genereras via 32 insignaler (32 olika pinnar på kretsen). Varje aktiverad avbrottssignal (IRO31--IRQ00) som är enablad kan läsas av program i kontrollregistret ctl4, även kallat ipending Register. (se figur 3.-2 i Nios II Manualen) För att en aktiverad och tillåten ("enablad") avbrottssignal ska leda till en exception krävs att PIE-biten, bit med index noll i kontrollregister ctl0 (status), är ettsatt. PIE-biten kan ettställas och nollställas av program. Om man vill kan man införa makron, EI - Enable Interrupt och DI - Disable Interrupt, för att få kortare (och kanske mer lättläst) programtext..macro EI # inte 100% provat.endm.macro DI PUSH.endm PUSH r8 rdctl r8, status # kopiera status till r8 ori r8, r8, 1 # ettställ bit med index 0 wrctl status, r8 # skriv tillbaks till status POP r8 # inte 100% provat r8 rdctl r8, status # kopiera status till r8 andi r8, r8, -2 # nollställ bit med index 0 wrctl status, r8 # skriv tillbaks till status POP r8 ösningar till övning CE_O9, sida 1 (av 15) 2015-02-24
Skiss hur man i Nios-2 kan strukturera sin programvara för att efterlikna vektoriserad hantering av exceptions. Hårdvaran i processorn måste erbjuda programmet möjlighet att avgöra orsaken till att ett undantag inträffat. I Nios-2 uppträder exception på grund av avbrott om PIE-biten är ettställd och minst en av de 32 IRQ-signalerna är aktiv och motsvarande bit i kontrollregister ienable är ettställd. Det logiska värdet, dvs signalnivån, på de aktiva och tillåtna signalerna IRQ31--IRQ00 kan avläsas av program i kontrollregister ctl4 (ipending). Ett exception som uppträder då PIE är nollställd eller om PIE är ettställd samtidigt som ingen bit i ctl4 (ipending) är ettställd, är inte ett avbrott utan något annat undantag. Det kan då vara undantag på grund av en TRAP-instruktion eller på grund av något annat. Exception på grund av en TRAP-instruktion måste "upptäckas" av programvaran genom att programmet jämför innehållet i minnet på den plats som finns 4 bytes före den plats som pekas ut av innehållet i Exception Return Address (r29=ea). Om det det inte är ett avbrott eller en TRAP-instruktion måste programvaran fortsätta att "lista ut" vilken annan orsak till undantag som gäller. Om programvaran fastställer att orsaken till exception är ett avbrott (interrupt) är det praktiskt att hoppa vidare i programmet till en Interrupt Handler. Interrupt Handler läser av innehåll i kontrollregister ctl4 (ipending) och kan ta reda på vilken IRQ-signal som är aktiv och tillåten att ge avbrott. För var och en av de 32 IRQ-signalerna som används ska det finnas en programkod som behandlar avbrott med just detta index. Om det blir avbrott på grund av ej inkopplade IRQ-signaler bör detta leda till en kod som meddelar att ett fel har uppträtt (ERROR). Allmänt: TRAP-instruktion brukar användas för att erbjuda användarprogram möjlighet att göra System Call till operativsystemets programvara. Ofta finns det fler än en TRAPinstruktion t.ex. TRAP0--TRAP63. Med Nios-2 kan man komplettera TRAP-instruktionen med en parameter (i r4 förstås) och på så sätt erbjuda ett (stort) antal olika System Call. I laboration 3 kan Du använda TRAP-instruktionen för att illustrera System Call. ämpligen införs ett macro i enlighet med.macro SysCall index movia r4, \index trap.endm Då programvaran upptäcker att ett undantag har orsakats av en TRAP-instruktion hoppar exekveringen lämpligen till ett läge med den symboliska beteckningen TrapHandler. I koden vid TrapHandler undersöks lämpligen innehållet i register r4 för att leda vidare till programkod på de symboliska adresserna TRAP0 -- TRAP15 (om man har begränsat antal SysCall till 16 olika). Oanvända eller ospecifierade SysCall bör rimligen leda till lämplig felutskrift av typen "Unimplemented System Call". TRAP-instruktioner är non-maskable och exekveras alltid, oberoende av värden på PIEbiten. ösningar till övning CE_O9, sida 2 (av 15) 2015-02-24
För vår labutrustning gäller att varje I/O-enhet, vid VHD-konstruktionen, har tilldelats en unik IRQ-signal (ett unikt index). Vi måste skriva en InteruptHandler-kod för varje IRQ som vi vill använda. En god ide': Vektoriserad hantering av undantag kan efterliknas genom att man skapar en tabell som innehåller startadresser till avbrottsrutiner för IRQ31--IRQ00 och ytterligare en tabell med startadresser till de programkoder som hanterar TRAP-instruktioner med olika "index". Genom att låta "index" för IRQ respektive TRAP peka ut en plats i tabellerna kan man ta sig fram till rätt undantagskod på ett systematisk sätt. 9.2 Kopiering av "stubbe" movia r4, 0x800020 # destination address movia r5, stub # source addres ldw r6, 0(r5) # läs 1:a instruktionen stw r6, 0(r4) # skriv 1:a instruktionen ldw r6, 4(r5) # läs 2:a instruktionen stw r6, 4(r4) # skriv 2:a instruktionen ldw r6, 8(r5) # läs 3:e instruktionen stw r6, 8(r4) # skriv 3:e instruktionen 9.3 Orsak till Exception: Interrupt, TRAP eller något annat? Skriv den del av koden i ExcHandler som "listar ut" om undantaget har orsakats av en Interrupt eller av en TRAP-instruktion. Hopp ska ske till de symboliska adresserna IntHandler respektive TrapHandler. Undantag av annat slag ska leda till Error. Denna programkod motsvarar övre delen av Figure 3-3. i kapitel 3. Programming Model. Förslag:.equ TRAPCODE, 0x003B683A # op-code för TRAP ExcHandler: rdctl et, estatus # läs estatus till r24 (et) andi et, et, 1 # kolla EPIE-biten beq et, r0, NoInt # hopp om EPIE=0 rdctl et, ipending # read from ipending beq et, r0, NoInt # no pending interrupts # här placeras koden för IntHandler (se uppgift 7.4) IntHandler:... NoInt: mov et, r29 # kopiera returadress till r24 subi et, et, 4 # peka på instruktionen innan ldw et, 0(r24) # kopiera instruktionskod PUSH r8 # skydda innehåll i r8 movia r8, TRAPCODE # till assembler temporary cmpeq et,et,r8 # jämför instruktion med TRAPCODE POP r8 # återställ innehåll till r8 beq et, r0, ElseHandler # hopp om varken Int eller Trap # här placeras koden för TrapHandler (se uppgift 7.7) TrapHandler:... här placeras koden för ElseHandler ElseHandler:... # varken Interrupt eller Trap # andra undantag kan vara "unimplemented instructions" med mera... ösningar till övning CE_O9, sida 3 (av 15) 2015-02-24
9.4 Nios II. Avbrott från timerenheten timer_1. I denna uppgift ingår att skriva den del av IntHandler (enligt uppgift 7.3) som upptäcker att avbrottet kommer just från Timer och som vid den upptäckten (gör ett hopp och) fortsätter exekvering vid den symboliska adresser TimerInt där man hittar koden för att hantera avbrott från timern. # här kommer koden för IntHandler IntHandler: subi ea, ea, 4 # fixa till returadressen # följande 3 rader tas med i uppgift 7.4, avrott från timer_1 rdctl et, ipending # läs ipending till r24 andi et, et, 0x400 # IRQ-10? (decimalt 10) bne et, r0, TimerInt # det måste vara Timern # följande 3 rader ska tas med i uppgift 7.5, avbrott från Uart_0 # rdctl et, ipending # läs ipending till r24 # andi et, et, 0b10000 # IRQ-4? # bne et, r0, Uart0Int # om IRQ4/Uart-0 används # följande 3 rader ska tas med i uppgift 7.6, avbrott från KEYS4 # rdctl et, ipending # läs ipending till r24 # andi et, et, 0b100 # IRQ-2? # bne et, r0, KeysInt # om IRQ2/KEYS4 används # and so on another 29 times (IRQ3--IRQ31) if wanted or bra error # unexpected happening Här kommer huvudprogram med initiering och bakgrundsprogramlooop.data Baktext:.byte 0x0A.byte 0x0D.asciz "Bakgrundsprogram" # NU-terminated string.text.align 2 # på adress delbar med 4 main: stubcopy # makro enligt uppgift 7.2 call inittimer # samma som tidigare uppgift 5.6 # ****** plats för ytterligare initiering av PIO-kretsar # ettställ ITO-biten i Timer dvs bit med index 0 på adress 0x924 movia r8, 0x920 # basadress till Timer movia r9, 0b0111 # Run Continous och ITO stwio r9, 4(r8) # run # ****** plats för ytterligare initiering av ienable/ctl3 # ettställ bit med index 10 i ienable/ctl3 för att tillåta IRQ-10 rdctl r8, ctl3 ori r8, r8, 0x400 # ett ställ PIE-biten i ctl0 EI wrctl ienable, r8 # klart # makro EnableInt in CPU loop: movia r4, Baktext # peka på texten DI # för att skriva utan avbrott call PRTEX # skriv ut "Bakgrundsprogram" 6-4 EI # efter en hel utskrift movia r4, 2 # 200 millisekunder call waitx # enligt uppgift 3.12 br loop ösningar till övning CE_O9, sida 4 (av 15) 2015-02-24
Här kommer programkod för avbrottsrutinen TimerInt # var tusende avbrott ska utskrift ske.data.align 2 # på adress delbar med 4 counter:.word 1000 # räknare initieras till 1000.align 2 # på adress delbar med 4.text # samt.align 2 TimerInt: PUSHA # skydda r1-r15 samt r31 movia r8, timer # timer = 0x920 stwio r0, 0(r8) # kvittera TimeOut movia r8, counter ldw r9, 0(r8) # läs in räknarvärde subi r9, r9, 1 # minska med 1 stw r9, 0(r8) # skriv tillbaks bge r9, r0, noprint # 999 av 1000 ggr movia r4, Timertext # pekare till texten # OBServera att om PRTEX har rätt att förstöra innehåll i r1-r15 call PRTEX # skriv ut movia r8, counter movia r9, 1000 # nytt värde 1000 stw r9, 0(r8) # skrivs till counter noprint: POPA # pop r31 samt r15-r1 eret # retur från interrupt.data Timertext:.byte 0x0A.byte 0x0D.asciz "Time-Out" # NU-terminated string 9.5 Nios-2. Avbrott från en serieport Serieporten uart_0 kan ställas in så att den genererar avbrott då biten RxRdy ettställs. RxRdy är bit med index 7 på adress 0x868. Motsvarande gäller för TxRdy (bit med index 6 på adress 0x868). På adress 0x86C finns en maskbit vardera för RxRdy och TxRdy i motsvarande positioner, det vill säga bitar med index 7 och 6. Serieporten uart_0 på adress 0x860 är ansluten till nios2-processorn, så att serieporten kan generera avbrott med index 4. Skriv i assemblerkod en avbrottsrutin, Uart0Int, som baserat på avbrott från uart_0 på adress 0x860 ekar tecken från tangentbord till terminalfönster. Använd IN_CHAR och OUT_CHAR från uppgift 5.3. Uart0Int: PUSHA # skydda r1-r15 samt r31 call IN_CHAR # kvitterar avbrottssignalen mov r4, r2 call OUT_CHAR POPA # pop r31 samt r15-r1 eret ösningar till övning CE_O9, sida 5 (av 15) 2015-02-24
Komplettera programkoden i IntHandler, i uppgift 7.3 och 7.4 så att den vid avbrott från uart_0 "hoppar" till den symboliska adressen Uart0Int. ösning: I koden efter IntHandler infogas nedanstående rader på lämplig plats rdctl et, ipending andi et, et, 0b10000 # IRQ4? bne et, r0, Uart0Int # ty IRQ4 används för uart_0 ösning: I koden efter main: som initierar avbrottshantering ska ytterligare viktiga rader tas med enligt nedan: # ettställ endast bit med index 7 på adress 0x86C för att # tillåta avbrott från RxRdy i uart_0 (OBS ettställ inte bit med index 6) movia r8, 0x860 # basadress till uart_0 movia r9, 0x80 # Enable Int on RxRdy (INTE TxRdy!) stwio r9, 12(r8) # # ettställ bit med index 4 i ienable/ctl3 för att tillåta IRQ-4 rdctl r8, ienable ori r8, r8, 0b10000 wrctl ienable, r8 9.6 Nios-2. Avbrott från parallellport de2_pio_keys4 Det rekommenderas mycket kraftigt att rita ett flödesschema och delar kommer här: Komplettera programkoden i IntHandler, i uppgift 7.3 och 7.4 så att den vid avbrott från de2_pio_keys_4 "hoppar" till den symboliska adressen KeysInt. ösningar till övning CE_O9, sida 6 (av 15) 2015-02-24
ösning: I koden efter IntHandler infogas nedanstående rader på lämplig plats rdctl et, ipending andi et, et, 0b00100 # IRQ2? bne et, r0, Keys_Int # ty IRQ2 används för KEY:s ösning: I koden efter main som initierar avbrottshantering ska ytterligare viktiga rader tas med enligt nedan: # Enable Int from ECR in keys, ettställ bitar med index 3, 2, 1 och 0 # på adress 0x848, i MASK -registret för keys4 movia r8, 0x840 # basadress till keys movia r9, 0b1111 # Enable Int all 4 keys stwio r9, 8(r8) # in Mask reg (not in ECR) # ettställ bit med index 2 i ienable dvs ctl3 för att tillåta IRQ-02 rdctl r8, ienable ori r8, r8, 0b100 wrctl ienable, r8 # hit kommer man från IntHandler vid avbrott från de2_pio_keys_4 Keys_Int: PUSHA # spara r1-r15 samt r31 PUSH r16 # långlivad plats för basadress PUSH r17 # långlivad plats för ECR movia r16, 0x840 # basadress till de2_pio_keys_4 ldwio r17, 12(r16) # läs av ECR stwio r21, 12(r16) # nollställ ECR, kvittera # nu kommer 4 block av kod som är snarlika, ett block per knapp TestK0:andi r8, r17, 0b0001 # avbrott från key0? beq r8, r0, TestK1 # nej inte från key0 IntK0: ldwio r8, 0(r16) # läs av knappar andi r8, r8, 0b0001 # maska fram key0 bne r8, r0, Up0 # key0 är uppe dvs 1 Down0: movi r4, '0' # key0 är nere dvs 0 call OUT_CHAR # skriv ut 0 br TestK1 # Up0: movi r4, 'O' # ascii för boktav O call OUT_CHAR # skriv ut O TestK1:andi r8, r17, 0b0010 # avbrott från key1? beq r8, r0, TestK2 # nej inte från key1 IntK1: ldwio r8, 0(r16) # läs av knappar andi r8, r8, 0b0010 # maska fram key1 bne r8, r0, Up1 # key1 är uppe Down1: movi r4, '1' # key1 är nere call OUT_CHAR # skriv ut 1 br TestK2 # Up1: movi r4, 'I' # ascii för boktav I call OUT_CHAR # skriv ut I TestK2:andi r8, r17, 0b0100 # avbrott från key2? beq r8, r0, TestK3 # nej inte från key2 IntK2: ldwio r8, 0(r16) # läs av knappar andi r8, r8, 0b0100 # maska fram key1 bne r8, r0, Up2 # key2 är uppe Down2: movi r4, '2' # key2 är nere call OUT_CHAR # skriv ut 2 br TestK3 # ösningar till övning CE_O9, sida 7 (av 15) 2015-02-24
Up2: movi r4, 'S' # ascii för boktav S call OUT_CHAR # skriv ut S TestK3:andi r8, r17, 0b1000 # avbrott från key3? beq r8, r0, NoMore # nej inte från key3 IntK3: ldwio r8, 0(r16) # läs av knappar andi r8, r8, 0b0010 # maska fram key3 bne r8, r0, TestK2 # key3 är uppe Down3: movi r4, '3' # key3 är nere call OUT_CHAR # skriv ut 3 br NoMore # Up3: movi r4, 'E' # ascii för boktav E call OUT_CHAR # skriv ut E NoMore:POP R17 POP R16 POPA # återställ r31 samt r15-r1 eret # 9.7 Exception med TRAP-instruktion Skriv programkod i Nios-2-assembler som använder en TRAP-instruktion. a) Du ska skriva kod för ett huvudprogram som med instruktionen TRAP "anropar" en "trap handler" som skriver ut en textsträng (nul-terminated string) som finns lagrad i minnet. Vid TRAP-anropet ska adressen till det första tecknet i strängen skickas med som en parameter i register r5 (ja r5 ety r4 eventuellt ska användas för annat). b) Du ska skriva koden för Trap-rutinen som ska anropa subrutinen PRTEX, se uppgift 5.4, för att skriva ut textsträngen. Repetition - Exempel på användbart direktiv:.data Hello:.asciz "Hello World!" # text lagras som nul-terminated ASCII-code ösnings-skiss.text TrapHand: PUSHA # spara r1-r15 samt r31 movia r4, Hello call PRTEX # får ändra innehåll i r1-r15 pop r4 POPA # återställ r31 samt r15-r1 eret # r29 ska inte justeras vid TRAP Om man vill bredda möjligheterna kan man införa ett makro enligt nedan samt komplettera koden i TrapHandler så att den undersöker innehåll i r4 för att ta sig till olika kod för olika värden i r4. Det öppnar för möjligheten att använda den enda TRAP-instruktionen för många olika System Call..macro SysCall index movia r4, \index trap.endm ösningar till övning CE_O9, sida 8 (av 15) 2015-02-24
9.8 Avbrott i en avbrottsrutin Diskutera vilka åtgärder som krävs för att ett avbrott ska kunna tillåtas och hanteras inne i en avbrottsrutin. Skissa ett förslag på hur en avbrottsrutin för key0 skulle kunna släppa fram ett avbrott från knapp key3. ösningsförslag: Diskussion om strategi, kanske följd av programförslag För att kunna tillåta avbrott (exceptions) inne i en avbrottsrutin (exception handler) krävs vissa skyddsåtgärder, nämligen följande 1. skydda innehållet i estatus som ju kommer att skrivas om ett nytt avbrott genereras. Det är lämpligt att pusha estatus på stacken. 2. Skydda returadressen i r29 (ea Exception Return Address) t.ex. förslagsvis genom att pusha den på stacken 3. Kvittera avbrottet från key0 och välj vilka knappar som får ge avbrott och vilka index 4. gör enable interrupt i CPU:n, dvs ettställ bit 0 i status dvs ctl0 Och nu några kodsnuttar just för att släppa in key0 i avbrottsrutinen för key3 men först några nya macron (som kanske redan finns i exempel på annan plats).macro PUSHESTATUS rdctl et, estatus # kopiera estatus till r24 PUSH et # och pusha på stacken.endm.macro POPESTATUS POP et # popa från stacken till r24 WRCT estatus, et # och skriv till estatus.endm.macro STOREI PUSH r8 PUSH r9 movia movia stw POP r9 POP r8.endm addr, data r8, \addr r9, \data r9, 0(r8) Koden vid platser markerad med... 1), 2) och 3) finns med nedan key0int:... # hit kommer man då key0 trycktes ned PUSH ea # returadress till stacken PUSHESTATUS # MAKRO för just detta... # 1)kod för att tillåta key3 men ej key0 EI # Enable Interrupt i CPU:n... # 2)här släpps avbrott från keys4 fram DI # Disable Interrupt för att kunna... # 3)kod för att tillåta key3 och key0 POPESTATUS # MAKRO: återställa ESTATUS POP ea # fixa tillbaks returadress eret # innan returen ösningar till övning CE_O9, sida 9 (av 15) 2015-02-24
Och nu koden för 1), 2) och 3) med användande av tidigare införda makron. ;1) STOREI 0x848,0x1 # 0b0001, bara key0 släpps fram ;2) PUTC (7) # skriv ut en 7:a MOVIA r4, 1000 # fördröj 1000 CA DEAY # från lab 1 och 2 millisekunder PUTC (*) # skriv ut en *:a ;3) STOREI 0x848,0x9 # 0b1001, key3 och key0 släpps fram 9.9 Timeravbrott i avbrottsrutin för timer_1 ösningsförslag: Den nya kvalite n i denna uppgift är att man inte vet exakt hur lång tid avbrottsrutinen tar. Om man inte släpper in avbrott från timern under den tid subrutinen WORK exekveras, kommer man att "tappa" räkningen av de avbrott som sker under denna tid och tidmätningen blir felaktig. Om man däremot tillåter avbrott i avbrottsrutinen, under den tid då WORK exekverar, kan man för varje inträffad Time- Out som ger avbrott, minska räknaren COUNT med 1 och det kommer att ta exakt 1000 nya avbrott innan man nästa gång anropar subrutinen. En viktig förutsättning för att det inte ska spåra ur är att subrutinen WORK garanterat tar mindre tid än en sekund. 9.10 Vektoriserat avbott med Alteras biblioteksfunktioner. Avbrottsrutinen sparar de register som får ändras av den subrutin som anropas: r1 (assembler temporary, at), r2 r15, r31 (returadress, ändras av call-instruktionen). Dessutom sparas r29 (avbrottsrutinens egen returadress, ea) och estatus, som innehåller status-registrets värde från precis före avbrottet. R29 och estatus behöver visserligen bara sparas vid nästlade avbrott, det vill säga när en avbrottsrutin tillåter andra avbrott medan den körs. För att förenkla koden så sparas r29 och estatus vid varje avbrott. Nios2 har ju egenheten att exception-retur-adressen i r29 (ea) ska minskas med 4 vid hårdvaruavbrott, men inte vid mjukvaruavbrott. Exception Handler ska testa vilket som inträffat och justerar vid behov det sparade värdet hos r29 så att det stämmer. Ovanstående del av Exception Handler är skriven i assembler. När registren är sparade så kan program skrivna i C-kod användas för att göra resten av arbetet. C- delen av avbrottsrutinen undersöker vilken enhet som orsakade avbrottet, och anropar den C-funktion som hör till enheten. För att anropa rätt enhet använder C-delen av avbrottsrutinen en tabell. Tabellen har 32 platser, en plats för varje möjlig avbrottssignal i Nios2. På varje plats i tabellen lagras två värden. Det ena värdet är startadressen för den funktion som ska anropas för just detta avbrott. Det andra värdet är en pekare, som funktionen får använda på valfritt sätt. Eftersom våra avbrottshanteringsfunktioner är rätt enkla så behöver de inte pekaren, men den måste finnas med i deklarationerna, annars ger kompilatorn onödiga varningar. Om flera enheter har aktiva avbrottssignaler så ser wrappern till att det avbrott som har lägst nummer behandlas. Övriga avbrott (med högre nummer) får vänta. Detta ösningar till övning CE_O9, sida 10 (av 15) 2015-02-24
innebär en prioritering mellan avbrottssignalerna, nummer 0 har högst prioritet och nummer 31 har lägst prioritet. Förr eller senare returnerar den avbrottshanteringsfunktion som anropats från avbrottsrutinen. Den avslutande delen av wrappern återställer värdena i de sparade registren och returnerar med instruktionen eret, som ju alltid ska användas för att avsluta en Exception Handler. Om då någon enhet fortfarande begär avbrott så startas wrappern igen av processorhårdvaran. Då kommer nästa avbrott i prioritetsordning att behandlas. 9.11 Funktion/subrutin för avbrottshantering. En C-funktion för avbrottshantering ska ha följande deklaration (funktionsprototyp): void irq_handler_for_some_device( void * context ); där irq_handler_for_some_device förstås är ett valfritt namn på funktionen, context är en pekarvariabel som funktionen kan använda (om den har definierats vid initialiseringen, se nästa uppgift). Funktionen/subrutinen måste kvittera avbrottet vilket innebär att skriva/läsa rätt värde på rätt adress, så att enheten som gav avbrottet återställer sin avbrottssignal. Funktionen/subrutinen måste följa de allmänna reglerna för subrutiner. Det betyder bland annat att endast register r1 r15 får ändra innehåll. Det är tillåtet att anropa andra subrutiner, men då måste förstås innehållet i r31 sparas innan (och återställas före den egna subrutinen returnerar). Register r16 r23 kan få användas om deras innehåll sparas först och återställs innan returen. Stackpekaren får ändras tillfälligt men måste ha samma värde vid returen som vid anropet. Eftersom funktionen/subrutinen ska anropas från en avbrottsrutin gäller några ytterligare begränsningar. Funktionen/subrutinen får inte ta lång tid på sig att köra. Eftersom avbrottsrutiner kör med alla avbrott avstängda, så finns risk att viktiga avbrott missas om rutinen tar för lång tid på sig. Att ett avbrott missas betyder att det hinner komma flera avbrott från samma enhet, innan det första avbrottet har behandlats. Avbrottshårdvaran kan inte "räkna"; den lagrar bara att det skett avbrott från en viss enhet men inte hur många gånger det skett. Det är programmerarens ansvar att se till att avbrottsrutinen har så kort körtid att avbrottet hinner behandlas klart innan nästa avbrott kommer. Att funktionen/subrutinen inte får ta lång tid innebär förstås också att den inte får anropa andra funktioner som tar lång tid. Funktionen/subrutinen får inte anropa någon funktion som förutsätter att avbrott är påslagna avbrottsrutiner kör ju som nämnts med alla avbrott avstängda. Observera att många vanliga in- och utmatningsfunktioner förutsätter att avbrott är påslagna! Detta gäller bland annat putchar och printf; dessa funktioner använder normalt kortets JTAG-UART, och den i sin ösningar till övning CE_O9, sida 11 (av 15) 2015-02-24
tur använder avbrott. Om en avbrottsrutin anropar en funktion som använder JTAG-UARTen, så kommer funktionen i vissa fall att vänta på avbrott från JTAG-UARTen. Eftersom avbrott är avstängda så kommer detta aldrig att inträffa. Effekten blir att DE2-kortet slutar reagera, det "hänger sig". Funktionen/subrutinen avslutas med instruktionen ret, som vilken vanlig subrutin som helst. 9.12 Initialisering av vektoriserat avbrott. För att initialisera avbrott krävs flera moment: Den enhet som ska ge avbrott måste initialiseras hårdvarumässigt. Bibliotekrutinens tabell ska initialiseras, så att adressen till rätt avbrottsrutin finns på rätt plats i tabellen. Processorns specialregister för avbrottsinställningar, status och ienable, måste ställas in så att avbrott från enheten accepteras. Det första momentet, att initialisera enheten, görs med vanliga skrivningar till den eller de adresser som hör till enheten. De andra två momenten görs med biblioteksfunktionen alt_ic_isr_register. Anta att det finns en funktion/subrutin för avbrottshantering enligt föregående uppgift, och att enheten det gäller har avbrottsnummer (index) 17. Då anropas alt_ic_isr_register så här (i princip): #define SOME_DEVICE_IRQ_INDEX ( 17 ) int return_val_check; void * contextpointer = (void *) 0; /* Simple case: no context */ return_val_check = alt_ic_isr_register( 0, SOME_DEVICE_IRQ_INDEX, irq_handler_for_some_device, contextpointer, (void *) 0 ); if( return_val_check!= 0 ) /* If return_val_check is nonzero, alt_ic_isr_register failed. */ Pekaren contextpointer kan sättas till noll, om avbrottsrutinen inte behöver pekaren. Vi rekommenderar att pekaren sätts till noll på labbarna. Syftet med pekaren är att programmeraren ska kunna använda samma programkod till flera olika enheter, även om programkoden behöver "eget" lagringsutrymme vars innehåll finns kvar från ett avbrott till nästa. Detta är aningen avancerat och används inte på laborationerna i kursen. ösningar till övning CE_O9, sida 12 (av 15) 2015-02-24
Förutom att se till att tabellen blir rätt, så ändrar också alt_ic_isr_register i processorns register ienable så att avbrott släpps fram på det index som angetts som parameter till alt_ic_isr_register. Dessutom ettställer alt_ic_isr_register den bit i specialregistret status som gör att avbrott tillåts över huvud taget. För att hantera avbrottsrutiner som gör tillfälliga ändringar i specialregistret ienable, så har biblioteksrutinerna en egen kopia av det värde som "borde" finnas i ienable. En assembler-rutin som ska initialisera en enhet för avbrott kan alltså inte skriva direkt i ienable; i så fall skulle värdet skrivas över och försvinna nästa gång bibliotekrutinerna används. Funktionen alt_ic_isr_register måste användas för alla enheter i systemet (eller också inte användas för någon enhet alls). 9.13 Speciellt för laborationerna: funktionen intfix Alteras bibliotekskod innehåller bland annat en startrutin som alltid körs innan ditt labb-program börjar. Startrutinen körs alltså först, därefter anropas din programkod med en call-instruktion till läget main. Startrutinen initialiserar normalt alla existerande enheter för avbrott, och lägger in standardrutiner som avbrottshanterare. För att du ska kunna använda enheterna efter eget huvud på laborationerna så måste startrutinens initialisering tas bort. Funktionen intfix stänger av avbrott från alla enheter utom den med index 0 och den med index 1. Dessa två enheter är JTAG-UARTen och en timer som heter timer_0. Timern timer_0 används av biblioteksrutinerna, bland annat för att hålla igång JTAG-UARTens kommunikation med programutvecklingsdatorn (din PC). 9.14 Huvudprogram /* Main C program for example 7.11. */ /* Include header file for alt_irq_register() * (used later, in initialization code). */ #include <sys/alt_irq.h> /* Define the null pointer */ #define NU_POINTER ( (void *) 0) /* Define address for de2_pio_redled18 */ #define UART_0_BASE ( (volatile int *) 0x860 ) #define UART_0_RXDATA ( (volatile int *) 0x860 ) #define UART_0_TXDATA ( (volatile int *) 0x864 ) #define UART_0_STATUS ( (volatile int *) 0x868 ) #define UART_0_CONTRO ( (volatile int *) 0x86C ) #define UART_0_INTINDEX ( 4 ) /* Delay parameter for somedelay() */ #define DEAYPARAM (65535) /* Declare those functions that are defined in other files. */ int initfix_int( void ); /* in initfix_int.c */ void out_char_uart_0( int ); /* in puttime_uart_0.c */ ösningar till övning CE_O9, sida 13 (av 15) 2015-02-24
/* This simple subroutine stalls * execution for a short while. */ void somedelay( void ) int i = DEAYPARAM; while( i -= 1 ); } /* The n2_fatal_error function is called for failures. * The function prints "FATA ERROR" using out_char_uart_0, * and then enters an infinite loop. */ void n2_fatal_error() /* Define error message text to be printed. */ static const char n2_fatal_error_text[] = "FATA ERROR"; /* Define pointer for pointing into the error message text. */ register const char * cp = n2_fatal_error_text; } /* Disable interrupts. The return value is the previous value * from the status register; since the n2_fatal_error function * never returns and never re-enables interrupts, this value * is discarded. To inform the compiler that we deliberately * ignore the return value, we cast it to void. */ (void) alt_irq_disable_all(); /* Print the error message. */ while( *cp ) out_char_uart_0( *cp ); cp += 1; } /* Stop and wait forever. */ while( 1 ); ösningsförslag 9.16 (avbrottshanterare för uart_0) ska placeras här. ösningsförslag 9.15 (initialisering av uart_0 för avbrott) ska placeras här. int main() /* Remove unwanted interrupts. * initfix_int is supplied by KTH. * A nonzero return value indicates failure. */ if( initfix_int()!= 0 ) n2_fatal_error(); /* Initialize uart_0 for interrupts. */ uart_0_init(); /* loop forever */ while( 1 ) out_char_uart_0('.'); /* print a dot */ } } /* Programmed delay. */ somedelay(); ösningar till övning CE_O9, sida 14 (av 15) 2015-02-24
9.15 Initialisering av serieport uart_0 i C /* Initialize uart_0 for interrupts. */ void uart_0_init( void ) /* Declare a temporary for checking return values * from system-calls and library functions. */ register int ret_val_check; /* Disable interrupts for uart_0, if they were enabled. * The function alt_irq_disable returns an int, which * always has the value 0. The type cast to void tells the * compiler that we really want to ignore the value. */ (void) alt_ic_irq_disable( 0, UART_0_INTINDEX ); /* Allow interrupts from RXRDY only. */ *UART_0_CONTRO = 0x80; } /* Set up Altera's interrupt wrapper for interrupts * from uart_0. The function alt_irq_register will * enable interrupts from uart_0. * Return value is zero for success, * nonzero for failure. */ ret_val_check = alt_ic_isr_register( 0, UART_0_INTINDEX, uart_0_handler, NU_POINTER, NU_POINTER ); /* If there was an error, terminate the program. */ if( ret_val_check!= 0 ) n2_fatal_error(); 9.16 Avbrottshanterare för serieport uart_0 i C. /* Handler for uart_0 interrupts. */ void uart_0_handler( void * context, alt_u32 irqnum ) /* Read the byte. The uart is special: reading * input-data also acknowledges the interrupt. * So no extra acknowledgement code is needed. */ register int c = ( *UART_0_RXDATA ) & 0xff; } out_char_uart_0( ' ' ); /* Print a space. */ out_char_uart_0( c ); /* Echo input character. */ out_char_uart_0( ' ' ); /* Print a space. */ 9.17 Placering av subrutiner i C-program. Se lösningsförslag till uppgift 9.14 9.18...nios2int/lab3upg7main.c ösningsförslag kommer eventuellt senare... ösningar till övning CE_O9, sida 15 (av 15) 2015-02-24