Advanced Interrupt Handlers in the Solaris Express 6/05 OS

本文介绍了Solaris Express 6/05操作系统中新的DDI中断框架,包括注册、注销中断的功能,对消息信号中断(MSI)的支持,以及如何管理优先级、中断屏蔽等。文章还详细说明了不同类型的中断及其处理方法,特别是高级中断的处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Advanced Interrupt Handlers in the Solaris Express 6/05 OS

 

The Solaris Express 6/05 release of the Solaris OS provides a new DDI Interrupt framework for registering interrupts and deregistering interrupts. Support for message-signalled interrupts (MSIs) is also provided. New management interfaces enable you to manipulate priorities and capabilities, to interrupt masking, and to obtain pending information. This article is a reprint of Chapter 8 in the Solaris Express 6/05 release of the Writing Device Drivers manual. Mechanisms are described here for handling interrupts, such as registering, servicing, and removing interrupts. This document covers the following subjects:

Interrupt Handler Overview

An interrupt is a hardware signal from a device to a CPU. An interrupt tells the CPU that the device needs attention and that the CPU should stop any current activity and respond to the device. If the CPU is not performing a task that has higher priority than the priority of the interrupt, then the CPU suspends the current thread. The CPU then invokes the interrupt handler for the device that sent the interrupt signal. The job of the interrupt handler is to service the device and stop the device from interrupting. When the interrupt handler returns, the CPU resumes the work it was doing before the interrupt occurred. The DDI/DKI provides interfaces for performing the following tasks:

  • Determining interrupt type and registration requirements
  • Registering interrupts
  • Servicing interrupts
  • Masking interrupts
  • Getting interrupt pending information
  • Getting and setting priority information
Device Interrupts

I/O buses implement interrupts in two common ways: vectored and polled. Both methods commonly supply a bus-interrupt priority level. Vectored devices also supply an interrupt vector. Polled devices do not supply interrupt vectors. To stay current with changing bus technologies, the Solaris OS has been enhanced to accommodate both newer types of interrupts and more traditional interrupts that have been in use for many years. Specifically, the operating system now recognizes three types of interrupts:

  • Legacy interrupts: Legacy or fixed interrupts refer to interrupts that use older bus technologies. With these technologies, interrupts are signalled by using one or more external pins that are wired "out-of-band," that is, separately from the main lines of the bus. Newer bus technologies, such as PCI Express, maintain software compatibility by emulating legacy interrupts through in-band mechanisms. These emulated interrupts are treated as legacy interrupts by the host OS.
  • Message-signalled interrupts: Instead of using pins, MSIs are in-band messages and can target addresses in the host bridge. (See PCI Local Bus in the Writing Device Drivers manual for more information on host bridges.) MSIs can send data along with the interrupt message. Each MSI is unshared so that an MSI that is assigned to a device is guaranteed to be unique within the system. A PCI function can request up to 32 MSI messages.
  • Extended message-signalled interrupts: Extended message-signalled interrupts (MSI-X) are an enhanced version of MSIs. MSI-X interrupts have the following added advantages and they:
    • Support 2048 messages rather than 32 messages.
    • Support independent message address and message data for each message.
    • Support per-message masking.
    • Enable more flexibility when software allocates fewer vectors than hardware requests. The software can reuse the same MSI-X address and data in multiple MSI-X slots.
Note: Some newer bus technologies, such as PCI Express, require MSIs but can accommodate legacy interrupts by using INTx emulation. INTx emulation is used for compatibility purposes, but is not considered to be good practice.
High-Level Interrupts

A bus prioritizes a device interrupt at a bus-interrupt level. The bus interrupt level is then mapped to a processor-interrupt level. A bus interrupt level that maps to a CPU interrupt priority above the scheduler priority level is called a high-level interrupt. High-level interrupt handlers are restricted to calling the following DDI interfaces:

  • mutex_enter(9F) and mutex_exit(9F) on a mutex that is initialized with an interrupt priority associated with the high-level interrupt
  • ddi_intr_trigger_softint(9F)
  • The following DDI get and put routines: ddi_get8(9F), ddi_put8(9F), ddi_get16(9F), ddi_put16(9F), ddi_get32(9F), ddi_put32(9F), ddi_get64(9F), and ddi_put64(9F)
A bus-interrupt level by itself does not determine whether a device interrupts at a high level. A particular bus-interrupt level can map to a high-level interrupt on one platform, but map to an ordinary interrupt on another platform. A driver is not required to support devices that have high-level interrupts. However, the driver is required to check the interrupt level. If the interrupt priority is greater than or equal to the highest system priority, the interrupt handler runs in high-level interrupt context. In this case, the driver can fail to attach, or the driver can use a two-level scheme to handle interrupts.
Legacy Interrupts

The only information that the system has about a device interrupt is the priority level of the bus interrupt and the interrupt request number. An example of the priority level for a bus interrupt is the IPL on an SBus in a SPARC processor-based machine. An example of an interrupt request number is the IRQ on an ISA bus in an x86 machine. When an interrupt handler is registered, the system adds the handler to a list of potential interrupt handlers for each IPL or IRQ. When the interrupt occurs, the system must determine which device, among all devices that are associated with a given IPL or IRQ, actually caused the interrupt. The system calls all the interrupt handlers for the designated IPL or IRQ until one handler claims the interrupt. The following buses are capable of supporting polled interrupts:

  • SBus
  • ISA
  • PCI
