reading ARM 9g20 GPIO using mmap wont work - arm

I'm trying access the GPIO pins on Atmel's Arm9 9g20. My code below keeps getting failing at
gpio = mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0xFFFFF400); // start of GPIOA
Could someone help me with my code and offer a bit of I/O example code to get me past this hump? Thanks.
// gpio.c
// compile arm-linux-gcc -o button button.c
//
#include<unistd.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
// GPIO Registers
//http://www.atmel.com/dyn/resources/prod_documents/doc6384.pdf - page 374
#define PIO_PER 0x0000 // PIO Enable Register Write-only –
#define PIO_PDR 0x0004 // PIO Disable Register Write-only –
#define PIO_PSR 0x0008 // PIO Status Register Read-only
#define PIO_OER 0x0010 // Output Enable Register Write-only –
#define PIO_ODR 0x0014 // Output Disable Register Write-only –
#define PIO_OSR 0x0018 // Output Status Register Read-only. reset 0x0000 0000
//0x001C Reserved
#define PIO_IFER 0x0020 // Glitch Input Filter Enable Register Write-only –
#define PIO_IFDR 0x0024 // Glitch Input Filter Disable Register Write-only –
#define PIO_IFSR 0x0028 // Glitch Input Filter Status Register Read-only. Reset 0x0000 0000
//0x002C Reserved
#define PIO_SODR 0x0030 // Set Output Data Register Write-only –
#define PIO_CODR 0x0034 // Clear Output Data Register Write-only
#define PIO_ODSR 0x0038 // Output Data Status Register Read-only or Read-write
#define PIO_PDSR 0x003C // Pin Data Status Register Read-only
#define PIO_IER 0x0040 // Interrupt Enable Register Write-only –
#define PIO_IDR 0x0044 // Interrupt Disable Register Write-only –
#define PIO_IMR 0x0048 // Interrupt Mask Register Read-only. Reset 0x00000000
#define PIO_ISR 0x004C // Interrupt Status Register Read-only. Reset 0x00000000
#define PIO_MDER 0x0050 // Multi-driver Enable Register Write-only –
#define PIO_MDDR 0x0054 // Multi-driver Disable Register Write-only –
#define PIO_MDSR 0x0058 // Multi-driver Status Register Read-only. Reset 0x00000000
//0x005C Reserved
#define PIO_PUDR 0x0060 // Pull-up Disable Register Write-only –
#define PIO_PUER 0x0064 // Pull-up Enable Register Write-only –
#define PIO_PUSR 0x0068 // Pad Pull-up Status Register
#define PIO_ASR 0x0070 // Peripheral A Select Register Write-only –
#define PIO_BSR 0x0074 // Peripheral B Select Register Write-only –
#define PIO_ABSR 0x0078 // AB Status Register Read-only 0x00000000
//0x007C to 0x009C Reserved
#define PIO_OWER 0x00A0 // Output Write Enable Write-only –
#define PIO_OWDR 0x00A4 // Output Write Disable Write-only –
#define PIO_OWSR 0x00A8 // Output Write Status Register Read-only 0x00000000
/*******************************************************************************************************
* MAIN
*******************************************************************************************************/
int main(int argc, char **argv) {
volatile unsigned int *PADR, *PADDR, *PBDR, *PBDDR, *PCDR, *PCDDR;
unsigned long *gpio;
int fd = open("/dev/mem", O_RDWR|O_SYNC);
if (fd < 0){
fprintf(stderr, "Unable to open port\n\r");
exit(fd);
}
gpio = mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0xFFFFF400); // start of GPIOA
if(gpio == (void *) -1) {
printf("Memory map failed.\n");
exit(0);
} else {
printf("Memory mapped at address %p.\n", gpio);
}
PADR = (unsigned int *)(gpio + 0x00); // port a
PADDR = (unsigned int *)(gpio + PIO_OER); // port a output enable
*PADDR = 0xff; // make all output
*PADR = 0xffff; // turn All of A Off
close(fd);
return 0;
}

