HPET: create /dev/hpetN as a way to access HPET from userland.

In some cases, TSC is broken and special applications might benefit
from memory mapping HPET and reading the registers to count time.
Most often the main HPET counter is 32-bit only[1], so this only gives
the application a 300 second window based on the default HPET
interval.
Other applications, such as Intel's DPDK, expect /dev/hpet to be
present and use it to count time as well.

Although we have an almost userland version of gettimeofday() which
uses rdtsc in userland, it's not always possible to use it, depending
on how broken the multi-socket hardware is.

Install the acpi_hpet.h so that applications can use the HPET register
definitions.

[1] I haven't found a system where HPET's main counter uses more than
32 bit.  There seems to be a discrepancy in the Intel documentation
(claiming it's a 64-bit counter) and the actual implementation (a
32-bit counter in a 64-bit memory area).

MFC after:	1 week
Relnotes:	yes
This commit is contained in:
Rui Paulo 2014-10-24 18:39:15 +00:00
parent ca807c8a88
commit 3149cc9df6
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=273598
2 changed files with 83 additions and 1 deletions

View File

@ -159,6 +159,8 @@ copies:
cd ${.CURDIR}/../sys/dev/acpica; \
${INSTALL} -C -o ${BINOWN} -g ${BINGRP} -m 444 acpiio.h \
${DESTDIR}${INCLUDEDIR}/dev/acpica
${INSTALL} -C -o ${BINOWN} -g ${BINGRP} -m 444 acpi_hpet.h \
${DESTDIR}${INCLUDEDIR}/dev/acpica
cd ${.CURDIR}/../sys/dev/agp; \
${INSTALL} -C -o ${BINOWN} -g ${BINGRP} -m 444 agpreg.h \
${DESTDIR}${INCLUDEDIR}/dev/agp
@ -243,7 +245,7 @@ symlinks:
done
.endfor
cd ${.CURDIR}/../sys/dev/acpica; \
for h in acpiio.h; do \
for h in acpiio.h acpi_hpet.h; do \
ln -fs ../../../../sys/dev/acpica/$$h \
${DESTDIR}${INCLUDEDIR}/dev/acpica; \
done

View File

@ -35,11 +35,13 @@ __FBSDID("$FreeBSD$");
#include "opt_apic.h"
#endif
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
@ -106,6 +108,23 @@ struct hpet_softc {
char name[8];
} t[32];
int num_timers;
struct cdev *pdev;
int mmap_allow;
int mmap_allow_write;
int devinuse;
};
static d_open_t hpet_open;
static d_close_t hpet_close;
static d_mmap_t hpet_mmap;
static struct cdevsw hpet_cdevsw = {
.d_version = D_VERSION,
.d_flags = D_TRACKCLOSE,
.d_name = "hpet",
.d_open = hpet_open,
.d_close = hpet_close,
.d_mmap = hpet_mmap,
};
static u_int hpet_get_timecount(struct timecounter *tc);
@ -317,6 +336,47 @@ hpet_find_irq_rid(device_t dev, u_long start, u_long end)
}
}
static int
hpet_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
{
struct hpet_softc *sc;
sc = cdev->si_drv1;
if (!sc->mmap_allow)
return (EPERM);
if (atomic_cmpset_32(&sc->devinuse, 0, 1) == 0)
return (EBUSY);
else
return (0);
}
static int
hpet_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
{
struct hpet_softc *sc;
sc = cdev->si_drv1;
sc->devinuse = 0;
return (0);
}
static int
hpet_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr,
int nprot, vm_memattr_t *memattr)
{
struct hpet_softc *sc;
sc = cdev->si_drv1;
if (offset > rman_get_size(sc->mem_res))
return (EINVAL);
if (!sc->mmap_allow_write && (nprot & PROT_WRITE))
return (EPERM);
*paddr = rman_get_start(sc->mem_res) + offset;
return (0);
}
/* Discover the HPET via the ACPI table of the same name. */
static void
hpet_identify(driver_t *driver, device_t parent)
@ -701,6 +761,26 @@ hpet_attach(device_t dev)
maxhpetet++;
}
}
sc->pdev = make_dev(&hpet_cdevsw, 0, UID_ROOT, GID_WHEEL,
0600, "hpet%d", device_get_unit(dev));
if (sc->pdev) {
sc->pdev->si_drv1 = sc;
sc->mmap_allow = 1;
TUNABLE_INT_FETCH("hw.acpi.hpet.mmap_allow",
&sc->mmap_allow);
sc->mmap_allow_write = 1;
TUNABLE_INT_FETCH("hw.acpi.hpet.mmap_allow_write",
&sc->mmap_allow_write);
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
OID_AUTO, "mmap_allow",
CTLFLAG_RW, &sc->mmap_allow, 0,
"Allow userland to memory map HPET");
} else
device_printf(dev, "could not create /dev/hpet%d\n",
device_get_unit(dev));
return (0);
}