Standard and Extended Message-Signalled Interrupts

Both standard (MSI) and extended (MSI-X) message-signalled interrupts are implemented as in-band messages. An MSI is posted as a write with an address and value that are specified by the software.

MSI Interrupts

Conventional PCI specifications include optional support for MSIs. An MSI is an in-band message that is implemented as a posted write. The address and the data for the MSI are specified by software and are specific to the host bridge. Because the messages are in-band, the receipt of the message can be used to "push" data that is associated with the interrupt. By definition, MSI interrupts are unshared. Each MSI message that is assigned to a device is guaranteed to be a unique message in the system. PCI functions can request 1, 2, 4, 8, 16, or 32 MSI messages. Note that the system software can allocate fewer MSI messages to a function than the function requested. The host bridge can be limited in the number of unique MSI messages that are allocated for devices.

MSI-X Interrupts

MSI-X interrupts are enhanced versions of MSI interrupts. With MSI-X, a function can allocate up to 2048 messages. The number of messages allocated must be a power of two. MSI-X messages are independent of each other. With MSI-X interrupts, an unallocated interrupt vector is permitted to use the same MSI address and data pair and the same interrupt handler with its arguments as a previously set interrupt vector. In such cases, use ddi_intr_dup_handler(9F) to get the new interrupt handle.

Software Interrupts

The DDI/DKI supports software interrupts, also known as soft interrupts. Soft interrupts are initiated by software rather than by a hardware device. Handlers for these interrupts must also be added to and removed from the system. Soft interrupt handlers run in interrupt context and therefore can be used to do many of the tasks that belong to an interrupt handler. Hardware interrupt handlers must perform their tasks quickly, because the handlers may have to suspend other system activity while doing these tasks. This requirement is particularly true for high-level interrupt handlers, which operate at priority levels greater than the priority level of the system scheduler. High-level interrupt handlers mask the operations of all lower-priority interrupts, including the interrupt operations of the system clock. Consequently, the interrupt handler must avoid involvement in activities that might cause it to sleep, such as acquiring a mutex. If the handler sleeps, then the system might hang because the clock is masked and incapable of scheduling the sleeping thread. For this reason, high-level interrupt handlers normally perform a minimum amount of work at high-priority levels and delegate other tasks to software interrupts, which run below the priority level of the high-level interrupt handler. Because software interrupt handlers run below the priority level of the system scheduler, software interrupt handlers can do the work that the high-level interrupt handler was incapable of doing.

DDI Interrupt Functions

The Solaris OS provides a framework for registering and unregistering interrupts and provides support for MSIs. Interrupt management interfaces enable you to manipulate priorities, capabilities, and interrupt masking, and to obtain pending information.

Interrupt Capability Functions

Use the following functions to obtain interrupt information:

ddi_intr_get_navail(9F)
Returns the number of interrupts available for a specified hardware device and interrupt type.
ddi_intr_get_nintrs(9F)
Returns the number of interrupts that the device supports for the specified interrupt type.
ddi_intr_get_supported_types(9F)
Returns the hardware interrupt types that are supported by both the device and the host.
ddi_intr_get_cap(9F)
Returns interrupt capability flags for the specified interrupt.

Interrupt Initialization and Destruction Functions

Use the following functions to create and remove interrupts:

ddi_intr_alloc(9F)
Allocates system resources and interrupt vectors for the specified type of interrupt.
ddi_intr_free(9F)
Releases the system resources and interrupt vectors for a specified interrupt handle.
ddi_intr_set_cap(9F)
Sets the capability of the specified interrupt through the use of the DDI_INTR_FLAG_LEVEL and DDI_INTR_FLAG_EDGE flags.
ddi_intr_add_handler(9F)
Adds an interrupt handler.
ddi_intr_dup_handler(9F)
Use with MSI-X only. Reuses an MSI address and data pair for an unallocated interrupt vector.
ddi_intr_remove_handler(9F)
Removes the specified interrupt handler.
ddi_intr_enable(9F)
Enables the specified interrupt.
di_intr_disable(9F)
Disables the specified interrupt.
ddi_block_enable(9F)
Use with MSI only. Enables the specified range of interrupts.
ddi_intr_block_disable(9F)
Use with MSI only. Disables the specified range of interrupts.
ddi_intr_set_mask(9F)
Increments an internal counter in an interrupt mask.
ddi_intr_clr_mask(9F)
Decrements an internal counter in an interrupt mask if value is non-zero.
ddi_intr_get_pending(9F)
Reads the interrupt pending bit if such a bit is supported by either the host bridge or the device.

Priority Management Functions

Use the following functions to obtain and set priority information:

ddi_intr_get_pri(9F)
Returns the current software priority setting for the specified interrupt.
ddi_intr_set_pri(9F)
Sets the interrupt priority level for the specified interrupt.
ddi_intr_get_hilevel_pri(9F)
Returns the minimum priority level for a high-level interrupt.

Soft Interrupt Functions

Use the following functions to manipulate soft interrupts and soft interrupt handlers:

ddi_intr_add_softint(9F)
Adds a soft interrupt handler.
ddi_intr_trigger_softint(9F)
Triggers the specified soft interrupt.
di_intr_remove_softint(9F)
Removes the specified soft interrupt handler.
ddi_intr_get_softint_pri(9F)
Returns the soft interrupt priority for the specified interrupt.
ddi_intr_set_softint_pri(9F)
Changes the relative soft interrupt priority for the specified soft interrupt.

