qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] VersatetilePB: request for comments on PL031 patch


From: Simone
Subject: [Qemu-devel] VersatetilePB: request for comments on PL031 patch
Date: Fri, 15 Jun 2007 18:14:04 +0200
User-agent: Mozilla Thunderbird 1.5.0.12 (X11/20070604)

Hello,

My name is Simone Basso, and I study Computer Science at Politecnico di Torino,
Italy. In the three past weeks I've been working to a project that involves
Qemu, as a part of an exam, together with another student, Gaspare Scherma.


The requested task was to add to Qemu 0.9.0 support for AMBA ARM PL031 RTC chip,
to avoid to have the emulated system (it was debian 4.0) always starting in
1970-01-01, which may be _very_ annoying :-).

The patch that we attach provides the following features:

      - The chip is recognized by the kernel;

      - It is possible to read the current time from the RTC;

      - It is possible to request an interrupt in the next 24 hours;

      - It is possible to acknowledge such interrupt, to lower the IRQ signal.


To implement such patch we've used the following knowledge sources:

      - The implementation of rtc-pl031 driver in the linux kernel;

      - The emulated device hw/pl011.c in qemu sources;

      - Documentation in the linux kernel.

Needless to say, any error in the patch was certainly introduced by us,
and does not depends on them :-).


The reason why we used linux kernel driver as a source of documentation were 
two:

      - We were not able to find complete documentation of the chip[1];

      - Another part of the exam task was to study Linux's RTC framework, so
        we were yet a bit confident with the driver's code.

Having used the Linux kernel as the main source of information, we've also
taken some code from the kernel itself, to implement some bits. Anyway, it
should not be a problem, since the new file hw/pl031.c, added by the patch,
is in the GPL.


I need also to point that, to test linux kernel with our patch, you also need to
patch the kernel itself: infact we've find out a problem in linux's rtc-pl031
interrupt handler. For this reason, a _very_ little patch for linux kernel is
also attached[2].


I write this email to make you, qemu developers, aware of our effert, in the
hope that the patch could be a base for PL031 support with Qemu. If I had missed
some information useful to revise the patch, please tell me.

Regards,
        -Simone Basso

--

Footnotes
---

[1] We've just found www.arm.com/pdfs/DDI0287B_arm926ejs_dev_chip_trm.pdf which
         says something about PL031 on page 222.

[2] The patch is for 2.6.21.3, however I known for sure that it applies without
         any problem to 2.6.18 (I guess that it won't create any problem for any
         kernel >= 2.6.18, but I can't say anything about less than .18).

         We've yet send via email the patch to the proper driver mantainer, who
         said that it seemed ok and that he'll forward it upstream.

This happened a week ago. However, at the moment now, no stable kernel from
         kernel.org has the patch, therefore it is necessary to apply it.







--- linux-2.6.21.3/drivers/rtc/rtc-pl031.c.orig 2007-05-24 23:22:47.000000000 
+0200
+++ linux-2.6.21.3/drivers/rtc/rtc-pl031.c      2007-06-06 22:07:53.000000000 
+0200
@@ -49,9 +49,14 @@
 
 static irqreturn_t pl031_interrupt(int irq, void *dev_id)
 {
-       struct rtc_device *rtc = dev_id;
+       struct pl031_local *ldata = dev_id;
 
-       rtc_update_irq(&rtc->class_dev, 1, RTC_AF);
+       rtc_update_irq(&ldata->rtc->class_dev, 1, RTC_AF);
+
+       /* www.arm.com/pdfs/DDI0287B_arm926ejs_dev_chip_trm.pdf at page
+          222 tells that "The interrupt is cleared by writing any data
+           value to the interrupt clear register RTCICR." */
+       __raw_writel(1, ldata->base + RTC_ICR);
 
        return IRQ_HANDLED;
 }
@@ -173,8 +178,11 @@
                goto out_no_remap;
        }
 
