Skip to content

Commit

Permalink
[PATCH] HPET: make frequency calculations 32 bit safe
Browse files Browse the repository at this point in the history
On 32-bit architectures, the multiplication in the argument for
hpet_time_div() often overflows.  In the typical case of a 14.32 MHz timer,
this happens when the desired frequency exceeds 61 Hz.

To avoid this multiplication, we can precompute and store the hardware
timer frequency, instead of the period, in the device structure, which
leaves us with a simple division when computing the number of timer ticks.

As a side effect, this also removes a theoretical bug where the timer
interpolator's frequency would be computed as a 32-bit value even if the
HPET frequency is greater than 2^32 Hz (the HPET spec allows up to 10 GHz).

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
  • Loading branch information
Clemens Ladisch authored and Linus Torvalds committed Oct 31, 2005
1 parent 9090e6d commit ba3f213
Showing 1 changed file with 19 additions and 13 deletions.
32 changes: 19 additions & 13 deletions drivers/char/hpet.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct hpets {
struct hpet __iomem *hp_hpet;
unsigned long hp_hpet_phys;
struct time_interpolator *hp_interpolator;
unsigned long hp_period;
unsigned long long hp_tick_freq;
unsigned long hp_delta;
unsigned int hp_ntimer;
unsigned int hp_which;
Expand Down Expand Up @@ -427,12 +427,14 @@ static int hpet_ioctl_ieon(struct hpet_dev *devp)
return 0;
}

static inline unsigned long hpet_time_div(unsigned long dis)
/* converts Hz to number of timer ticks */
static inline unsigned long hpet_time_div(struct hpets *hpets,
unsigned long dis)
{
unsigned long long m = 1000000000000000ULL;
unsigned long long m;

m = hpets->hp_tick_freq + (dis >> 1);
do_div(m, dis);

return (unsigned long)m;
}

Expand Down Expand Up @@ -480,7 +482,7 @@ hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg, int kernel)
{
struct hpet_info info;

info.hi_ireqfreq = hpet_time_div(hpetp->hp_period *
info.hi_ireqfreq = hpet_time_div(hpetp,
devp->hd_ireqfreq);
info.hi_flags =
readq(&timer->hpet_config) & Tn_PER_INT_CAP_MASK;
Expand Down Expand Up @@ -524,7 +526,7 @@ hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg, int kernel)
break;
}

devp->hd_ireqfreq = hpet_time_div(hpetp->hp_period * arg);
devp->hd_ireqfreq = hpet_time_div(hpetp, arg);
}

return err;
Expand Down Expand Up @@ -713,7 +715,7 @@ static void hpet_register_interpolator(struct hpets *hpetp)
ti->source = TIME_SOURCE_MMIO64;
ti->shift = 10;
ti->addr = &hpetp->hp_hpet->hpet_mc;
ti->frequency = hpet_time_div(hpets->hp_period);
ti->frequency = hpetp->hp_tick_freq;
ti->drift = HPET_DRIFT;
ti->mask = -1;

Expand Down Expand Up @@ -750,7 +752,7 @@ static unsigned long hpet_calibrate(struct hpets *hpetp)
t = read_counter(&timer->hpet_compare);

i = 0;
count = hpet_time_div(hpetp->hp_period * TICK_CALIBRATE);
count = hpet_time_div(hpetp, TICK_CALIBRATE);

local_irq_save(flags);

Expand All @@ -775,7 +777,8 @@ int hpet_alloc(struct hpet_data *hdp)
size_t siz;
struct hpet __iomem *hpet;
static struct hpets *last = (struct hpets *)0;
unsigned long ns;
unsigned long ns, period;
unsigned long long temp;

/*
* hpet_alloc can be called by platform dependent code.
Expand Down Expand Up @@ -825,8 +828,12 @@ int hpet_alloc(struct hpet_data *hdp)

last = hpetp;

hpetp->hp_period = (cap & HPET_COUNTER_CLK_PERIOD_MASK) >>
HPET_COUNTER_CLK_PERIOD_SHIFT;
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,
Expand All @@ -835,8 +842,7 @@ int hpet_alloc(struct hpet_data *hdp)
printk("%s %d", i > 0 ? "," : "", hdp->hd_irq[i]);
printk("\n");

ns = hpetp->hp_period; /* femptoseconds, 10^-15 */
ns /= 1000000; /* convert to nanoseconds, 10^-9 */
ns = period / 1000000; /* convert to nanoseconds, 10^-9 */
printk(KERN_INFO "hpet%d: %ldns tick, %d %d-bit timers\n",
hpetp->hp_which, ns, hpetp->hp_ntimer,
cap & HPET_COUNTER_SIZE_MASK ? 64 : 32);
Expand Down

0 comments on commit ba3f213

Please sign in to comment.