Add support for MSI/MSIX deallocation on GICv3-ITS

Allow to deallocate previously allocated ITS device along with
its interrupts. Interrupt numbers are being freed when the last
LPI number is no longer busy.

Reviewed by:	wma
Obtained from:	Semihalf
Sponsored by:	Cavium
Differential Revision:	https://reviews.freebsd.org/D6351
This commit is contained in:
Zbigniew Bodek 2016-05-18 10:09:07 +00:00
parent b108666635
commit b02a807b46
2 changed files with 87 additions and 8 deletions

View File

@ -75,8 +75,10 @@ static device_method_t gic_v3_its_methods[] = {
*/
/* MSI-X */
DEVMETHOD(pic_alloc_msix, gic_v3_its_alloc_msix),
DEVMETHOD(pic_release_msix, gic_v3_its_release_msix),
/* MSI */
DEVMETHOD(pic_alloc_msi, gic_v3_its_alloc_msi),
DEVMETHOD(pic_release_msi, gic_v3_its_release_msi),
DEVMETHOD(pic_map_msi, gic_v3_its_map_msi),
/* End */
@ -882,6 +884,7 @@ retry:
bit_nset(bitmap, fclr, fclr + nvecs - 1);
lpic->lpi_base = fclr + GIC_FIRST_LPI;
lpic->lpi_num = nvecs;
lpic->lpi_busy = 0;
lpic->lpi_free = lpic->lpi_num;
lpic->lpi_col_ids = col_ids;
for (i = 0; i < lpic->lpi_num; i++) {
@ -901,10 +904,9 @@ lpi_free_chunk(struct gic_v3_its_softc *sc, struct lpi_chunk *lpic)
{
int start, end;
KASSERT((lpic->lpi_free == lpic->lpi_num),
("Trying to free LPI chunk that is still in use.\n"));
mtx_lock_spin(&sc->its_dev_lock);
KASSERT((lpic->lpi_busy == 0),
("Trying to free LPI chunk that is still in use.\n"));
/* First bit of this chunk in a global bitmap */
start = lpic->lpi_base - GIC_FIRST_LPI;
/* and last bit of this chunk... */
@ -1493,6 +1495,7 @@ its_device_alloc(struct gic_v3_its_softc *sc, device_t pci_dev,
u_int nvecs)
{
struct its_dev *newdev;
vm_offset_t itt_addr;
uint64_t typer;
uint32_t devid;
size_t esize;
@ -1528,16 +1531,18 @@ its_device_alloc(struct gic_v3_its_softc *sc, device_t pci_dev,
* Allocate ITT for this device.
* PA has to be 256 B aligned. At least two entries for device.
*/
newdev->itt = (vm_offset_t)contigmalloc(
roundup2(roundup2(nvecs, 2) * esize, 0x100), M_GIC_V3_ITS,
(M_NOWAIT | M_ZERO), 0, ~0UL, 0x100, 0);
if (newdev->itt == 0) {
newdev->itt_size = roundup2(roundup2(nvecs, 2) * esize, 0x100);
itt_addr = (vm_offset_t)contigmalloc(
newdev->itt_size, M_GIC_V3_ITS, (M_NOWAIT | M_ZERO),
0, ~0UL, 0x100, 0);
if (itt_addr == 0) {
lpi_free_chunk(sc, &newdev->lpis);
free(newdev, M_GIC_V3_ITS);
return (NULL);
}
mtx_lock_spin(&sc->its_dev_lock);
newdev->itt = itt_addr;
TAILQ_INSERT_TAIL(&sc->its_dev_list, newdev, entry);
mtx_unlock_spin(&sc->its_dev_lock);
@ -1547,6 +1552,50 @@ its_device_alloc(struct gic_v3_its_softc *sc, device_t pci_dev,
return (newdev);
}
static void
its_device_free(struct gic_v3_its_softc *sc, device_t pci_dev,
u_int nvecs)
{
struct its_dev *odev;
mtx_lock_spin(&sc->its_dev_lock);
/* Find existing device if any */
odev = its_device_find_locked(sc, pci_dev, 0);
if (odev == NULL) {
mtx_unlock_spin(&sc->its_dev_lock);
return;
}
KASSERT((nvecs <= odev->lpis.lpi_num) && (nvecs <= odev->lpis.lpi_busy),
("Invalid number of LPI vectors to free %d (total %d) (busy %d)",
nvecs, odev->lpis.lpi_num, odev->lpis.lpi_busy));
/* Just decrement number of busy LPIs in chunk */
odev->lpis.lpi_busy -= nvecs;
if (odev->lpis.lpi_busy != 0) {
mtx_unlock_spin(&sc->its_dev_lock);
return;
}
/*
* At that point we know that there are no busy LPIs for this device.
* Entire ITS device can now be removed.
*/
mtx_unlock_spin(&sc->its_dev_lock);
/* Unmap device in ITS */
its_cmd_mapd(sc, odev, 0);
/* Free ITT */
KASSERT(odev->itt != 0, ("Invalid ITT in valid ITS device"));
contigfree((void *)odev->itt, odev->itt_size, M_GIC_V3_ITS);
/* Free chunk */
lpi_free_chunk(sc, &odev->lpis);
/* Free device */
mtx_lock_spin(&sc->its_dev_lock);
TAILQ_REMOVE(&sc->its_dev_list, odev, entry);
mtx_unlock_spin(&sc->its_dev_lock);
free((void *)odev, M_GIC_V3_ITS);
}
static __inline void
its_device_asign_lpi_locked(struct gic_v3_its_softc *sc,
struct its_dev *its_dev, u_int *irq)
@ -1561,6 +1610,7 @@ its_device_asign_lpi_locked(struct gic_v3_its_softc *sc,
*irq = its_dev->lpis.lpi_base + (its_dev->lpis.lpi_num -
its_dev->lpis.lpi_free);
its_dev->lpis.lpi_free--;
its_dev->lpis.lpi_busy++;
}
/*
@ -1677,6 +1727,18 @@ gic_v3_its_alloc_msix(device_t dev, device_t pci_dev, int *irq)
return (0);
}
int
gic_v3_its_release_msix(device_t dev, device_t pci_dev, int irq __unused)
{
struct gic_v3_its_softc *sc;
sc = device_get_softc(dev);
its_device_free(sc, pci_dev, 1);
return (0);
}
int
gic_v3_its_alloc_msi(device_t dev, device_t pci_dev, int count, int *irqs)
{
@ -1700,6 +1762,18 @@ gic_v3_its_alloc_msi(device_t dev, device_t pci_dev, int count, int *irqs)
return (0);
}
int
gic_v3_its_release_msi(device_t dev, device_t pci_dev, int count,
int *irqs __unused)
{
struct gic_v3_its_softc *sc;
sc = device_get_softc(dev);
its_device_free(sc, pci_dev, count);
return (0);
}
int
gic_v3_its_map_msi(device_t dev, device_t pci_dev, int irq, uint64_t *addr,
uint32_t *data)

View File

@ -112,9 +112,11 @@ DECLARE_CLASS(gic_v3_its_driver);
/* LPI chunk owned by ITS device */
struct lpi_chunk {
u_int lpi_base;
u_int lpi_num;
u_int lpi_free; /* First free LPI in set */
u_int *lpi_col_ids;
u_int lpi_num; /* Total number of LPIs in chunk */
u_int lpi_busy; /* Number of busy LPIs in chink */
};
/* ITS device */
@ -128,6 +130,7 @@ struct its_dev {
struct lpi_chunk lpis;
/* Virtual address of ITT */
vm_offset_t itt;
size_t itt_size;
};
TAILQ_HEAD(its_dev_list, its_dev);
@ -277,7 +280,9 @@ extern devclass_t gic_v3_its_devclass;
int gic_v3_its_detach(device_t);
int gic_v3_its_alloc_msix(device_t, device_t, int *);
int gic_v3_its_release_msix(device_t, device_t, int);
int gic_v3_its_alloc_msi(device_t, device_t, int, int *);
int gic_v3_its_release_msi(device_t, device_t, int, int *);
int gic_v3_its_map_msi(device_t, device_t, int, uint64_t *, uint32_t *);
int its_init_cpu(struct gic_v3_its_softc *);