Interrupt Function Examples

This section provides examples for performing the following tasks:

  • Changing soft interrupt priority
  • Checking for pending interrupts
  • Setting interrupt masks
  • Clearing interrupt masks

Use the ddi_intr_set_softint_pri(9F) function to change the soft interrupt priority to 9.

if (ddi_intr_set_softint_pri(mydev->mydev_softint_hdl, 9) !=
    DDI_SUCCESS) {
    cmn_err (CE_WARN, "ddi_intr_set_softint_pri failed");
}
 

Use the ddi_intr_get_pending(9F) function to check whether an interrupt is pending.

if (ddi_intr_get_pending(mydevp->htable[0], &pending) != DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_get_pending() failed");
} else if (pending)
    cmn_err(CE_NOTE, "ddi_intr_get_pending(): Interrupt pending");
 

Use the ddi_intr_set_mask(9F) function to set interrupt masking to prevent the device from receiving interrupts.

if ((ddi_intr_set_mask(mydevp->htable[0]) != DDI_SUCCESS))
    cmn_err(CE_WARN, "ddi_intr_set_mask() failed");
 

Use the ddi_intr_clr_mask(9F) function to clear the interrupt masking. If clearing the interrupt masking is successful, the device will start generating interrupts.

if (ddi_intr_clr_mask(mydevp->htable[0]) != DDI_SUCCESS)
    cmn_err(CE_WARN, "ddi_intr_clr_mask() failed");
 
Registering Interrupts

Before a device driver can receive and service interrupts, the driver must call ddi_add_intr(9F) to register an interrupt handler with the system. Registering interrupt handlers provides the system with a way to associate an interrupt handler with an interrupt specification. The interrupt handler is called when the device might have been responsible for the interrupt. The handler has the responsibility of determining whether it should handle the interrupt and, if so, of claiming that interrupt.

Registering Legacy Interrupts

To register a driver's interrupt handler, the driver typically performs the following steps in its attach(9E) entry point:

  1. Use ddi__intr_get_supported_types(9F) to determine which types of interrupts are supported.
  2. Use ddi__intr_get_nintrs(9F) to determine the number of supported interrupt types.
  3. Use kmem_zalloc(9F) to allocate memory for DDI interrupt handles.
  4. For each interrupt type that you allocate, take the following steps:
    1. Use ddi__intr_get_pri(9F) to get the priority for the interrupt.
    2. If you need to set a new priority for the interrupt, use ddi__intr_set_pri(9F).
    3. Use mutex_init(9F) to initialize the lock.
    4. Use ddi__intr_add_handler(9F) to register the handler for the interrupt.
    5. Use ddi__intr_enable(9F) to enable the interrupt.
  5. Take the following steps to free each interrupt:
    1. Disable each interrupt using ddi__intr_disable(9F).
    2. Remove the interrupt handler using ddi__intr_remove_handler(9F).
    3. Remove the lock using mutex_destroy(9F).
    4. Free the interrupt using ddi__intr_free(9F) and kmem_free(9F) to free memory that was allocated for DDI interrupt handles.

The following example shows how to install an interrupt handler for a device called mydev. This example assumes that mydev supports one interrupt only.

/* Determine which types of interrupts supported */
ret = ddi_intr_get_supported_types(mydevp->mydev_dip, &type);

if ((ret != DDI_SUCCESS) || (!(type & DDI_INTR_TYPE_FIXED))) {
    cmn_err(CE_WARN, "Fixed type interrupt is not supported");
    return (DDI_FAILURE);
}

/* Determine number of supported interrupts */
ret = ddi_intr_get_nintrs(mydevp->mydev_dip, DDI_INTR_TYPE_FIXED, 
    &count);

/*
 * Fixed interrupts can only have one interrupt. Check to make
 * sure that number of supported interrupts and number of
 * available interrupts are both equal to 1.
 */
if ((ret != DDI_SUCCESS) || (count != 1)) {
    cmn_err(CE_WARN, "No fixed interrupts");
    return (DDI_FAILURE);
}

/* Allocate memory for DDI interrupt handles */
mydevp->mydev_htable = kmem_zalloc(sizeof (ddi_intr_handle_t), 
    KM_SLEEP);
ret = ddi_intr_alloc(mydevp->mydev_dip, mydevp->mydev_htable,
    DDI_INTR_TYPE_FIXED, 0, count, &actual, 0);