-       if (request_irq(adev->irq[0], pl031_interrupt, IRQF_DISABLED,
-                       "rtc-pl031", ldata->rtc)) {
+       /* Pass ldata to the interrupt handler, so we're able to write
+          the register that clears the interrupt: we need ldata->base
+          for that. */
+       if (request_irq(adev->irq[0], pl031_interrupt, IRQF_DISABLED,
+                       "rtc-pl031", ldata)) {
                ret = -EIO;
                goto out_no_irq;
        }
















--- qemu-0.9.0/hw/versatilepb.c.orig    2007-06-06 21:45:40.000000000 +0200
+++ qemu-0.9.0/hw/versatilepb.c 2007-06-06 21:45:40.000000000 +0200
@@ -214,6 +214,9 @@
        that includes hardware cursor support from the PL111.  */
     pl110_init(ds, 0x10120000, pic, 16, 1);
 
+    /* Add PL031 Real Time Clock. */
+    pl031_init(0x101e8000,pic,10);
+
     /* Memory map for Versatile/PB:  */
     /* 0x10000000 System registers.  */
     /* 0x10001000 PCI controller config registers.  */
--- qemu-0.9.0/vl.h.orig        2007-06-06 21:45:40.000000000 +0200
+++ qemu-0.9.0/vl.h     2007-06-06 21:45:40.000000000 +0200
@@ -1307,6 +1307,9 @@
 /* smc91c111.c */
 void smc91c111_init(NICInfo *, uint32_t, void *, int);
 
+/* pl031.c */
+void pl031_init(uint32_t base, void * pic, int irq);
+
 /* pl110.c */
 void *pl110_init(DisplayState *ds, uint32_t base, void *pic, int irq, int);
 
--- qemu-0.9.0/Makefile.target.orig     2007-06-06 21:45:40.000000000 +0200
+++ qemu-0.9.0/Makefile.target  2007-06-06 21:45:40.000000000 +0200
@@ -399,7 +399,7 @@
 endif
 ifeq ($(TARGET_BASE_ARCH), arm)
 VL_OBJS+= integratorcp.o versatilepb.o ps2.o smc91c111.o arm_pic.o arm_timer.o
-VL_OBJS+= arm_boot.o pl011.o pl050.o pl080.o pl110.o pl190.o
+VL_OBJS+= arm_boot.o pl011.o pl050.o pl080.o pl110.o pl190.o pl031.o
 VL_OBJS+= versatile_pci.o
 VL_OBJS+= arm_gic.o realview.o arm_sysctl.o
 VL_OBJS+= arm-semi.o
--- qemu-0.9.0/hw/pl031.c.orig  2007-06-06 21:56:59.000000000 +0200
+++ qemu-0.9.0/hw/pl031.c       2007-06-06 21:52:15.000000000 +0200
@@ -0,0 +1,480 @@
+/*
+ * ARM AMBA PrimeCell PL031 RTC
+ *
+ * Copyright (c) 2007 Politecnico di Torino, Italy.
+ * Initially written by Simone M.Basso <address@hidden>,
+ *                      Gaspare Scherma <address@hidden>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * References to implement this emulated device were
+ *   linux-2.6.22-rc3/drivers/rtc/{rtc-pl031.c,class.c,interface.c,rtc-lib.c}
+ *   qemu-0.9.0/hw/{versatilepb.c,pl011.c,arm_timer.c,pl*.c}
+ *   http://www.arm.com/pdfs/DDI0287B_arm926ejs_dev_chip_trm.pdf [pag. 222]
+ *
+ * PATCH's ChangeLog
+ *
+ *   version_2   : fixed the problem with interrupts: it was a problem in
+ *                 linux 2.6.21.3 rtc-pl031.c driver. updated the code
+ *                 to have interrupt in next 24 hours working [2007/06/06]
+ *
+ *   version_1   : the patch works, but crashes the kernel when an
+ *                 interrupt is triggered [2007/06/04]
+ */
+
+#define PL031_DEBUG            /* Print some debug messages */
+
+#include"vl.h"
+
+#define RTC_DR         0x00    /* Data read register */
+#define RTC_MR         0x04    /* Match register */
+#define RTC_LR         0x08    /* Data load register */
+#define RTC_CR         0x0c    /* Control register */
+#define RTC_IMSC       0x10    /* Interrupt mask and set register */
+#define RTC_RIS                0x14    /* Raw interrupt status register */
+#define RTC_MIS                0x18    /* Masked interrupt status register */
+#define RTC_ICR                0x1c    /* Interrupt clear register */
+
+struct pl031_state {
+       uint32_t                  base;         /* Base I/O memory      */
+       void                    * pic;          /* Reference to pic     */
+       int                       irq;          /* Assigned IRQ         */
+
+       uint32_t                  boot_time;
+
+       struct {
+               uint32_t          time;
+               uint32_t          enabled;
+               uint32_t          pending;
+
+               QEMUTimer       * _timer;
+       } alarm;
+};
+
+static const unsigned char pl031_id[] = {
+       0x31, 0x10, 0x14, 0x00,                 /* Device ID            */
+       0x0d, 0xf0, 0x05, 0xb1                  /* Cell ID              */
+};
+
+static void pl031_interrupt(void * opaque)
+{
+       struct pl031_state      * state = opaque;
+
+       pic_set_irq_new(state->pic, state->irq, 1);
+#ifdef PL031_DEBUG
+       fprintf(stderr, "qemu: pl031: tick!\n");
+#endif
+}
+
+static inline uint32_t read_current_time(struct pl031_state * state,
+                                               int64_t *ticks)
+{
+       /*
+        * WARNING! state->boot_time is _approx_ the second since Epoch
+        * when the emulation was started. The vm_clock counts in ticks,
+        * as specified in vl.h, from the time when the vm was started.
+        * So, if we sum these two values, we _approx_ the needed time,
+        * which is ok since RTC usually have a resolution of one second.
+        *
+        * state->boot_time is saved during pl031_init().
+        */ 
+       int64_t myTicks = qemu_get_clock(vm_clock);
+       uint32_t cur_time = state->boot_time;
+
+       cur_time += (uint32_t)(myTicks / ticks_per_sec);
+       if (ticks)
+               *ticks = myTicks;
+       return cur_time;
+}
+
+static uint32_t pl031_read(void * opaque, target_phys_addr_t offset)
+{
+       struct pl031_state      * state = opaque;
+
+       offset -= state->base;
+
+       if (offset >= 0xfe0  &&  offset < 0x1000)
+               return pl031_id[(offset - 0xfe0) >> 2];
+       /*
+        * We don't need to (offset >> 2), as it happens with other pl*.c
+        * emulated devices: Infact the defines RTC_* were taken from the
+        * linux kernel drivers/rtc/rtc-pl031.c driver and their values
+        * don't need such shift.
+        */
+       switch (offset) {
+
+       case RTC_DR:
+               return read_current_time(state, NULL);
+               break;
+
+       case RTC_MR:
+               return state->alarm.time;
+               break;
+       case RTC_IMSC:
+               return state->alarm.enabled;
+               break;
+       case RTC_RIS:
+               return state->alarm.pending;
+               break;
+
+       case RTC_LR:
+       case RTC_CR:
+       case RTC_MIS:
+       case RTC_ICR:
+               fprintf(stderr, "qemu: pl031_read: Unexpected offset "
+                               "0x%x\n", offset);
+               break;
+
+       default:
+               cpu_abort (cpu_single_env, "pl031_read: Bad offset "
+                               "0x%x\n", offset);
+       }
+
+       return 0;
+}
+
+static inline void write_current_time(struct pl031_state * state,
+                                       uint32_t value)
+{
+       /*
+        * XXX - We can store the offset between the virtual machine and
+        * the real one in a variable. But, how we can save such value
+        * to survive a machine restart?
+        */ 
+       fprintf(stderr, "qemu: warning: pl031_write: can't change time\n");
+}
+
+static void rtc_time_to_tm(unsigned long time, struct tm *tm);
+static int rtc_tm_to_time(struct tm *tm, unsigned long *time);
+
+/*
+ * WARNING! The Linux kernel, in driver rtc-pl031, sends us a time from a
+ * "strange" struct tm, which has the following parameters
+ *
+ *   alarm.time.tm_mday = -1;
+ *   alarm.time.tm_mon = -1;
+ *   alarm.time.tm_year = -1;
+ *   alarm.time.tm_wday = -1;
+ *   alarm.time.tm_yday = -1;
+ *   alarm.time.tm_isdst = -1;
+ *
+ * And has the right day, hour and minute. Such structure is then processed
+ * using the function that in this file is called ke_mktime(), which leads
+ * to the following value, as result, when day = hour = minute = 0;
+ *
+ * So, we subtract such value to the 32 bit number that we receive to have
+ * the right HHMMSS offset from Epoch.
+ */
+#define OFFSET 2051591296
+
+static inline void save_alarm(struct pl031_state * state, uint32_t value)
+{
+       struct tm alarm, now;
+
+       rtc_time_to_tm(read_current_time(state, NULL), &now);
+       value -= OFFSET;
+       rtc_time_to_tm(value, &alarm);
+       now.tm_hour = alarm.tm_hour;
+       now.tm_min  = alarm.tm_min;
+       now.tm_sec  = alarm.tm_sec;
+
+       rtc_tm_to_time(&now, &state->alarm.time);
+#ifdef PL031_DEBUG
+       fprintf(stderr,"qemu: pl031: alarm at %02d/%02d/%02d, %02d:%02d:%02d\n",
+               now.tm_mday, now.tm_mon +1, now.tm_year + 1900,
+               now.tm_hour, now.tm_min, now.tm_sec);
+#endif
+}
+
+static inline void enable_disable_alarm(struct pl031_state * state,
+                                       int disable)
+{
+       int64_t alarm;
+       uint32_t now;
+
+       if (disable) {
+#ifdef PL031_DEBUG
+               fprintf(stderr, "qemu: pl031: disabled alarm\n");
+#endif
+               qemu_del_timer(state->alarm._timer);
+               return;
+       }
+
+       now = read_current_time(state, &alarm);
+#ifdef PL031_DEBUG
+       fprintf(stderr, "qemu: pl031: current cpu ticks %lld\n", alarm);
+#endif
+       alarm += ticks_per_sec * (state->alarm.time - now);
+#ifdef PL031_DEBUG
+       fprintf(stderr, "qemu: pl031: alarm at cpu ticks %lld\n", alarm);
+#endif
+       qemu_mod_timer(state->alarm._timer, alarm);
+       ++state->alarm.enabled;
+}
+
+static void pl031_write(void * opaque, target_phys_addr_t offset,
+                       uint32_t value)
+{
+       struct pl031_state      * state = opaque;
+
+       offset -= state->base;
+
+       /*
+        * We don't need to (offset >> 2), as it happens with other pl*.c
+        * emulated devices: Infact the defines RTC_* were taken from the
+        * linux kernel drivers/rtc/rtc-pl031.c driver and their values
+        * don't need such shift.
+        */
+       switch (offset) {
+       case RTC_LR:
+               write_current_time(state, value);
+               break;
+
+       case RTC_MR:
+               save_alarm(state, value);
+               break;
+       case RTC_MIS:
+               enable_disable_alarm(state, value);
+               break;
+
+       case RTC_ICR:
+               /* www.arm.com/pdfs/DDI0287B_arm926ejs_dev_chip_trm.pdf at
+                  page 222 tells that "The interrupt is cleared by writing
+                  any data value to the interrupt clear register RTCICR." */
+               pic_set_irq_new(state->pic, state->irq, 0);
+               break;
+
+       case RTC_DR:
+       case RTC_CR:
+       case RTC_IMSC:
+       case RTC_RIS:
+               fprintf(stderr, "qemu: pl031_write: Unexpected offset "
+                               "0x%x\n", offset);
+               break;
+
+       default:
+               cpu_abort (cpu_single_env, "pl031_write: Bad offset "
+                               "0x%x\n", offset);
+       }
+}
+
+static CPUWriteMemoryFunc * pl031_writefn[] = {
+       pl031_write,                            /* To access byte  (index 0) */
+       pl031_write,                            /* To access word  (index 1) */
+       pl031_write                             /* To access dword (index 2) */
+};
+
+static CPUReadMemoryFunc * pl031_readfn[] = {
+       pl031_read,                             /* To access byte  (index 0) */
+       pl031_read,                             /* To access word  (index 1) */
+       pl031_read                              /* To access dword (index 2) */
+};
+
+void pl031_init(uint32_t base, void * pic, int irq)
+{
+       int                       offset;
+       struct pl031_state      * state;
+
+       state = qemu_mallocz(sizeof (*state));
+       if (!state)
+               goto err_no_mem;
+
+       offset = cpu_register_io_memory(0, pl031_readfn, pl031_writefn, state);
+       if (offset == -1)
+               goto err_no_offset;
+
+       cpu_register_physical_memory(base, 0x00000fff, offset);
+
+       state->base = base;
+       state->pic  = pic;
+       state->irq  = irq;
+       /*
+        * Synchronize emulated RTC with host RTC. The precision of a second
+        * will suffice, so it's not important that we have booted some
+        * milliseconds ago: we just need an amount of seconds to add to
+        * the number of ticks since startup.
+        */
+       state->boot_time = time(NULL);
+
+       state->alarm._timer = qemu_new_timer(vm_clock, pl031_interrupt, state);
+       if (!state->alarm._timer)
+               goto err_no_mem;
+       return;
+
+err_no_mem:
+       cpu_abort(cpu_single_env, "pl031_init: Out of memory\n");
+err_no_offset:
+       cpu_abort(cpu_single_env, "pl031_init: Can't register I/O memory\n");
+}
+
+/*
+ * @from-file: linux-2.6.21.3/drivers/rtc/rtc-lib.c
+ *
+ * rtc and date/time utility functions
+ *
+ * Copyright (C) 2005-06 Tower Technologies
+ * Author: Alessandro Zummo <address@hidden>
+ *
+ * based on arch/arm/common/rtctime.c and other bits
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+static unsigned long
+ke_mktime(const unsigned int year0, const unsigned int mon0,
+       const unsigned int day, const unsigned int hour,
+       const unsigned int min, const unsigned int sec);
+
+static const unsigned char rtc_days_in_month[] = {
+       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+static const unsigned short rtc_ydays[2][13] = {
+       /* Normal years */
+       { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+       /* Leap years */
+       { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+};
+
+#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)
+#define LEAP_YEAR(year) ((!(year % 4) && (year % 100)) || !(year % 400))
+
+/*
+ * The number of days in the month.
+ */
+static
+int rtc_month_days(unsigned int month, unsigned int year)
+{
+       return rtc_days_in_month[month] + (LEAP_YEAR(year) && month == 1);
+}
+
+/*
+ * The number of days since January 1. (0 to 365)
+ */
+static
+int rtc_year_days(unsigned int day, unsigned int month, unsigned int year)
+{
+       return rtc_ydays[LEAP_YEAR(year)][month] + day-1;
+}
+
+/*
+ * Convert seconds since 01-01-1970 00:00:00 to Gregorian date.
+ */
+static
+void rtc_time_to_tm(unsigned long time, struct tm *tm)
+{
+       register int days, month, year;
+
+       days = time / 86400;
+       time -= days * 86400;
+
+       /* day of the week, 1970-01-01 was a Thursday */
+       tm->tm_wday = (days + 4) % 7;
+
+       year = 1970 + days / 365;
+       days -= (year - 1970) * 365
+               + LEAPS_THRU_END_OF(year - 1)
+               - LEAPS_THRU_END_OF(1970 - 1);
+       if (days < 0) {
+               year -= 1;
+               days += 365 + LEAP_YEAR(year);
+       }
+       tm->tm_year = year - 1900;
+       tm->tm_yday = days + 1;
+
+       for (month = 0; month < 11; month++) {
+               int newdays;
+
+               newdays = days - rtc_month_days(month, year);
+               if (newdays < 0)
+                       break;
+               days = newdays;
+       }
+       tm->tm_mon = month;
+       tm->tm_mday = days + 1;
+
+       tm->tm_hour = time / 3600;
+       time -= tm->tm_hour * 3600;
+       tm->tm_min = time / 60;
+       tm->tm_sec = time - tm->tm_min * 60;
+}
+
+/*
+ * Does the rtc_time represent a valid date/time?
+ */
+static
+int rtc_valid_tm(struct tm *tm)
+{
+       if (tm->tm_year < 70
+               || ((unsigned)tm->tm_mon) >= 12
+               || tm->tm_mday < 1
+               || tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year + 1900)
+               || ((unsigned)tm->tm_hour) >= 24
+               || ((unsigned)tm->tm_min) >= 60
+               || ((unsigned)tm->tm_sec) >= 60)
+               return 1;
+
+       return 0;
+}
+
+/*
+ * Convert Gregorian date to seconds since 01-01-1970 00:00:00.
+ */
+static
+int rtc_tm_to_time(struct tm *tm, unsigned long *time)
+{
+       *time = ke_mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+                       tm->tm_hour, tm->tm_min, tm->tm_sec);
+       return 0;
+}
+
+/*
+ *  @from-file: linux-2.6.21.3/linux/kernel/time.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *
+ *  This file contains the interface functions for the various
+ *  time related system calls: time, stime, gettimeofday, settimeofday,
+ *                            adjtime
+ */
+
+/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
+ * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
+ * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
+ *
+ * [For the Julian calendar (which was used in Russia before 1917,
+ * Britain & colonies before 1752, anywhere else before 1582,
+ * and is still in use by some communities) leave out the
+ * -year/100+year/400 terms, and add 10.]
+ *
+ * This algorithm was first published by Gauss (I think).
+ *
+ * WARNING: this function will overflow on 2106-02-07 06:28:16 on
+ * machines were long is 32-bit! (However, as time_t is signed, we
+ * will already get problems at other places on 2038-01-19 03:14:08)
+ */
+static unsigned long
+ke_mktime(const unsigned int year0, const unsigned int mon0,
+       const unsigned int day, const unsigned int hour,
+       const unsigned int min, const unsigned int sec)
+{
+       unsigned int mon = mon0, year = year0;
+
+       /* 1..12 -> 11,12,1..10 */
+       if (0 >= (int) (mon -= 2)) {
+               mon += 12;      /* Puts Feb last since it has leap day */
+               year -= 1;
+       }
+
+       return ((((unsigned long)
+                 (year/4 - year/100 + year/400 + 367*mon/12 + day) +
+                 year*365 - 719499
+           )*24 + hour /* now have hours */
+         )*60 + min /* now have minutes */
+       )*60 + sec; /* finally seconds */
+}
+





reply via email to

[Prev in Thread] Current Thread [Next in Thread]