linux/drivers/char/hpet.c
Linus Torvalds 426ee5196d sysctl-6.7-rc1
To help make the move of sysctls out of kernel/sysctl.c not incur a size
 penalty sysctl has been changed to allow us to not require the sentinel, the
 final empty element on the sysctl array. Joel Granados has been doing all this
 work. On the v6.6 kernel we got the major infrastructure changes required to
 support this. For v6.7-rc1 we have all arch/ and drivers/ modified to remove
 the sentinel. Both arch and driver changes have been on linux-next for a bit
 less than a month. It is worth re-iterating the value:
 
   - this helps reduce the overall build time size of the kernel and run time
      memory consumed by the kernel by about ~64 bytes per array
   - the extra 64-byte penalty is no longer inncurred now when we move sysctls
     out from kernel/sysctl.c to their own files
 
 For v6.8-rc1 expect removal of all the sentinels and also then the unneeded
 check for procname == NULL.
 
 The last 2 patches are fixes recently merged by Krister Johansen which allow
 us again to use softlockup_panic early on boot. This used to work but the
 alias work broke it. This is useful for folks who want to detect softlockups
 super early rather than wait and spend money on cloud solutions with nothing
 but an eventual hung kernel. Although this hadn't gone through linux-next it's
 also a stable fix, so we might as well roll through the fixes now.
 -----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCgAwFiEENnNq2KuOejlQLZofziMdCjCSiKcFAmVCqKsSHG1jZ3JvZkBr
 ZXJuZWwub3JnAAoJEM4jHQowkoinEgYQAIpkqRL85DBwems19Uk9A27lkctwZ6Fc
 HdslQCObQTsbuKVimZFP4IL2beUfUE0cfLZCXlzp+4nRDOf6vyhyf3w19jPQtI0Q
 YdqwTk9y6G5VjDsb35QK0+UBloY/kZ1H3/LW4uCwjXTuksUGmWW2Qvey35696Scv
 hDMLADqKQmdpYxLUaNi9QyYbEAjYtOai2ezg3+i7hTG168t1k/Ab2BxIFrPVsCR2
 FAiq05L4ugWjNskdsWBjck05JZsx9SK/qcAxpIPoUm4nGiFNHApXE0E0hs3vsnmn
 WIHIbxCQw8ZlUDlmw4S+0YH3NFFzFbWfmW8k2b0f2qZTJm/rU4KiJfcJVknkAUVF
 raFox6XDW0AUQ9L/NOUJ9ip5rup57GcFrMYocdJ3PPAvvmHKOb1D1O741p75RRcc
 9j7zwfIRrzjPUqzhsQS/GFjdJu3lJNmEBK1AcgrVry6WoItrAzJHKPPDC7TwaNmD
 eXpjxMl1sYzzHqtVh4hn+xkUYphj/6gTGMV8zdo+/FopFswgeJW9G8kHtlEWKDPk
 MRIKwACmfetP6f3ngHunBg+BOipbjCANL7JI0nOhVOQoaULxCCPx+IPJ6GfSyiuH
 AbcjH8DGI7fJbUkBFoF0dsRFZ2gH8ds1PYMbWUJ6x3FtuCuv5iIuvQYoaWU6itm7
 6f0KvCogg0fU
 =Qf50
 -----END PGP SIGNATURE-----

Merge tag 'sysctl-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux

Pull sysctl updates from Luis Chamberlain:
 "To help make the move of sysctls out of kernel/sysctl.c not incur a
  size penalty sysctl has been changed to allow us to not require the
  sentinel, the final empty element on the sysctl array. Joel Granados
  has been doing all this work. On the v6.6 kernel we got the major
  infrastructure changes required to support this. For v6.7-rc1 we have
  all arch/ and drivers/ modified to remove the sentinel. Both arch and
  driver changes have been on linux-next for a bit less than a month. It
  is worth re-iterating the value:

   - this helps reduce the overall build time size of the kernel and run
     time memory consumed by the kernel by about ~64 bytes per array

   - the extra 64-byte penalty is no longer inncurred now when we move
     sysctls out from kernel/sysctl.c to their own files

  For v6.8-rc1 expect removal of all the sentinels and also then the
  unneeded check for procname == NULL.

  The last two patches are fixes recently merged by Krister Johansen
  which allow us again to use softlockup_panic early on boot. This used
  to work but the alias work broke it. This is useful for folks who want
  to detect softlockups super early rather than wait and spend money on
  cloud solutions with nothing but an eventual hung kernel. Although
  this hadn't gone through linux-next it's also a stable fix, so we
  might as well roll through the fixes now"