Not sure what type of failings you are experiencing (they are not described) but operations with GPIO port and comments are not correct. First of all, register PIO_PER is IO enable register, setting bits is not making them output but enabling. On the other hand, PIO_OER is indeed for making them output and not turning all off. So, you should stick to the following sequence:
// initializing
*(unsigned int *) (gpio + PIO_PER) = 0xff; // enable
*(unsigned int *) (gpio + PIO_OER) = 0xff; // set output
// working
...
*(unsigned int *) (gpio + PIO_SODR) = 0xff; // set 1's
...
*(unsigned int *) (gpio + PIO_CODR) = 0xff; // set 0's
UPDATE
Since only whole pages can be mapped, you should take that into consideration:
#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)
#define GPIOA_BASE 0xFFFFF400
...
/* Map one page */
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIOA_BASE & & ~MAP_MASK);
...
gpio = map_base + (GPIOA_BASE & MAP_MASK);
...
Check the sources of well-known devmem tool: here

Related

Enable GPIO on AM335x in C

I have the following function initGPIO. The goal is to enable the GPIOs 0, 1 and 2 on beaglebone using am335x. How can I enable the corresponding GPIO set to the reg_GPIO that are given in the header file? I have the header file GPIO.h which contains the GPIO's numbers, register number and register structure. I have tried to set the GPIO in the function initGPIO. Does it make sense?
gpio.h
#include <stdint.h>
#include <stdbool.h>
#define GPIO0 0 /*!< GPIO 0 number */
#define GPIO1 1 /*!< GPIO 1 number */
#define GPIO2 2 /*!< GPIO 2 number */
#define GPIO3 3 /*!< GPIO 3 number */
// Base address for each gpio hardware module.
#define GPIO0_REG 0x44E07000 //<! gpio0 hardware module address.
#define GPIO1_REG 0x4804C000 //<! gpio1 hardware module address.
#define GPIO2_REG 0x481AC000 //<! gpio2 hardware module address.
#define GPIO3_REG 0x481AE000 //<! gpio3 hardware module address.
// Register Structure
typedef struct
{
volatile uint32_t irqstatus_set_0; // Offset 0x34 - Enables specific interrupt event to trigger.
volatile uint32_t irqstatus_set_1; // Offset 0x38 - Enables specific interrupt event to trigger.
volatile uint32_t irqwaken_0; // Offset 0x44 - Enables wakeup events on an interrupt.
volatile uint32_t irqwaken_1; // Offset 0x48 - Enables wakeup events on an interrupt.
volatile uint32_t ctrl; // Offset 0x130 - Controls clock gating functionality, i.e. enables module.
volatile uint32_t oe; // Offset 0x134 – Output Enable pin (clear bit to 0) output capability.
volatile uint32_t datain; // Offset 0x138 - Registers data read from the GPIO pins.
volatile uint32_t dataout; // Offset 0x13c - Sets value of GPIO output pins.
volatile uint32_t cleardataout; // Offset 0x190 - Clears to 0 bits in dataout
volatile uint32_t setdataout; // Offset 0x194 - Sets to 1 bits in dataout
} GPIO_REGS;
void initGPIO();
gpio.c
/*!
* \brief Initialize GPIOs.
*
* Enables GPIO0, GPIO1, and GPIO2 (GPIO3 not used in IDP. Also configures the output pins
* used in the IDP to control relays and address the ADC's on relays.
*
*****************************************************************************************/
void initGPIO()
{
//enable GPIOs
GPIO_REGS gpio_regs;
//might need to change ctrl
gpio_regs.datain |= (GPIO0 << GPIO0_REG );
gpio_regs.datain |= (GPIO1 << GPIO1_REG );
gpio_regs.datain |= (GPIO2 << GPIO2_REG );
}
By default Beaglebones come with Debian Linux. Unless you've decided to drop that, the kernel has a GPIO driver which assumes control of all GPIO. You should not try to access raw GPIO registers directly, but rather talk to the kernel driver. The simplest way to do that is to install libgpiod (which may be installed by default on recent Beagles) and call its API.
For bare bone platforms you may be used to do something like this to access hardware addresses:
volatile GPIO_REGS* gpio1_regs = (GPIO_REGS*)0x44E07000;
volatile GPIO_REGS* gpio2_regs = (GPIO_REGS*)0x4804C000;
Now your code isn't doing this either, but I'm going to assume that is what you meant.
Except inside an OS the memory offsets in you user-space application don't map one-on-one to hardware address offsets and this simply won't work.
If you want direct register access to things like this, you'll have to do something like Memory Mapping using /dev/mem or if your platform supports it, /dev/gpiomem (I know Raspberry Pi supports this, I'm not sure about the BBB). This will check if you are allowed to access the hardware address range you want, as well as make sure the address range is mapped correctly in your user-space.
For example:
/* open /dev/mem */
int fd = open("/dev/mem", O_RDWR|O_SYNC);
if (fd >= 0) {
/* mmap GPIO */
volatile GPIO_REGS* gpio1_regs = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x44E07000);
close(fd); /* No need to keep fd open after mmap */
/* Check for MAP_FAILED error */
if (gpio1_regs != (GPIO_REGS*)-1) {
/* Do whatever */
(The last argument to mmap is where you place your hardware address, I'm using 0x44E07000 in this example.)
For further reading, see also:
https://man7.org/linux/man-pages/man2/mmap.2.html
https://pagefault.blog/2017/03/14/access-hardware-from-userspace-with-mmap/

Programmable Interrupt Controller: PIC1 and PIC2 are not returning zeros in real mode

Hyper-V output:
Code:
/*PIC Definition*/
#define PIC1 0x20 /* IO base address for master PIC */
#define PIC2 0xA0 /* IO base address for slave PIC */
#define PIC1_COMMAND PIC1
#define PIC1_DATA (PIC1+1)
#define PIC2_COMMAND PIC2
#define PIC2_DATA (PIC2+1)
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
#define ICW1_INIT 0x10 /* Initialization - required! */
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
#define inb(x,y) asm volatile ("inb %1, %0" : "=a"(x) : "d"(y));
void PIC_remap(BYTE offset1, BYTE offset2)
{
unsigned char a1, a2, cmd;
WORD portnum = PIC1_DATA;
inb(a1, portnum); // save masks
portnum = PIC2_DATA;
inb(a2, portnum);
WORD ret1 = a1, ret2 = a2;
printf("Response from PIC1 and PIC2: %d %d", ret1, ret2);
portnum = PIC1_COMMAND;
cmd = (ICW1_INIT | ICW1_ICW4);
outb(portnum, cmd); // starts the initialization sequence (in cascade mode)
io_wait();
portnum = PIC2_COMMAND;
outb(portnum, cmd);
io_wait();
portnum = PIC1_DATA;
outb(portnum, offset1); // ICW2: Master PIC vector offset
io_wait();
portnum = PIC2_DATA;
outb(portnum, offset2); // ICW2: Slave PIC vector offset
io_wait();
portnum = PIC1_DATA;
cmd = 4;
outb(portnum, cmd); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
io_wait();
portnum = PIC2_DATA;
cmd = 2;
outb(portnum, cmd); // ICW3: tell Slave PIC its cascade identity (0000 0010)
io_wait();
portnum = PIC1_DATA;
cmd = ICW4_8086;
outb(portnum, cmd);
io_wait();
portnum = PIC2_DATA;
cmd = ICW4_8086;
outb(portnum, cmd);
io_wait();
outb(PIC1_DATA, a1); // restore saved masks.
outb(PIC2_DATA, a2);
}
I am doing some search on how the programmable interrupt controller behaves under the real mode. But I came up with some problems.
Expected behavior: PIC1 and PIC2 should return 0 0.
Reality: They return 184 (0xB8) and 15 (0xF). Can any one tell me why?
Reference: https://wiki.osdev.org/8259_PIC
Zack (The OP) provided an update with their implementation of inb and this answer has been modified to provide a more specific answer. inb has been defined as a macro this way:
#define inb(x,y) asm volatile ("inb %1, %0" : "=a"(x) : "d"(y));
It might be clearer to name macros with upper case identifiers like INB. It was unclear to me from the original code that this was a macro at all. An alternative to a macro would be to make inb a static inline function in a shared header file. You could even mark it with __attribute__((always_inline)) so that it gets inlined on lower optimization levels. The function could have been defined this way:
typedef unsigned short int WORD;
typedef unsigned char BYTE;
#define alwaysinline __attribute__((always_inline))
static inline alwaysinline BYTE inb (WORD portnum)
{
BYTE byteread;
asm volatile ("inb %1, %0"
: "=a"(byteread)
: "Nd"(portnum));
return byteread;
}
Values Returned by PIC DATA Port
When you read from the PIC DATA port you will be reading the current mask values that are used to determine which interrupts will cause the CPU to be interrupted. It effectively determines which specific interrupts are enabled and disabled on each PIC. A bit value of 0 represents enabled and 1 represents disabled.
If both values are 0 then all the interrupts on both PIC1 and PIC2 will be enabled. This may be the case in some environments, but it doesn't necessarily have to be the case. What you read may be non zero and indicative of what interrupts the BIOS enabled and disabled.
According to the screenshot PIC1 has the value 184 (binary 10111000) and PIC2 is 15 (binary 00001111). If these are in fact the values read from the PICs then it would suggest the BIOS/Firmware enabled these interrupts (and the rest disabled):
IRQ0 - Timer
IRQ1 - Keyboard
IRQ2 - Cascade interrupt
IRQ6 - Usually the Floppy controller
IRQ12 - Mouse/PS2
IRQ13 - Inter Processor interrupt (IPI) - in old days it was for the separate FPU
IRQ14 - Primary ATA Channel (HDD/CD-ROM etc)
IRQ15 - Secondary ATA Channel (HDD/CD-ROM etc)
These make sense given they are the common interrupts that you would expect would be present on a system that was supporting legacy BIOS and devices. Although your values are not zero, they do in fact look reasonable and are likely the real values read from both PICs

Zynq7000 I2C don't work, but register are set properly

I'm trying to write piece of code, to send data via I2C on my Zynq7020 device. There are 11 register asociated with I2C and I'm prety sure, that I have set this properly. I also double check registers asociated with CPU_1X clock enable a and I2C reset, but they are set properly by default. When I set all data by the code bellow, status register is 0x00000040 and interupt status register is 0x00000000 all the time. I think, there must be some enable register, but I can't find anything in datasheet. Thanks for all replies.
//I2C registers
#define XIICPS_CR 0xE0004000 //Controll register
#define XIICPS_SR 0xE0004004 //Status register
#define XIICPS_ADDR 0xE0004008 //IIC Address register
#define XIICPS_DATA 0xE000400C //IIC data register
#define XIICPS_ISR 0xE0004010 //IIC interrupt status register
#define XIICPS_TRANS_SIZE 0xE0004014 //Transfer Size Register
#define XIICPS_SLV_PAUSE 0xE0004018 //Slave Monitor Pause Register
#define XIICPS_TIME_OUT 0xE000401C //Time out register
#define XIICPS_IMR 0xE0004020 //Interrupt mask register
#define XIICPS_IER 0xE0004024 //Interrupt Enable Register
#define XIICPS_IDR 0xE0004028 //Interrupt Disable Register
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
int main()
{
init_platform();
initI2C();
while(1){
printf("Write start\n\r");
writeI2C(0x6C, 0x00); //I have device with 0x6C adress connected;
printf("Write done\n\r");
}
cleanup_platform();
return 0;
}
void initI2C(){
*((unsigned int*)XIICPS_CR) = 0x0000905E; //((15:14)CLK_A = 2, (13:8)CLK_B = 16, (6)CLR_FIFO=1, (5)MONITOR_MODE=0, (4)HOLD=1, (3)1, (2)1, (1)MASTER=1, (0)rv = 0)
*((unsigned int*)XIICPS_TIME_OUT) = 0x000000FF; //set timeout to 255
*((unsigned int*)XIICPS_IER) = 0x00000000; //no interupts (I'm pretty sure, that this line can't do anything, but it's in datasheet...)
*((unsigned int*)XIICPS_IDR) = 0x000002FF; //no interupts
return;
}
void writeI2C(unsigned addr, unsigned data){
*((unsigned int*)XIICPS_CR) = (*((unsigned int*)XIICPS_CR)|0x00000040)&0xFFFFFFFE; //CLR_FIFO and WRITE_MODE
*((unsigned int*)XIICPS_DATA) = data; //When I debug this with JTAG, I can see XIICPS_TRANS_SIZE increment by this lines. So data goes into FIFO properly, right?
*((unsigned int*)XIICPS_DATA) = data;
*((unsigned int*)XIICPS_DATA) = data;
*((unsigned int*)XIICPS_DATA) = data;
*((unsigned int*)XIICPS_ADDR) = addr;
while(*((unsigned int*)XIICPS_SR) != 0){ //this loop will never exit. Data sits in FIFO forever. No errors in status or interupt status registers, everything looks fine.
print("Wait1...\n\r");
}
while(*((unsigned int*)XIICPS_ISR)&0x00000001 == 0){
print("Wait2...\n\r");
}
return;
}

While loop stops no error or warning message in Xilinx SDK on Zynq processor

I am using a sensor and reading values from it. I have added uart_lite in the xilinx EDK and mapped it's pins to the boards GPIO. The PS (Zynq PS) clock is 50Mhz/100Mhz/200Mhz.
I am receiving data from the RX of UART at 115200 baud rate with 2 stop bits and even parity.
I am using the UART using the registers provided in the document. Four registers are there: ctrl_reg, stat_reg, RX_reg, TX_reg. Using these I can configure my uart to receive data and transmit data. Here is the documentation for uartlite.
Now I am receiving data in and trying to print the data on Teraterm.
Here is the code to set up the uart in correct mode and its registers.
#include <stdio.h>
#include "platform.h"
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#define UART_BASE XPAR_AXI_UARTLITE_0_BASEADDR
#define RX_FIFO_REG UART_BASE + 0x00
#define TX_FIFO_REG UART_BASE + 0x04
#define STAT_REG UART_BASE + 0x08
#define CTRL_REG UART_BASE + 0x0C
// Control Register
#define ENA_INTR 27
#define RST_RXFIFO 30
#define RST_TXFIFO 31
// Control Register
// xsp module
unsigned int *pAddr;
#define RS_DXEN 0
#define RS_UART_OUI 1
#define RS_HF_OUT 2
#define RS_RXEN_BAR 3
#define RS_TE_485 4
unsigned int Setting;
// xsp module
unsigned int *pData1, *pData2, *pData3, *pData4;
unsigned int RxStat = 0;
unsigned int RxData = 0;
int main()
{
init_platform();
pAddr = XPAR_UART_IP_0_BASEADDR;
// RS485 RX Mode
Setting = ( 0<<RS_TE_485 | 0<<RS_RXEN_BAR | 1<<RS_HF_OUT | 1<<RS_UART_OUI | 0<<RS_DXEN );
// RS485 TX Mode
//Setting = ( 0<<RS_TE_485 | 1<<RS_RXEN_BAR | 1<<RS_HF_OUT | 1<<RS_UART_OUI | 1<<RS_DXEN );
*pAddr = Setting;
// Control Register
pData1 = CTRL_REG;
*pData1 = (1<<ENA_INTR)|(1<RST_RXFIFO)|(1<RST_TXFIFO);
pData2 = RX_FIFO_REG;//FIFO REG;
pData3 = STAT_REG;//STAT_REG;
pData4 = TX_FIFO_REG; // TX_FIFO_REG
while (1) {
xil_printf("success");
xil_printf("0x%X\r\n",*pData2);
}
return 0;
}
So My while loop should continuously run and print values but it stops only after printing 18 bytes??
but My output is like this:
Here is my debug try:

How to write C program for 8259 controller [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I am working on interrupts. I have to understand how to use 8259 PIC. Can anyone guide me how to write a program in C for it. (I already worked on 8051) I suppose it can be used as 8051.
This is just a set of definitions common to the rest of this section. For the outb(), inb() and io_wait() functions, see this page.
#define PIC1 0x20 /* IO base address for master PIC */
#define PIC2 0xA0 /* IO base address for slave PIC */
#define PIC1_COMMAND PIC1
#define PIC1_DATA (PIC1+1)
#define PIC2_COMMAND PIC2
#define PIC2_DATA (PIC2+1)
Perhaps the most common command issued to the PIC chips is the end of interrupt (EOI) command (code 0x20). This is issued to the PIC chips at the end of an IRQ-based interrupt routine. If the IRQ came from the Master PIC, it is sufficient to issue this command only to the Master PIC; however if the IRQ came from the Slave PIC, it is necessary to issue the command to both PIC chips.
#define PIC_EOI 0x20 /* End-of-interrupt command code */
void PIC_sendEOI(unsigned char irq)
{
if(irq >= 8)
outb(PIC2_COMMAND,PIC_EOI);
outb(PIC1_COMMAND,PIC_EOI);
}
When you enter protected mode (or even before hand, if you're not using GRUB) the first command you will need to give the two PICs is the initialise command (code 0x11). This command makes the PIC wait for 3 extra "initialisation words" on the data port. These bytes give the PIC:
Its vector offset. (ICW2)
Tell it how it is wired to master/slaves. (ICW3)
Gives additional information about the environment. (ICW4)
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
#define ICW1_INIT 0x10 /* Initialization - required! */
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
void PIC_remap(int offset1, int offset2)
{
unsigned char a1, a2;
a1 = inb(PIC1_DATA); // save masks
a2 = inb(PIC2_DATA);
outb(PIC1_COMMAND, ICW1_INIT+ICW1_ICW4); // starts the initialization sequence (in cascade mode)
io_wait();
outb(PIC2_COMMAND, ICW1_INIT+ICW1_ICW4);
io_wait();
outb(PIC1_DATA, offset1); // ICW2: Master PIC vector offset
io_wait();
outb(PIC2_DATA, offset2); // ICW2: Slave PIC vector offset
io_wait();
outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
io_wait();
outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
io_wait();
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
outb(PIC1_DATA, a1); // restore saved masks.
outb(PIC2_DATA, a2);
}
For masking
void IRQ_set_mask(unsigned char IRQline) {
uint16_t port;
uint8_t value;
if(IRQline < 8) {
port = PIC1_DATA;
} else {
port = PIC2_DATA;
IRQline -= 8;
}
value = inb(port) | (1 << IRQline);
outb(port, value);
}
void IRQ_clear_mask(unsigned char IRQline) {
uint16_t port;
uint8_t value;
if(IRQline < 8) {
port = PIC1_DATA;
} else {
port = PIC2_DATA;
IRQline -= 8;
}
value = inb(port) & ~(1 << IRQline);
outb(port, value);
}

Resources