if ((ret != DDI_SUCCESS) || (actual != 1)) {
    cmn_err(CE_WARN, "ddi_intr_alloc() failed 0x%x", ret);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

/* Sanity check that count and available are the same. */
ASSERT(count == actual);

/* Get the priority of the interrupt */
if (ddi_intr_get_pri(mydevp->mydev_htable[0], &mydevp->mydev_intr_pri)) {
    cmn_err(CE_WARN, "ddi_intr_alloc() failed 0x%x", ret);

    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

cmn_err(CE_NOTE, "Supported Interrupt pri = 0x%x", mydevp->mydev_intr_pri);

/* Test for high level mutex */
if (mydevp->mydev_intr_pri >= ddi_intr_get_hilevel_pri()) {
    cmn_err(CE_WARN, "Hi level interrupt not supported");

    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

/* Initialize the mutex */
mutex_init(&mydevp->mydev_int_mutex, NULL, MUTEX_DRIVER,
    DDI_INTR_PRI(mydevp->mydev_intr_pri));

/* Register the interrupt handler */
if (ddi_intr_add_handler(mydevp->mydev_htable[0], mydev_intr, 
   (caddr_t)mydevp, NULL) !=DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_add_handler() failed");

    mutex_destroy(&mydevp->mydev_int_mutex);
    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

/* Enable the interrupt */
if (ddi_intr_enable(mydevp->mydev_htable[0]) != DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_enable() failed");

    (void) ddi_intr_remove_handler(mydevp->mydev_htable[0]);
    mutex_destroy(&mydevp->mydev_int_mutex);
    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
 

The following example shows how legacy interrupts are removed.

/* disable interrupt */
(void) ddi_intr_disable(mydevp->mydev_htable[0]);

/* Remove interrupt handler */
(void) ddi_intr_remove_handler(mydevp->mydev_htable[0]);

/* free interrupt handle */
(void) ddi_intr_free(mydevp->mydev_htable[0]);

/* free memory */
kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));
 
Registering MSI Interrupts

To register a driver's interrupt handler, the driver typically performs the following steps in its attach(9E) entry point:

  1. Use to determine which types of interrupts are supported.
  2. Use ddi__intr_get_nintrs(9F) to determine the number of supported MSI interrupt types.
  3. Use ddi__intr_alloc(9F) to allocate memory for the MSI interrupts.
  4. For each interrupt type that you allocate, take the following steps:
    1. Use ddi__intr_get_pri(9F) to get the priority for the interrupt.
    2. If you need to set a new priority for the interrupt, use ddi__intr_set_pri(9F).
    3. Use mutex_init(9F) to initialize the lock.
    4. Use ddi__intr_add_handler(9F) to register the handler for the interrupt.
  5. Use one of the following functions to enable all the interrupts:
    1. Use ddi__intr_block_enable(9F) to enable all the interrupts in a block.
    2. Use ddi__intr_enable(9F) in a loop to enable each interrupt individually.

The following example illustrates how to register an MSI interrupt for a device called mydev.

/* Get supported interrupt types */
if (ddi_intr_get_supported_types(devinfo, &intr_types) != DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_get_supported_types failed");
    goto attach_fail;
}

if (intr_types & DDI_INTR_TYPE_MSI) 
    mydev_add_msi_intrs(mydevp);

/* Check count, available and actual interrupts */
static int
mydev_add_msi_intrs(mydev_t *mydevp)
{
    dev_info_t    *devinfo = mydevp->devinfo;
    int        count, avail, actual;
    int        x, y, rc, inum = 0;

    /* Get number of interrupts */
    rc = ddi_intr_get_nintrs(devinfo, DDI_INTR_TYPE_MSI, &count);
    if ((rc != DDI_SUCCESS) || (count == 0)) {
        cmn_err(CE_WARN, "ddi_intr_get_nintrs() failure, rc: %d, "
            "count: %d", rc, count);

        return (DDI_FAILURE);
    }

    /* Get number of available interrupts */
    rc = ddi_intr_get_navail(devinfo, DDI_INTR_TYPE_MSI, &avail);
    if ((rc != DDI_SUCCESS) || (avail == 0)) {
        cmn_err(CE_WARN, "ddi_intr_get_navail() failure, "
            "rc: %d, avail: %d/n", rc, avail);
        return (DDI_FAILURE);
    }
    if (avail < count) {
        cmn_err(CE_NOTE, "nitrs() returned %d, navail returned %d",
            count, avail);
    }

    /* Allocate memory for MSI interrupts */
    mydevp->intr_size = count * sizeof (ddi_intr_handle_t);
    mydevp->htable = kmem_alloc(mydevp->intr_size, KM_SLEEP);

    rc = ddi_intr_alloc(devinfo, mydevp->htable, DDI_INTR_TYPE_MSI, inum,
        count, &actual, DDI_INTR_ALLOC_NORMAL);

    if ((rc != DDI_SUCCESS) || (actual == 0)) {
        cmn_err(CE_WARN, "ddi_intr_alloc() failed: %d", rc);

        kmem_free(mydevp->htable, mydevp->intr_size);
        return (DDI_FAILURE);
    }

    if (actual < count) {
        cmn_err(CE_NOTE, "Requested: %d, Received: %d", count, actual);
    }

    mydevp->intr_cnt = actual;

    /*
     * Get priority for first msi, assume remaining are all the same
     */
    if (ddi_intr_get_pri(mydevp->htable[0], &mydev->intr_pri) !=
        DDI_SUCCESS) {
        cmn_err(CE_WARN, "ddi_intr_get_pri() failed");

        /* Free already allocated intr */
        for (y = 0; y < actual; y++) {
            (void) ddi_intr_free(mydevp->htable[y]);
        }

        kmem_free(mydevp->htable, mydevp->intr_size);
        return (DDI_FAILURE);
    }

    /* Call ddi_intr_add_handler() */
    for (x = 0; x < actual; x++) {
        if (ddi_intr_add_handler(mydevp->htable[x], mydev_intr,
           (caddr_t)mydevp, NULL) != DDI_SUCCESS) {
            cmn_err(CE_WARN, "ddi_intr_add_handler() failed");

            /* Free already allocated intr */
            for (y = 0; y < actual; y++) {
                (void) ddi_intr_free(mydevp->htable[y]);
            }

            kmem_free(mydevp->htable, mydevp->intr_size);
            return (DDI_FAILURE);
        }
    }

    (void) ddi_intr_get_cap(mydevp->htable[0], &mydevp->intr_cap);
    if (mydev->m_intr_cap & DDI_INTR_FLAG_BLOCK) {
        /* Call ddi_intr_block_enable() for MSI */
        (void) ddi_intr_block_enable(mydev->m_htable, mydev->m_intr_cnt);
    } else {
        /* Call ddi_intr_enable() for MSI non block enable */
        for (x = 0; x < mydev->m_intr_cnt; x++) {
            (void) ddi_intr_enable(mydev->m_htable[x]);
        }
    }

    return (DDI_SUCCESS);
}
 

The following example shows how to remove MSI interrupts.

static void
mydev_rem_intrs(mydev_t *mydev)
{
    int        x;

    /* Disable all interrupts */
    if (mydev->m_intr_cap & DDI_INTR_FLAG_BLOCK) {
        /* Call ddi_intr_block_disable() */
        (void) ddi_intr_block_disable(mydev->m_htable, mydev->m_intr_cnt);
    } else {
        for (x = 0; x < mydev->m_intr_cnt; x++) {
            (void) ddi_intr_disable(mydev->m_htable[x]);
        }
    }

    /* Call ddi_intr_remove_handler() */
    for (x = 0; x < mydev->m_intr_cnt; x++) {
        (void) ddi_intr_remove_handler(mydev->m_htable[x]);
        (void) ddi_intr_free(mydev->m_htable[x]);
    }

    kmem_free(mydev->m_htable, mydev->m_intr_size);
}    
 
Interrupt Handler Functionality

The driver framework and the device each place demands on the interrupt handler. All interrupt handlers are required to do the following tasks:

  • Determine whether the device is interrupting, and possibly reject the interrupt. The interrupt handler first examines the device to determine whether this device issued the interrupt. If this device did not issue the interrupt, the handler must return DDI_INTR_UNCLAIMED. This step enables the implementation of device polling. Any device at the given interrupt priority level might have issued the interrupt. Device polling tells the system whether this device issued the interrupt.
  • Inform the device that the device is being serviced. Informing a device about servicing is a device-specific operation that is required for the majority of devices. For example, SBus devices are required to interrupt until the driver tells the SBus devices to stop. This approach guarantees that all SBus devices that interrupt at the same priority level are serviced.
  • Perform any I/O request-related processing. Devices interrupt for different reasons, such as transfer done or transfer error. This step can involve using data access functions to read the device's data buffer, examine the device's error register, and set the status field in a data structure accordingly. Interrupt dispatching and processing are relatively time consuming.
  • Do any additional processing that could prevent another interrupt. For example, read the next item of data from the device.
  • Return DDI_INTR_CLAIMED.
  • MSI interrupts must always be claimed. Claiming an interrupt is optional for MSI-X interrupts. In either case, the ownership of the interrupt need not be checked because MSI and MSI-X interrupts are not shared with other devices.
  • Drivers that support hotplugging and multiple MSI or MSI-X interrupts should retain a separate interrupt for hotplug events, and register a separate ISR (interrupt service routine) for that interrupt.

The following example shows an interrupt routine for a device called mydev.

static uint_t
mydev_intr(caddr_t arg1, caddr_t arg2)
{
    struct mydevstate *xsp = (struct mydevstate *)arg1;
    uint8_t     status; 
    volatile  uint8_t  temp;

    /*
     * Claim or reject the interrupt.This example assumes
     * that the device's CSR includes this information.
     */
    mutex_enter(&xsp->high_mu);
    /* use data access routines to read status */
    status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    if (!(status & INTERRUPTING)) {
        mutex_exit(&xsp->high_mu);
        return (DDI_INTR_UNCLAIMED); /* dev not interrupting */
    }
    /*
     * Inform the device that it is being serviced, and re-enable
     * interrupts. The example assumes that writing to the
     * CSR accomplishes this. The driver must ensure that this data
     * access operation makes it to the device before the interrupt
     * service routine returns. For example, using the data access
     * functions to read the CSR, if it does not result in unwanted
     * effects, can ensure this.
     */
    ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
        CLEAR_INTERRUPT | ENABLE_INTERRUPTS);
    /* flush store buffers */
    temp = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    
    mutex_exit(&xsp->mu);
    return (DDI_INTR_CLAIMED);
}
 

Most of the steps performed by the interrupt routine depend on the specifics of the device itself. Consult the hardware manual for the device to determine the cause of the interrupt, detect error conditions, and access the device data registers.

Handling High-Level Interrupts

High-level interrupts are those interrupts that interrupt at the level of the scheduler and above. This level does not allow the scheduler to run. Therefore, high-level interrupt handlers cannot be preempted by the scheduler. High-level interrupts cannot block because of the scheduler. High-level interrupts can only use mutual exclusion locks for locking.

The driver must determine whether the device is using high-level interrupts. Do this test in the driver's attach(9E) entry point when you register interrupts. See High-Level Interrupt Handling Example.

  • If the interrupt priority returned from ddi__intr_get_pri(9F) is greater than or equal to the priority returned from ddi__intr_get_hilevel_pri(9F), the driver can fail to attach, or the driver can implement a high-level interrupt handler. The high-level interrupt handler uses a lower-priority software interrupt to handle the device. To allow more concurrency, use a separate mutex to protect data from the high-level handler.
  • If the interrupt priority returned from ddi_intr_get_pri(9F) is less than the priority returned from ddi_intr_get_hilevel_pri(9F), the attach(9E) entry point falls through to regular interrupt registration. In this case, a soft interrupt is not necessary.
High-Level Mutexes

A mutex initialized with an interrupt priority that represents a high-level interrupt is known as a high-level mutex. While holding a high-level mutex, the driver is subject to the same restrictions as a high-level interrupt handler.

Example of High-Level Interrupt Handling

In the following example, the high-level mutex (xsp->high_mu) is used only to protect data shared between the high-level interrupt handler and the soft interrupt handler. The protected data includes a queue used by both the high-level interrupt handler and the low-level handler, and a flag that indicates that the low-level handler is running. A separate low-level mutex (xsp->low_mu) protects the rest of the driver from the soft interrupt handler.

static int
mydevattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    struct mydevstate *xsp;
    /* ... */

    ret = ddi_intr_get_supported_types(dip, &type);
    if ((ret != DDI_SUCCESS) || (!(type & DDI_INTR_TYPE_FIXED))) {
        cmn_err(CE_WARN, "ddi_intr_get_supported_types() failed");
        return (DDI_FAILURE);
    }

    ret = ddi_intr_get_nintrs(dip, DDI_INTR_TYPE_FIXED, &count);

    /*
     * Fixed interrupts can only have one interrupt. Check to make
     * sure that number of supported interrupts and number of
     * available interrupts are both equal to 1.
     */
    if ((ret != DDI_SUCCESS) || (count != 1)) {
    cmn_err(CE_WARN, "No fixed interrupts found");
            return (DDI_FAILURE);
    }

    xsp->xs_htable = kmem_zalloc(count * sizeof (ddi_intr_handle_t),
        KM_SLEEP);

    ret = ddi_intr_alloc(dip, xsp->xs_htable, DDI_INTR_TYPE_FIXED, 0,
        count, &actual, 0);

    if ((ret != DDI_SUCCESS) || (actual != 1)) {
    cmn_err(CE_WARN, "ddi_intr_alloc failed 0x%x", ret");
        kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }

    ret = ddi_intr_get_pri(xsp->xs_htable[0], &intr_pri);
    if (ret != DDI_SUCCESS) {
        cmn_err(CE_WARN, "ddi_intr_get_pri failed 0x%x", ret");
        (void) ddi_intr_free(xsp->xs_htable[0]);
        kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }

    if (intr_pri >= ddi_intr_get_hilevel_pri()) {

    mutex_init(&xsp->high_mu, NULL, MUTEX_DRIVER,
        DDI_INTR_PRI(intr_pri));

    ret = ddi_intr_add_handler(xsp->xs_htable[0],
        mydevhigh_intr, (caddr_t)xsp, NULL);
    if (ret != DDI_SUCCESS) {
        cmn_err(CE_WARN, "ddi_intr_add_handler failed 0x%x", ret");
        mutex_destroy(&xsp>xs_int_mutex);
            (void) ddi_intr_free(xsp->xs_htable[0]);
            kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }

    /* add soft interrupt */
    if (ddi_intr_add_softint(xsp->xs_dip, &xsp->xs_softint_hdl,
        DDI_INTR_SOFTPRI_MAX, xs_soft_intr, (caddr_t)xsp) !=
        DDI_SUCCESS) {
        cmn_err(CE_WARN, "add soft interrupt failed");
        mutex_destroy(&xsp->high_mu);
        (void) ddi_intr_remove_handler(xsp->xs_htable[0]);
            (void) ddi_intr_free(xsp->xs_htable[0]);
            kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }


    xsp->low_soft_pri = DDI_INTR_SOFTPRI_MAX;

    mutex_init(&xsp->low_mu, NULL, MUTEX_DRIVER,
        DDI_INTR_PRI(xsp->low_soft_pri));

    } else {
    /*
     * regular interrupt registration continues from here
     * do not use a soft interrupt
     */
    }

    return (DDI_SUCCESS);
}
 

The high-level interrupt routine services the device and queues the data. The high-level routine triggers a software interrupt if the low-level routine is not running, as the following example demonstrates.

static uint_t
mydevhigh_intr(caddr_t arg1, caddr_t arg2)
{
   struct mydevstate    *xsp = (struct mydevstate *)arg1;
   uint8_t    status;
   volatile  uint8_t  temp;
   int    need_softint;

    mutex_enter(&xsp->high_mu);
    /* read status */
    status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    if (!(status & INTERRUPTING)) {
        mutex_exit(&xsp->high_mu);
        return (DDI_INTR_UNCLAIMED); /* dev not interrupting */
    }

    ddi_put8(xsp->data_access_handle,&xsp->regp->csr,
        CLEAR_INTERRUPT | ENABLE_INTERRUPTS);
    /* flush store buffers */
    temp = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);

    /* read data from device, queue data for low-level interrupt handler */

    if (xsp->softint_running)
        need_softint = 0;
    else {
        xsp->softint_count++;
        need_softint = 1;
    }
    mutex_exit(&xsp->high_mu);

    /* read-only access to xsp->id, no mutex needed */
    if (need_softint) {
        ret = ddi_intr_trigger_softint(xsp->xs_softint_hdl, NULL);
        if (ret == DDI_EPENDING) {
            cmn_err(CE_WARN, "ddi_intr_trigger_softint() soft interrupt "
                "already pending for this handler");
        } else if (ret != DDI_SUCCESS) {
            cmn_err(CE_WARN, "ddi_intr_trigger_softint() failed");
        }           
    }

    return (DDI_INTR_CLAIMED);
}
 

The low-level interrupt routine is started by the high-level interrupt routine, which triggers a software interrupt. The low-level interrupt routine runs until nothing is left to process, as the following example shows.

static uint_t
mydev_soft_intr(caddr_t arg1, caddr_t arg2)
{
    struct mydevstate *mydevp = (struct mydevstate *)arg1;
    /* ... */
    mutex_enter(&mydevp->low_mu);
    mutex_enter(&mydevp->high_mu);
    if (mydevp->softint_count > 1) {
        mydevp->softint_count--;
        mutex_exit(&mydevp->high_mu);
        mutex_exit(&mydevp->low_mu);
        return (DDI_INTR_CLAIMED);
    }

    if (queue empty) {
        mutex_exit(&mydevp->high_mu);
        mutex_exit(&mydevp->low_mu);
        return (DDI_INTR_UNCLAIMED);
    }

    mydevp->softint_running = 1;
    while (EMBEDDED COMMENT:data on queue) {
        ASSERT(mutex_owned(&mydevp->high_mu);
        dequeue data from high-level queue;
        mutex_exit(&mydevp->high_mu);
        normal interrupt processing
        mutex_enter(&mydevp->high_mu);
    }

    mydevp->softint_running = 0;
    mydevp->softint_count = 0;
    mutex_exit(&mydevp->high_mu);
    mutex_exit(&mydevp->low_mu);
    return (DDI_INTR_CLAIMED);
}
 
About the Authors

John Stearns is a former developer and technical writer working as a senior documentation engineer for the Sun Developer Network (SDN). He has written numerous articles and manuals for Sun Microsystems software products.

Govinda Tatti is a senior software engineer at Sun Microsystems, Inc. and has played a major role in the development of USB, PCI-Express, and advanced DDI interrupt frameworks for the Solaris OS.

Edward Gillett is a Member of the Technical Staff at Sun Microsystems, Inc. He has worked on various drivers (audio, smartcard) in the past and currently works on advanced DDI interrupt frameworks.

Anish Gupta is a senior I/O engineer at Sun Microsystems, Inc. He has played a major role in the development of USB, InfiniBand, PCI-Express, and advanced DDI interrupt frameworks for the Solaris OS.

<think>嗯,用户问的是在TI System Configuration Tool中,把Interrupt 1 SOC Source配置为SOC/EOC number 6是什么意思。首先,我需要回忆一下TI的SOC和EOC的概念。SOC是Start of Conversion,也就是转换开始,而EOC是End of Conversion,转换结束。在ADC模块中,通常每个转换通道或序列会有对应的SOC配置,可能每个SOC有一个编号,比如0到N。 接下来,用户提到的SOC/EOC number 6,应该是指第6个SOC或EOC事件作为中断源。不过具体是SOC还是EOC触发中断,可能需要看具体的芯片型号和文档。例如,某些TI的MCU允许在SOC或EOC时触发中断,或者两者之间的某个阶段。需要确认SOC/EOC number 6是指第6个转换序列的开始还是结束。 然后,Interrupt 1可能指的是某个特定的中断线或中断向量,不同的中断号对应不同的处理函数。将SOC Source配置为6,意味着当第6个SOC事件发生时,会触发Interrupt 1。或者,当第6个EOC事件完成时触发中断。这时候需要明确SOC和EOC在配置中的具体作用。 另外,用户之前的问题是关于ADC的中断配置,所以可能是在配置ADC模块的中断触发源。例如,在ADC的多个转换序列中,每个序列的SOC或EOC可以配置不同的中断源。比如,某个ADC有多个SOC,每个SOC对应一个转换通道或序列,当该SOC的转换开始或结束时,触发对应的中断。 可能的混淆点在于SOC和EOC的区别。SOC是转换开始的触发点,而EOC是转换完成的触发点。如果用户将中断源配置为SOC number 6,那么每次第6个转换开始时触发中断;如果是EOC number 6,则是当第6个转换完成时触发中断。需要确认这里的SOC/EOC number 6具体指的是哪一个。 另外,需要考虑到不同的TI芯片可能配置方式不同。比如,MSP430、C2000或Tiva系列可能有不同的ADC模块结构。因此,可能需要参考具体的芯片手册来确认SOC/EOC的编号和中断源的关系。例如,在某个芯片中,ADC可能有多个SOC配置,每个SOC对应一个触发源和通道,当该SOC被触发时启动转换,而EOC则是转换完成的中断。 因此,正确的解释应该是:当配置Interrupt 1的SOC Source为SOC/EOC number 6时,表示该中断将在第6个转换序列的开始或完成时被触发。具体是SOC还是EOC,需要根据配置选项的命名和芯片手册来确定。例如,如果选项是SOC number 6,则中断在转换开始时触发;如果是EOC number 6,则在转换完成时触发。这种配置允许用户针对特定的转换事件设置中断,以便及时处理数据或进行下一步操作。 此外,用户可能的应用场景可能涉及多通道或序列转换,需要不同的中断处理。例如,在多个ADC通道按顺序转换时,每个通道的转换完成(EOC)触发中断,以便读取数据。或者,在特定通道的转换开始时(SOC)触发中断,用于同步其他外设或操作。 总结来说,用户的问题需要明确SOC/EOC number 6的具体含义,以及Interrupt 1对应的中断处理函数或优先级。结合TI的SysConfig工具中的选项和芯片手册,可以确定该配置的具体作用和应用方式。</think>在 **TI System Configuration Tool**(如 SysConfig)中,将 **Interrupt 1 SOC Source** 配置为 **SOC/EOC number 6**,主要用于关联特定 ADC 转换事件(如某个通道或序列的启动/完成)与中断触发的绑定关系。以下是详细解析: --- ### **一、基本概念** 1. **SOC(Start of Conversion)** - ADC 转换的**启动事件**,通常由软件或硬件触发(如定时器、外部信号)。 - 每个 SOC 对应一个独立的转换配置(如通道选择、采样时间)。 2. **EOC(End of Conversion)** - ADC 转换完成的**结束事件**,标志数据已就绪可读取。 3. **SOC Number** - 在支持多通道/序列转换的 ADC 模块中,每个 SOC 分配一个编号(如 0~15),对应不同转换任务的配置。 --- ### **二、配置 `SOC/EOC number 6` 的作用** - **含义**:将 **Interrupt 1** 的中断触发源绑定到 **第 6 号 SOC 或 EOC 事件**。 - **具体行为**: - **若绑定 SOC 事件**:当第 6 号 SOC **启动转换**时,触发 Interrupt 1。 - **若绑定 EOC 事件**:当第 6 号 SOC **完成转换**时,触发 Interrupt 1。 - **典型场景**: - 需对特定通道(如温度传感器)的转换完成事件进行实时响应。 - 在多通道序列中,需对某一关键步骤(如第 6 步)的启动或完成进行同步控制。 --- ### **三、配置示例(以 TI C2000 系列为例)** 1. **SysConfig 中的选项**: - 在 ADC 中断配置界面,选择 `Interrupt 1`,将其触发源设置为 `SOC/EOC 6`。 2. **代码生成结果**: SysConfig 会自动生成关联中断服务函数(ISR)和事件标志的代码,例如: ```c // 自动生成的中断配置代码 void ADC_Interrupt1_ISR(void) { if (ADC_getInterruptStatus(ADC_INT_NUMBER6)) { // 检查第 6 号事件标志 uint16_t result = ADC_readResult(ADC_SOC_NUMBER6); // 读取数据 // 处理数据或触发后续操作 ADC_clearInterruptStatus(ADC_INT_NUMBER6); // 清除标志 } } ``` --- ### **四、关键配置项解析** | **配置项** | **说明** | |-------------------------|-------------------------------------------------------------------------| | **SOC/EOC 选择** | 决定中断触发时机:<br>- **SOC**:转换开始时触发(适合同步其他操作)。<br>- **EOC**:转换完成时触发(适合读取数据)。 | | **SOC 编号(如 6)** | 指定具体哪个 SOC 配置(通道、触发源)关联此中断。 | | **中断服务函数绑定** | 自动生成 ISR 框架,开发者只需填充数据处理逻辑。 | --- ### **五、典型应用场景** #### 1. **单通道高优先级数据采集** - 配置 SOC 6 对应一个关键传感器通道(如电流检测),绑定 EOC 中断,确保转换完成后立即处理数据。 ```c // 初始化代码(SysConfig 生成) ADC_setupSOC(6, ADC_CH_ADCIN4, ADC_TRIGGER_EPWM1, 20); // 通道4,由ePWM1触发,采样时间20ns ADC_enableInterrupt(ADC_INT_NUMBER6); // 使能中断 ``` #### 2. **多通道序列中的关键节点** - 在 10 个通道的扫描序列中,SOC 6 对应一个特殊校准通道,通过 SOC 中断在启动时同步其他外设。 ```c void ADC_Interrupt1_ISR(void) { if (ADC_checkSOCFlag(6)) { // SOC 6 触发 DAC_setVoltage(CALIBRATION_VOLTAGE); // 同步输出校准电压 ADC_clearSOCFlag(6); } } ``` --- ### **六、注意事项** 1. **避免中断冲突** - 若多个 SOC/EOC 共享同一中断线,需在 ISR 中检查所有相关标志位。 - **示例**: ```c void ADC_Interrupt1_ISR(void) { if (ADC_getInterruptStatus(ADC_INT_NUMBER6)) { /* 处理 SOC6 */ } if (ADC_getInterruptStatus(ADC_INT_NUMBER7)) { /* 处理 SOC7 */ } } ``` 2. **时序与性能** - 高频中断(如 EOC 触发)需确保 ISR 执行时间短,必要时结合 DMA 传输。 3. **芯片特定差异** - 不同 TI 芯片的 ADC 中断机制可能不同,需参考具体手册(如 MSP430 的 `ADC12IE` 位,Tiva 的 `ADC_ISC` 寄存器)。 --- ### **七、调试技巧** - **逻辑分析仪**:观察 SOC 触发信号与中断触发的时序关系。 - **断点调试**:在 ISR 内设置断点,验证是否按预期触发。 - **寄存器监控**:通过调试器查看 `ADC_INTx` 相关寄存器(如 `ADC_RAW_INTR`)的状态。 --- ### **总结** 将 `Interrupt 1 SOC Source` 配置为 `SOC/EOC number 6`,本质上是将 ADC 模块的特定转换事件(启动或完成)与中断机制绑定,实现精确的事件驱动控制。此配置在多通道、实时性要求高的系统中尤为重要,开发者需结合具体需求选择 SOC 或 EOC 触发模式,并合理优化中断服务函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值