* tag 'sysctl-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux: (23 commits)
  watchdog: move softlockup_panic back to early_param
  proc: sysctl: prevent aliased sysctls from getting passed to init
  intel drm: Remove now superfluous sentinel element from ctl_table array
  Drivers: hv: Remove now superfluous sentinel element from ctl_table array
  raid: Remove now superfluous sentinel element from ctl_table array
  fw loader: Remove the now superfluous sentinel element from ctl_table array
  sgi-xp: Remove the now superfluous sentinel element from ctl_table array
  vrf: Remove the now superfluous sentinel element from ctl_table array
  char-misc: Remove the now superfluous sentinel element from ctl_table array
  infiniband: Remove the now superfluous sentinel element from ctl_table array
  macintosh: Remove the now superfluous sentinel element from ctl_table array
  parport: Remove the now superfluous sentinel element from ctl_table array
  scsi: Remove now superfluous sentinel element from ctl_table array
  tty: Remove now superfluous sentinel element from ctl_table array
  xen: Remove now superfluous sentinel element from ctl_table array
  hpet: Remove now superfluous sentinel element from ctl_table array
  c-sky: Remove now superfluous sentinel element from ctl_talbe array
  powerpc: Remove now superfluous sentinel element from ctl_table arrays
  riscv: Remove now superfluous sentinel element from ctl_table array
  x86/vdso: Remove now superfluous sentinel element from ctl_table array
  ...
2023-11-01 20:51:41 -10:00

