Resource Allocation

The allocation of I/O ports and irqs is done via standard kernel functions. Unlike ALSA ver.0.5.x., there are no helpers for that. And these resources must be released in the destructor function (see below). Also, on ALSA 0.9.x, you don't need to allocate (pseudo-)DMA for PCI like in ALSA 0.5.x.

Now assume that the PCI device has an I/O port with 8 bytes and an interrupt. Then struct mychip will have the following fields:


  struct mychip {
          struct snd_card *card;

          unsigned long port;
          int irq;
  };

          

For an I/O port (and also a memory region), you need to have the resource pointer for the standard resource management. For an irq, you have to keep only the irq number (integer). But you need to initialize this number as -1 before actual allocation, since irq 0 is valid. The port address and its resource pointer can be initialized as null by kzalloc() automatically, so you don't have to take care of resetting them.

The allocation of an I/O port is done like this:


  err = pci_request_regions(pci, "My Chip");
  if (err < 0) { 
          kfree(chip);
          pci_disable_device(pci);
          return err;
  }
  chip->port = pci_resource_start(pci, 0);

          

It will reserve the I/O port region of 8 bytes of the given PCI device. The returned value, chip->res_port, is allocated via kmalloc() by request_region(). The pointer must be released via kfree(), but there is a problem with this. This issue will be explained later.

The allocation of an interrupt source is done like this:


  if (request_irq(pci->irq, snd_mychip_interrupt,
                  IRQF_SHARED, KBUILD_MODNAME, chip)) {
          printk(KERN_ERR "cannot grab irq %d\n", pci->irq);
          snd_mychip_free(chip);
          return -EBUSY;
  }
  chip->irq = pci->irq;

          

where snd_mychip_interrupt() is the interrupt handler defined later. Note that chip->irq should be defined only when request_irq() succeeded.

On the PCI bus, interrupts can be shared. Thus, IRQF_SHARED is used as the interrupt flag of request_irq().

The last argument of request_irq() is the data pointer passed to the interrupt handler. Usually, the chip-specific record is used for that, but you can use what you like, too.

I won't give details about the interrupt handler at this point, but at least its appearance can be explained now. The interrupt handler looks usually like the following:


  static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
  {
          struct mychip *chip = dev_id;
          ....
          return IRQ_HANDLED;
  }

          

Now let's write the corresponding destructor for the resources above. The role of destructor is simple: disable the hardware (if already activated) and release the resources. So far, we have no hardware part, so the disabling code is not written here.

To release the resources, the check-and-release method is a safer way. For the interrupt, do like this:


  if (chip->irq >= 0)
          free_irq(chip->irq, chip);

          

Since the irq number can start from 0, you should initialize chip->irq with a negative value (e.g. -1), so that you can check the validity of the irq number as above.

When you requested I/O ports or memory regions via pci_request_region() or pci_request_regions() like in this example, release the resource(s) using the corresponding function, pci_release_region() or pci_release_regions().


  pci_release_regions(chip->pci);

          

When you requested manually via request_region() or request_mem_region, you can release it via release_resource(). Suppose that you keep the resource pointer returned from request_region() in chip->res_port, the release procedure looks like:


  release_and_free_resource(chip->res_port);

          

Don't forget to call pci_disable_device() before the end.

And finally, release the chip-specific record.


  kfree(chip);

          

We didn't implement the hardware disabling part in the above. If you need to do this, please note that the destructor may be called even before the initialization of the chip is completed. It would be better to have a flag to skip hardware disabling if the hardware was not initialized yet.

When the chip-data is assigned to the card using snd_device_new() with SNDRV_DEV_LOWLELVEL , its destructor is called at the last. That is, it is assured that all other components like PCMs and controls have already been released. You don't have to stop PCMs, etc. explicitly, but just call low-level hardware stopping.

The management of a memory-mapped region is almost as same as the management of an I/O port. You'll need three fields like the following:


  struct mychip {
          ....
          unsigned long iobase_phys;
          void __iomem *iobase_virt;
  };

          

and the allocation would be like below:


  if ((err = pci_request_regions(pci, "My Chip")) < 0) {
          kfree(chip);
          return err;
  }
  chip->iobase_phys = pci_resource_start(pci, 0);
  chip->iobase_virt = ioremap_nocache(chip->iobase_phys,
                                      pci_resource_len(pci, 0));

          

and the corresponding destructor would be:


  static int snd_mychip_free(struct mychip *chip)
  {
          ....
          if (chip->iobase_virt)
                  iounmap(chip->iobase_virt);
          ....
          pci_release_regions(chip->pci);
          ....
  }