1017 lines
23 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel & MS High Precision Event Timer Implementation.
*
* Copyright (C) 2003 Intel Corporation
* Venki Pallipadi
* (c) Copyright 2004 Hewlett-Packard Development Company, L.P.
* Bob Picco <robert.picco@hp.com>
*/
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/sysctl.h>
#include <linux/wait.h>
#include <linux/sched/signal.h>
#include <linux/bcd.h>
#include <linux/seq_file.h>
#include <linux/bitops.h>
#include <linux/compat.h>
#include <linux/clocksource.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/acpi.h>
#include <linux/hpet.h>
#include <asm/current.h>
#include <asm/irq.h>
#include <asm/div64.h>
/*
* The High Precision Event Timer driver.
* This driver is closely modelled after the rtc.c driver.
* See HPET spec revision 1.
*/
#define HPET_USER_FREQ (64)
#define HPET_DRIFT (500)
#define HPET_RANGE_SIZE 1024 /* from HPET spec */
/* WARNING -- don't get confused. These macros are never used
* to write the (single) counter, and rarely to read it.
* They're badly named; to fix, someday.
*/
#if BITS_PER_LONG == 64
#define write_counter(V, MC) writeq(V, MC)
#define read_counter(MC) readq(MC)
#else
#define write_counter(V, MC) writel(V, MC)
#define read_counter(MC) readl(MC)
#endif
static DEFINE_MUTEX(hpet_mutex); /* replaces BKL */
static u32 hpet_nhpet, hpet_max_freq = HPET_USER_FREQ;
/* A lock for concurrent access by app and isr hpet activity. */
static DEFINE_SPINLOCK(hpet_lock);
#define HPET_DEV_NAME (7)
struct hpet_dev {
struct hpets *hd_hpets;
struct hpet __iomem *hd_hpet;
struct hpet_timer __iomem *hd_timer;
unsigned long hd_ireqfreq;
unsigned long hd_irqdata;
wait_queue_head_t hd_waitqueue;
struct fasync_struct *hd_async_queue;
unsigned int hd_flags;
unsigned int hd_irq;
unsigned int hd_hdwirq;
char hd_name[HPET_DEV_NAME];
};
struct hpets {
struct hpets *hp_next;
struct hpet __iomem *hp_hpet;
unsigned long hp_hpet_phys;
struct clocksource *hp_clocksource;
unsigned long long hp_tick_freq;
unsigned long hp_delta;
unsigned int hp_ntimer;
unsigned int hp_which;
struct hpet_dev hp_dev[];
};
static struct hpets *hpets;
#define HPET_OPEN 0x0001
#define HPET_IE 0x0002 /* interrupt enabled */
#define HPET_PERIODIC 0x0004
#define HPET_SHARED_IRQ 0x0008
static irqreturn_t hpet_interrupt(int irq, void *data)
{
struct hpet_dev *devp;
unsigned long isr;
devp = data;
isr = 1 << (devp - devp->hd_hpets->hp_dev);
if ((devp->hd_flags & HPET_SHARED_IRQ) &&
!(isr & readl(&devp->hd_hpet->hpet_isr)))
return IRQ_NONE;
spin_lock(&hpet_lock);
devp->hd_irqdata++;
/*
* For non-periodic timers, increment the accumulator.
* This has the effect of treating non-periodic like periodic.
*/
if ((devp->hd_flags & (HPET_IE | HPET_PERIODIC)) == HPET_IE) {
unsigned long t, mc, base, k;
struct hpet __iomem *hpet = devp->hd_hpet;
struct hpets *hpetp = devp->hd_hpets;
t = devp->hd_ireqfreq;
read_counter(&devp->hd_timer->hpet_compare);
mc = read_counter(&hpet->hpet_mc);
/* The time for the next interrupt would logically be t + m,
* however, if we are very unlucky and the interrupt is delayed
* for longer than t then we will completely miss the next
* interrupt if we set t + m and an application will hang.
* Therefore we need to make a more complex computation assuming
* that there exists a k for which the following is true:
* k * t + base < mc + delta
* (k + 1) * t + base > mc + delta
* where t is the interval in hpet ticks for the given freq,
* base is the theoretical start value 0 < base < t,
* mc is the main counter value at the time of the interrupt,
* delta is the time it takes to write the a value to the
* comparator.
* k may then be computed as (mc - base + delta) / t .
*/
base = mc % t;
k = (mc - base + hpetp->hp_delta) / t;
write_counter(t * (k + 1) + base,
&devp->hd_timer->hpet_compare);
}
if (devp->hd_flags & HPET_SHARED_IRQ)
writel(isr, &devp->hd_hpet->hpet_isr);
spin_unlock(&hpet_lock);
wake_up_interruptible(&devp->hd_waitqueue);
kill_fasync(&devp->hd_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
static void hpet_timer_set_irq(struct hpet_dev *devp)
{
unsigned long v;
int irq, gsi;
struct hpet_timer __iomem *timer;
spin_lock_irq(&hpet_lock);
if (devp->hd_hdwirq) {
spin_unlock_irq(&hpet_lock);
return;
}
timer = devp->hd_timer;
/* we prefer level triggered mode */
v = readl(&timer->hpet_config);
if (!(v & Tn_INT_TYPE_CNF_MASK)) {
v |= Tn_INT_TYPE_CNF_MASK;
writel(v, &timer->hpet_config);
}
spin_unlock_irq(&hpet_lock);
v = (readq(&timer->hpet_config) & Tn_INT_ROUTE_CAP_MASK) >>
Tn_INT_ROUTE_CAP_SHIFT;
/*
* In PIC mode, skip IRQ0-4, IRQ6-9, IRQ12-15 which is always used by
* legacy device. In IO APIC mode, we skip all the legacy IRQS.
*/
if (acpi_irq_model == ACPI_IRQ_MODEL_PIC)
v &= ~0xf3df;
else
v &= ~0xffff;
for_each_set_bit(irq, &v, HPET_MAX_IRQ) {
if (irq >= nr_irqs) {
irq = HPET_MAX_IRQ;
break;
}
gsi = acpi_register_gsi(NULL, irq, ACPI_LEVEL_SENSITIVE,
ACPI_ACTIVE_LOW);
if (gsi > 0)
break;
/* FIXME: Setup interrupt source table */
}
if (irq < HPET_MAX_IRQ) {
spin_lock_irq(&hpet_lock);
v = readl(&timer->hpet_config);
v |= irq << Tn_INT_ROUTE_CNF_SHIFT;
writel(v, &timer->hpet_config);
devp->hd_hdwirq = gsi;
spin_unlock_irq(&hpet_lock);
}
return;
}
static int hpet_open(struct inode *inode, struct file *file)
{
struct hpet_dev *devp;
struct hpets *hpetp;
int i;
if (file->f_mode & FMODE_WRITE)
return -EINVAL;
mutex_lock(&hpet_mutex);
spin_lock_irq(&hpet_lock);
for (devp = NULL, hpetp = hpets; hpetp && !devp; hpetp = hpetp->hp_next)
for (i = 0; i < hpetp->hp_ntimer; i++)
if (hpetp->hp_dev[i].hd_flags & HPET_OPEN) {
continue;
} else {
devp = &hpetp->hp_dev[i];
break;
}
if (!devp) {
spin_unlock_irq(&hpet_lock);
mutex_unlock(&hpet_mutex);
return -EBUSY;
}
file->private_data = devp;
devp->hd_irqdata = 0;
devp->hd_flags |= HPET_OPEN;
spin_unlock_irq(&hpet_lock);
mutex_unlock(&hpet_mutex);
hpet_timer_set_irq(devp);
return 0;
}
static ssize_t
hpet_read(struct file *file, char __user *buf, size_t count, loff_t * ppos)
{
DECLARE_WAITQUEUE(wait, current);
unsigned long data;
ssize_t retval;
struct hpet_dev *devp;
devp = file->private_data;
if (!devp->hd_ireqfreq)
return -EIO;
if (count < sizeof(unsigned long))
return -EINVAL;
add_wait_queue(&devp->hd_waitqueue, &wait);
for ( ; ; ) {
set_current_state(TASK_INTERRUPTIBLE);
spin_lock_irq(&hpet_lock);
data = devp->hd_irqdata;
devp->hd_irqdata = 0;
spin_unlock_irq(&hpet_lock);
if (data) {
break;
} else if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
goto out;
} else if (signal_pending(current)) {
retval = -ERESTARTSYS;
goto out;
}
schedule();
}
retval = put_user(data, (unsigned long __user *)buf);
if (!retval)
retval = sizeof(unsigned long);
out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&devp->hd_waitqueue, &wait);
return retval;
}
static __poll_t hpet_poll(struct file *file, poll_table * wait)
{
unsigned long v;
struct hpet_dev *devp;
devp = file->private_data;
if (!devp->hd_ireqfreq)
return 0;
poll_wait(file, &devp->hd_waitqueue, wait);
spin_lock_irq(&hpet_lock);
v = devp->hd_irqdata;
spin_unlock_irq(&hpet_lock);
if (v != 0)
return EPOLLIN | EPOLLRDNORM;
return 0;
}
#ifdef CONFIG_HPET_MMAP
#ifdef CONFIG_HPET_MMAP_DEFAULT
static int hpet_mmap_enabled = 1;
#else
static int hpet_mmap_enabled = 0;
#endif
static __init int hpet_mmap_enable(char *str)
{
get_option(&str, &hpet_mmap_enabled);
pr_info("HPET mmap %s\n", hpet_mmap_enabled ? "enabled" : "disabled");
return 1;
}
__setup("hpet_mmap=", hpet_mmap_enable);
static int hpet_mmap(struct file *file, struct vm_area_struct *vma)
{
struct hpet_dev *devp;
unsigned long addr;
if (!hpet_mmap_enabled)
return -EACCES;
devp = file->private_data;
addr = devp->hd_hpets->hp_hpet_phys;
if (addr & (PAGE_SIZE - 1))
return -ENOSYS;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return vm_iomap_memory(vma, addr, PAGE_SIZE);
}
#else
static int hpet_mmap(struct file *file, struct vm_area_struct *vma)
{
return -ENOSYS;
}
#endif
static int hpet_fasync(int fd, struct file *file, int on)
{
struct hpet_dev *devp;
devp = file->private_data;
if (fasync_helper(fd, file, on, &devp->hd_async_queue) >= 0)
return 0;
else
return -EIO;
}
static int hpet_release(struct inode *inode, struct file *file)
{
struct hpet_dev *devp;
struct hpet_timer __iomem *timer;
int irq = 0;
devp = file->private_data;
timer = devp->hd_timer;
spin_lock_irq(&hpet_lock);
writeq((readq(&timer->hpet_config) & ~Tn_INT_ENB_CNF_MASK),
&timer->hpet_config);
irq = devp->hd_irq;
devp->hd_irq = 0;
devp->hd_ireqfreq = 0;
if (devp->hd_flags & HPET_PERIODIC
&& readq(&timer->hpet_config) & Tn_TYPE_CNF_MASK) {
unsigned long v;
v = readq(&timer->hpet_config);
v ^= Tn_TYPE_CNF_MASK;
writeq(v, &timer->hpet_config);
}
devp->hd_flags &= ~(HPET_OPEN | HPET_IE | HPET_PERIODIC);
spin_unlock_irq(&hpet_lock);
if (irq)
free_irq(irq, devp);
file->private_data = NULL;
return 0;
}
static int hpet_ioctl_ieon(struct hpet_dev *devp)
{
struct hpet_timer __iomem *timer;
struct hpet __iomem *hpet;
struct hpets *hpetp;
int irq;
unsigned long g, v, t, m;
unsigned long flags, isr;
timer = devp->hd_timer;
hpet = devp->hd_hpet;
hpetp = devp->hd_hpets;
if (!devp->hd_ireqfreq)
return -EIO;
spin_lock_irq(&hpet_lock);
if (devp->hd_flags & HPET_IE) {
spin_unlock_irq(&hpet_lock);
return -EBUSY;
}
devp->hd_flags |= HPET_IE;
if (readl(&timer->hpet_config) & Tn_INT_TYPE_CNF_MASK)
devp->hd_flags |= HPET_SHARED_IRQ;
spin_unlock_irq(&hpet_lock);
irq = devp->hd_hdwirq;
if (irq) {
unsigned long irq_flags;
if (devp->hd_flags & HPET_SHARED_IRQ) {
/*
* To prevent the interrupt handler from seeing an
* unwanted interrupt status bit, program the timer
* so that it will not fire in the near future ...
*/
writel(readl(&timer->hpet_config) & ~Tn_TYPE_CNF_MASK,
&timer->hpet_config);
write_counter(read_counter(&hpet->hpet_mc),
&timer->hpet_compare);
/* ... and clear any left-over status. */
isr = 1 << (devp - devp->hd_hpets->hp_dev);
writel(isr, &hpet->hpet_isr);
}
sprintf(devp->hd_name, "hpet%d", (int)(devp - hpetp->hp_dev));
irq_flags = devp->hd_flags & HPET_SHARED_IRQ ? IRQF_SHARED : 0;
if (request_irq(irq, hpet_interrupt, irq_flags,
devp->hd_name, (void *)devp)) {
printk(KERN_ERR "hpet: IRQ %d is not free\n", irq);
irq = 0;
}
}
if (irq == 0) {
spin_lock_irq(&hpet_lock);
devp->hd_flags ^= HPET_IE;
spin_unlock_irq(&hpet_lock);
return -EIO;
}
devp->hd_irq = irq;
t = devp->hd_ireqfreq;
v = readq(&timer->hpet_config);
/* 64-bit comparators are not yet supported through the ioctls,
* so force this into 32-bit mode if it supports both modes
*/
g = v | Tn_32MODE_CNF_MASK | Tn_INT_ENB_CNF_MASK;
if (devp->hd_flags & HPET_PERIODIC) {
g |= Tn_TYPE_CNF_MASK;
v |= Tn_TYPE_CNF_MASK | Tn_VAL_SET_CNF_MASK;
writeq(v, &timer->hpet_config);
local_irq_save(flags);
/*
* NOTE: First we modify the hidden accumulator
* register supported by periodic-capable comparators.
* We never want to modify the (single) counter; that
* would affect all the comparators. The value written
* is the counter value when the first interrupt is due.
*/
m = read_counter(&hpet->hpet_mc);
write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
/*
* Then we modify the comparator, indicating the period
* for subsequent interrupt.
*/
write_counter(t, &timer->hpet_compare);
} else {
local_irq_save(flags);
m = read_counter(&hpet->hpet_mc);
write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
}
if (devp->hd_flags & HPET_SHARED_IRQ) {
isr = 1 << (devp - devp->hd_hpets->hp_dev);
writel(isr, &hpet->hpet_isr);
}
writeq(g, &timer->hpet_config);
local_irq_restore(flags);
return 0;
}
/* converts Hz to number of timer ticks */
static inline unsigned long hpet_time_div(struct hpets *hpets,
unsigned long dis)
{
unsigned long long m;
m = hpets->hp_tick_freq + (dis >> 1);
return div64_ul(m, dis);
}
static int
hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg,
struct hpet_info *info)
{
struct hpet_timer __iomem *timer;
struct hpets *hpetp;
int err;
unsigned long v;
switch (cmd) {
case HPET_IE_OFF:
case HPET_INFO:
case HPET_EPI:
case HPET_DPI:
case HPET_IRQFREQ:
timer = devp->hd_timer;
hpetp = devp->hd_hpets;
break;
case HPET_IE_ON:
return hpet_ioctl_ieon(devp);
default:
return -EINVAL;
}
err = 0;
switch (cmd) {
case HPET_IE_OFF:
if ((devp->hd_flags & HPET_IE) == 0)
break;
v = readq(&timer->hpet_config);
v &= ~Tn_INT_ENB_CNF_MASK;
writeq(v, &timer->hpet_config);
if (devp->hd_irq) {
free_irq(devp->hd_irq, devp);
devp->hd_irq = 0;
}
devp->hd_flags ^= HPET_IE;
break;
case HPET_INFO:
{
memset(info, 0, sizeof(*info));
if (devp->hd_ireqfreq)
info->hi_ireqfreq =
hpet_time_div(hpetp, devp->hd_ireqfreq);
info->hi_flags =
readq(&timer->hpet_config) & Tn_PER_INT_CAP_MASK;
info->hi_hpet = hpetp->hp_which;
info->hi_timer = devp - hpetp->hp_dev;
break;
}
case HPET_EPI:
v = readq(&timer->hpet_config);
if ((v & Tn_PER_INT_CAP_MASK) == 0) {
err = -ENXIO;
break;
}
devp->hd_flags |= HPET_PERIODIC;
break;
case HPET_DPI:
v = readq(&timer->hpet_config);
if ((v & Tn_PER_INT_CAP_MASK) == 0) {
err = -ENXIO;
break;
}
if (devp->hd_flags & HPET_PERIODIC &&
readq(&timer->hpet_config) & Tn_TYPE_CNF_MASK) {
v = readq(&timer->hpet_config);
v ^= Tn_TYPE_CNF_MASK;
writeq(v, &timer->hpet_config);
}
devp->hd_flags &= ~HPET_PERIODIC;
break;
case HPET_IRQFREQ:
if ((arg > hpet_max_freq) &&
!capable(CAP_SYS_RESOURCE)) {
err = -EACCES;
break;
}
if (!arg) {
err = -EINVAL;
break;
}
devp->hd_ireqfreq = hpet_time_div(hpetp, arg);
}
return err;
}
static long
hpet_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct hpet_info info;
int err;
mutex_lock(&hpet_mutex);
err = hpet_ioctl_common(file->private_data, cmd, arg, &info);
mutex_unlock(&hpet_mutex);
if ((cmd == HPET_INFO) && !err &&
(copy_to_user((void __user *)arg, &info, sizeof(info))))
err = -EFAULT;
return err;
}
#ifdef CONFIG_COMPAT
struct compat_hpet_info {
compat_ulong_t hi_ireqfreq; /* Hz */
compat_ulong_t hi_flags; /* information */
unsigned short hi_hpet;
unsigned short hi_timer;
};
static long
hpet_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct hpet_info info;
int err;
mutex_lock(&hpet_mutex);
err = hpet_ioctl_common(file->private_data, cmd, arg, &info);
mutex_unlock(&hpet_mutex);
if ((cmd == HPET_INFO) && !err) {
struct compat_hpet_info __user *u = compat_ptr(arg);
if (put_user(info.hi_ireqfreq, &u->hi_ireqfreq) ||
put_user(info.hi_flags, &u->hi_flags) ||
put_user(info.hi_hpet, &u->hi_hpet) ||
put_user(info.hi_timer, &u->hi_timer))
err = -EFAULT;
}
return err;
}
#endif
static const struct file_operations hpet_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = hpet_read,
.poll = hpet_poll,
.unlocked_ioctl = hpet_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = hpet_compat_ioctl,
#endif
.open = hpet_open,
.release = hpet_release,
.fasync = hpet_fasync,
.mmap = hpet_mmap,
};
static int hpet_is_known(struct hpet_data *hdp)
{
struct hpets *hpetp;
for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
if (hpetp->hp_hpet_phys == hdp->hd_phys_address)
return 1;
return 0;
}
static struct ctl_table hpet_table[] = {
{
.procname = "max-user-freq",
.data = &hpet_max_freq,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
};
static struct ctl_table_header *sysctl_header;
/*
* Adjustment for when arming the timer with
* initial conditions. That is, main counter
* ticks expired before interrupts are enabled.
*/
#define TICK_CALIBRATE (1000UL)
static unsigned long __hpet_calibrate(struct hpets *hpetp)
{
struct hpet_timer __iomem *timer = NULL;
unsigned long t, m, count, i, flags, start;
struct hpet_dev *devp;
int j;
struct hpet __iomem *hpet;
for (j = 0, devp = hpetp->hp_dev; j < hpetp->hp_ntimer; j++, devp++)
if ((devp->hd_flags & HPET_OPEN) == 0) {
timer = devp->hd_timer;
break;
}
if (!timer)
return 0;
hpet = hpetp->hp_hpet;
t = read_counter(&timer->hpet_compare);
i = 0;
count = hpet_time_div(hpetp, TICK_CALIBRATE);
local_irq_save(flags);
start = read_counter(&hpet->hpet_mc);
do {
m = read_counter(&hpet->hpet_mc);
write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
} while (i++, (m - start) < count);
local_irq_restore(flags);
return (m - start) / i;
}
static unsigned long hpet_calibrate(struct hpets *hpetp)
{
unsigned long ret = ~0UL;
unsigned long tmp;
/*
* Try to calibrate until return value becomes stable small value.
* If SMI interruption occurs in calibration loop, the return value
* will be big. This avoids its impact.
*/
for ( ; ; ) {
tmp = __hpet_calibrate(hpetp);
if (ret <= tmp)
break;
ret = tmp;
}
return ret;
}
int hpet_alloc(struct hpet_data *hdp)
{
u64 cap, mcfg;
struct hpet_dev *devp;
u32 i, ntimer;
struct hpets *hpetp;
struct hpet __iomem *hpet;
static struct hpets *last;
unsigned long period;
unsigned long long temp;
u32 remainder;
/*
* hpet_alloc can be called by platform dependent code.
* If platform dependent code has allocated the hpet that
* ACPI has also reported, then we catch it here.
*/
if (hpet_is_known(hdp)) {
printk(KERN_DEBUG "%s: duplicate HPET ignored\n",
__func__);
return 0;
}
hpetp = kzalloc(struct_size(hpetp, hp_dev, hdp->hd_nirqs),
GFP_KERNEL);
if (!hpetp)
return -ENOMEM;
hpetp->hp_which = hpet_nhpet++;
hpetp->hp_hpet = hdp->hd_address;
hpetp->hp_hpet_phys = hdp->hd_phys_address;
hpetp->hp_ntimer = hdp->hd_nirqs;
for (i = 0; i < hdp->hd_nirqs; i++)
hpetp->hp_dev[i].hd_hdwirq = hdp->hd_irq[i];
hpet = hpetp->hp_hpet;
cap = readq(&hpet->hpet_cap);
ntimer = ((cap & HPET_NUM_TIM_CAP_MASK) >> HPET_NUM_TIM_CAP_SHIFT) + 1;
if (hpetp->hp_ntimer != ntimer) {
printk(KERN_WARNING "hpet: number irqs doesn't agree"
" with number of timers\n");
kfree(hpetp);
return -ENODEV;
}
if (last)
last->hp_next = hpetp;
else
hpets = hpetp;
last = hpetp;
period = (cap & HPET_COUNTER_CLK_PERIOD_MASK) >>
HPET_COUNTER_CLK_PERIOD_SHIFT; /* fs, 10^-15 */
temp = 1000000000000000uLL; /* 10^15 femtoseconds per second */
temp += period >> 1; /* round */
do_div(temp, period);
hpetp->hp_tick_freq = temp; /* ticks per second */
printk(KERN_INFO "hpet%d: at MMIO 0x%lx, IRQ%s",
hpetp->hp_which, hdp->hd_phys_address,
hpetp->hp_ntimer > 1 ? "s" : "");
for (i = 0; i < hpetp->hp_ntimer; i++)
printk(KERN_CONT "%s %d", i > 0 ? "," : "", hdp->hd_irq[i]);
printk(KERN_CONT "\n");
temp = hpetp->hp_tick_freq;
remainder = do_div(temp, 1000000);
printk(KERN_INFO
"hpet%u: %u comparators, %d-bit %u.%06u MHz counter\n",
hpetp->hp_which, hpetp->hp_ntimer,
cap & HPET_COUNTER_SIZE_MASK ? 64 : 32,
(unsigned) temp, remainder);
mcfg = readq(&hpet->hpet_config);
if ((mcfg & HPET_ENABLE_CNF_MASK) == 0) {
write_counter(0L, &hpet->hpet_mc);
mcfg |= HPET_ENABLE_CNF_MASK;
writeq(mcfg, &hpet->hpet_config);
}
for (i = 0, devp = hpetp->hp_dev; i < hpetp->hp_ntimer; i++, devp++) {
struct hpet_timer __iomem *timer;
timer = &hpet->hpet_timers[devp - hpetp->hp_dev];
devp->hd_hpets = hpetp;
devp->hd_hpet = hpet;
devp->hd_timer = timer;
/*
* If the timer was reserved by platform code,
* then make timer unavailable for opens.
*/
if (hdp->hd_state & (1 << i)) {
devp->hd_flags = HPET_OPEN;
continue;
}
init_waitqueue_head(&devp->hd_waitqueue);
}
hpetp->hp_delta = hpet_calibrate(hpetp);
return 0;
}
static acpi_status hpet_resources(struct acpi_resource *res, void *data)
{
struct hpet_data *hdp;
acpi_status status;
struct acpi_resource_address64 addr;
hdp = data;
status = acpi_resource_to_address64(res, &addr);
if (ACPI_SUCCESS(status)) {
hdp->hd_phys_address = addr.address.minimum;
hdp->hd_address = ioremap(addr.address.minimum, addr.address.address_length);
if (!hdp->hd_address)
return AE_ERROR;
if (hpet_is_known(hdp)) {
iounmap(hdp->hd_address);
return AE_ALREADY_EXISTS;
}
} else if (res->type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) {
struct acpi_resource_fixed_memory32 *fixmem32;
fixmem32 = &res->data.fixed_memory32;
hdp->hd_phys_address = fixmem32->address;
hdp->hd_address = ioremap(fixmem32->address,
HPET_RANGE_SIZE);
if (!hdp->hd_address)
return AE_ERROR;
if (hpet_is_known(hdp)) {
iounmap(hdp->hd_address);
return AE_ALREADY_EXISTS;
}
} else if (res->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) {
struct acpi_resource_extended_irq *irqp;
int i, irq;
irqp = &res->data.extended_irq;
for (i = 0; i < irqp->interrupt_count; i++) {
if (hdp->hd_nirqs >= HPET_MAX_TIMERS)
break;
irq = acpi_register_gsi(NULL, irqp->interrupts[i],
irqp->triggering,
irqp->polarity);
if (irq < 0)
return AE_ERROR;
hdp->hd_irq[hdp->hd_nirqs] = irq;
hdp->hd_nirqs++;
}
}
return AE_OK;
}
static int hpet_acpi_add(struct acpi_device *device)
{
acpi_status result;
struct hpet_data data;
memset(&data, 0, sizeof(data));
result =
acpi_walk_resources(device->handle, METHOD_NAME__CRS,
hpet_resources, &data);
if (ACPI_FAILURE(result))
return -ENODEV;
if (!data.hd_address || !data.hd_nirqs) {
if (data.hd_address)
iounmap(data.hd_address);
printk("%s: no address or irqs in _CRS\n", __func__);
return -ENODEV;
}
return hpet_alloc(&data);
}
static const struct acpi_device_id hpet_device_ids[] = {
{"PNP0103", 0},
{"", 0},
};
static struct acpi_driver hpet_acpi_driver = {
.name = "hpet",
.ids = hpet_device_ids,
.ops = {
.add = hpet_acpi_add,
},
};
static struct miscdevice hpet_misc = { HPET_MINOR, "hpet", &hpet_fops };
static int __init hpet_init(void)
{
int result;
result = misc_register(&hpet_misc);
if (result < 0)
return -ENODEV;
sysctl_header = register_sysctl("dev/hpet", hpet_table);
result = acpi_bus_register_driver(&hpet_acpi_driver);
if (result < 0) {
if (sysctl_header)
unregister_sysctl_table(sysctl_header);
misc_deregister(&hpet_misc);
return result;
}
return 0;
}
device_initcall(hpet_init);
/*
MODULE_AUTHOR("Bob Picco <Robert.Picco@hp.com>");
MODULE_LICENSE("GPL");
*/