2019-03-14 06:02:48 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2006-03-27 17:16:41 +08:00
|
|
|
/*
|
|
|
|
* RTC subsystem, dev interface
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005 Tower Technologies
|
|
|
|
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
|
|
|
*
|
|
|
|
* based on arch/arm/common/rtctime.c
|
2019-03-14 06:02:48 +08:00
|
|
|
*/
|
2006-03-27 17:16:41 +08:00
|
|
|
|
2013-02-22 08:45:23 +08:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
compat_ioctl: move rtc handling into drivers/rtc/dev.c
We no longer need the rtc compat handling to be in common code, now that
all drivers are either moved to the rtc-class framework, or (rarely)
exist in drivers/char for architectures without compat mode (m68k,
alpha and ia64, respectively).
I checked the list of ioctl commands in drivers, and the ones that are
not already handled are all compatible, again with the one exception of
m68k driver, which implements RTC_PLL_GET and RTC_PLL_SET, but has no
compat mode.
Unlike earlier versions of this patch, I'm now adding a separate
compat_ioctl handler that takes care of RTC_IRQP_READ32/RTC_IRQP_SET32
and treats all other commands as compatible, leaving the native
behavior unchanged.
The old conversion handler also deals with RTC_EPOCH_READ and
RTC_EPOCH_SET, which are not handled in rtc-dev.c but only in a single
device driver (rtc-vr41xx), so I'm adding the compat version in the same
place. I don't expect other drivers to need those commands in the future.
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Reviewed-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
v4: handle RTC_EPOCH_SET32 in rtc_dev_compat_ioctl
v3: handle RTC_IRQP_READ32/RTC_IRQP_SET32 in rtc_dev_compat_ioctl
v2: merge compat handler into ioctl function to avoid the
compat_alloc_user_space() roundtrip, based on feedback
from Al Viro.
2018-08-24 06:11:19 +08:00
|
|
|
#include <linux/compat.h>
|
2006-03-27 17:16:41 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/rtc.h>
|
2017-02-03 02:15:33 +08:00
|
|
|
#include <linux/sched/signal.h>
|
2007-05-08 15:33:27 +08:00
|
|
|
#include "rtc-core.h"
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
static dev_t rtc_devt;
|
|
|
|
|
|
|
|
#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
|
|
|
|
|
|
|
|
static int rtc_dev_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct rtc_device *rtc = container_of(inode->i_cdev,
|
|
|
|
struct rtc_device, char_dev);
|
|
|
|
|
2008-08-13 06:08:41 +08:00
|
|
|
if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))
|
|
|
|
return -EBUSY;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
2007-05-08 15:33:30 +08:00
|
|
|
file->private_data = rtc;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
2017-08-23 08:33:04 +08:00
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
rtc->irq_data = 0;
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
2006-03-27 17:16:41 +08:00
|
|
|
|
2017-08-23 08:33:04 +08:00
|
|
|
return 0;
|
2006-03-27 17:16:41 +08:00
|
|
|
}
|
|
|
|
|
2011-02-12 09:45:40 +08:00
|
|
|
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
|
|
|
/*
|
|
|
|
* Routine to poll RTC seconds field for change as often as possible,
|
|
|
|
* after first RTC_UIE use timer to reduce polling
|
|
|
|
*/
|
|
|
|
static void rtc_uie_task(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct rtc_device *rtc =
|
|
|
|
container_of(work, struct rtc_device, uie_task);
|
|
|
|
struct rtc_time tm;
|
|
|
|
int num = 0;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
|
|
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
if (rtc->stop_uie_polling || err) {
|
|
|
|
rtc->uie_task_active = 0;
|
|
|
|
} else if (rtc->oldsecs != tm.tm_sec) {
|
|
|
|
num = (tm.tm_sec + 60 - rtc->oldsecs) % 60;
|
|
|
|
rtc->oldsecs = tm.tm_sec;
|
2019-03-20 19:59:09 +08:00
|
|
|
rtc->uie_timer.expires = jiffies + HZ - (HZ / 10);
|
2011-02-12 09:45:40 +08:00
|
|
|
rtc->uie_timer_active = 1;
|
|
|
|
rtc->uie_task_active = 0;
|
|
|
|
add_timer(&rtc->uie_timer);
|
|
|
|
} else if (schedule_work(&rtc->uie_task) == 0) {
|
|
|
|
rtc->uie_task_active = 0;
|
|
|
|
}
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
if (num)
|
2011-02-12 10:15:23 +08:00
|
|
|
rtc_handle_legacy_irq(rtc, num, RTC_UF);
|
2011-02-12 09:45:40 +08:00
|
|
|
}
|
2019-03-20 19:59:09 +08:00
|
|
|
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 05:43:17 +08:00
|
|
|
static void rtc_uie_timer(struct timer_list *t)
|
2011-02-12 09:45:40 +08:00
|
|
|
{
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 05:43:17 +08:00
|
|
|
struct rtc_device *rtc = from_timer(rtc, t, uie_timer);
|
2011-02-12 09:45:40 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&rtc->irq_lock, flags);
|
|
|
|
rtc->uie_timer_active = 0;
|
|
|
|
rtc->uie_task_active = 1;
|
|
|
|
if ((schedule_work(&rtc->uie_task) == 0))
|
|
|
|
rtc->uie_task_active = 0;
|
|
|
|
spin_unlock_irqrestore(&rtc->irq_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int clear_uie(struct rtc_device *rtc)
|
|
|
|
{
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
if (rtc->uie_irq_active) {
|
|
|
|
rtc->stop_uie_polling = 1;
|
|
|
|
if (rtc->uie_timer_active) {
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
del_timer_sync(&rtc->uie_timer);
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
rtc->uie_timer_active = 0;
|
|
|
|
}
|
|
|
|
if (rtc->uie_task_active) {
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
flush_scheduled_work();
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
}
|
|
|
|
rtc->uie_irq_active = 0;
|
|
|
|
}
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_uie(struct rtc_device *rtc)
|
|
|
|
{
|
|
|
|
struct rtc_time tm;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
if (!rtc->uie_irq_active) {
|
|
|
|
rtc->uie_irq_active = 1;
|
|
|
|
rtc->stop_uie_polling = 0;
|
|
|
|
rtc->oldsecs = tm.tm_sec;
|
|
|
|
rtc->uie_task_active = 1;
|
|
|
|
if (schedule_work(&rtc->uie_task) == 0)
|
|
|
|
rtc->uie_task_active = 0;
|
|
|
|
}
|
|
|
|
rtc->irq_data = 0;
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rtc_dev_update_irq_enable_emul(struct rtc_device *rtc, unsigned int enabled)
|
|
|
|
{
|
|
|
|
if (enabled)
|
|
|
|
return set_uie(rtc);
|
|
|
|
else
|
|
|
|
return clear_uie(rtc);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(rtc_dev_update_irq_enable_emul);
|
|
|
|
|
|
|
|
#endif /* CONFIG_RTC_INTF_DEV_UIE_EMUL */
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
|
|
|
{
|
2007-10-16 16:28:17 +08:00
|
|
|
struct rtc_device *rtc = file->private_data;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
unsigned long data;
|
|
|
|
ssize_t ret;
|
|
|
|
|
2006-05-02 03:16:16 +08:00
|
|
|
if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
|
2006-03-27 17:16:41 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
add_wait_queue(&rtc->irq_queue, &wait);
|
|
|
|
do {
|
|
|
|
__set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
|
|
data = rtc->irq_data;
|
|
|
|
rtc->irq_data = 0;
|
|
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
|
|
|
|
if (data != 0) {
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
ret = -EAGAIN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
|
|
ret = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
schedule();
|
|
|
|
} while (1);
|
|
|
|
set_current_state(TASK_RUNNING);
|
|
|
|
remove_wait_queue(&rtc->irq_queue, &wait);
|
|
|
|
|
|
|
|
if (ret == 0) {
|
2006-05-02 03:16:16 +08:00
|
|
|
if (sizeof(int) != sizeof(long) &&
|
|
|
|
count == sizeof(unsigned int))
|
|
|
|
ret = put_user(data, (unsigned int __user *)buf) ?:
|
|
|
|
sizeof(unsigned int);
|
|
|
|
else
|
|
|
|
ret = put_user(data, (unsigned long __user *)buf) ?:
|
|
|
|
sizeof(unsigned long);
|
2006-03-27 17:16:41 +08:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-07-03 18:39:46 +08:00
|
|
|
static __poll_t rtc_dev_poll(struct file *file, poll_table *wait)
|
2006-03-27 17:16:41 +08:00
|
|
|
{
|
2007-10-16 16:28:17 +08:00
|
|
|
struct rtc_device *rtc = file->private_data;
|
2006-03-27 17:16:41 +08:00
|
|
|
unsigned long data;
|
|
|
|
|
|
|
|
poll_wait(file, &rtc->irq_queue, wait);
|
|
|
|
|
|
|
|
data = rtc->irq_data;
|
|
|
|
|
2018-02-12 06:34:03 +08:00
|
|
|
return (data != 0) ? (EPOLLIN | EPOLLRDNORM) : 0;
|
2006-03-27 17:16:41 +08:00
|
|
|
}
|
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
static long rtc_dev_ioctl(struct file *file,
|
2019-03-20 19:59:09 +08:00
|
|
|
unsigned int cmd, unsigned long arg)
|
2006-03-27 17:16:41 +08:00
|
|
|
{
|
|
|
|
int err = 0;
|
2007-05-08 15:33:30 +08:00
|
|
|
struct rtc_device *rtc = file->private_data;
|
2006-10-01 14:28:17 +08:00
|
|
|
const struct rtc_class_ops *ops = rtc->ops;
|
2006-03-27 17:16:41 +08:00
|
|
|
struct rtc_time tm;
|
|
|
|
struct rtc_wkalrm alarm;
|
2019-03-20 19:59:09 +08:00
|
|
|
void __user *uarg = (void __user *)arg;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
err = mutex_lock_interruptible(&rtc->ops_lock);
|
|
|
|
if (err)
|
2008-07-30 13:33:30 +08:00
|
|
|
return err;
|
2008-07-24 12:30:33 +08:00
|
|
|
|
2006-11-26 03:09:27 +08:00
|
|
|
/* check that the calling task has appropriate permissions
|
2006-06-25 20:48:20 +08:00
|
|
|
* for certain ioctls. doing this check here is useful
|
|
|
|
* to avoid duplicate code in each driver.
|
|
|
|
*/
|
|
|
|
switch (cmd) {
|
|
|
|
case RTC_EPOCH_SET:
|
|
|
|
case RTC_SET_TIME:
|
|
|
|
if (!capable(CAP_SYS_TIME))
|
2008-07-24 12:30:33 +08:00
|
|
|
err = -EACCES;
|
2006-06-25 20:48:20 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_IRQP_SET:
|
|
|
|
if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
|
2008-07-24 12:30:33 +08:00
|
|
|
err = -EACCES;
|
2006-06-25 20:48:20 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PIE_ON:
|
2007-10-16 16:28:23 +08:00
|
|
|
if (rtc->irq_freq > rtc->max_user_freq &&
|
2019-03-20 19:59:09 +08:00
|
|
|
!capable(CAP_SYS_RESOURCE))
|
2008-07-24 12:30:33 +08:00
|
|
|
err = -EACCES;
|
2006-06-25 20:48:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
if (err)
|
|
|
|
goto done;
|
|
|
|
|
2011-02-03 08:55:19 +08:00
|
|
|
/*
|
2008-02-06 17:38:45 +08:00
|
|
|
* Drivers *SHOULD NOT* provide ioctl implementations
|
|
|
|
* for these requests. Instead, provide methods to
|
|
|
|
* support the following code, so that the RTC's main
|
|
|
|
* features are accessible without using ioctls.
|
|
|
|
*
|
|
|
|
* RTC and alarm times will be in UTC, by preference,
|
|
|
|
* but dual-booting with MS-Windows implies RTCs must
|
|
|
|
* use the local wall clock time.
|
2006-03-27 17:16:41 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case RTC_ALM_READ:
|
2008-07-24 12:30:33 +08:00
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
|
2007-05-08 15:33:30 +08:00
|
|
|
err = rtc_read_alarm(rtc, &alarm);
|
2006-03-27 17:16:41 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
|
2008-07-24 12:30:33 +08:00
|
|
|
err = -EFAULT;
|
|
|
|
return err;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
case RTC_ALM_SET:
|
2008-07-24 12:30:33 +08:00
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
|
2006-03-27 17:16:41 +08:00
|
|
|
if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
alarm.enabled = 0;
|
|
|
|
alarm.pending = 0;
|
|
|
|
alarm.time.tm_wday = -1;
|
|
|
|
alarm.time.tm_yday = -1;
|
|
|
|
alarm.time.tm_isdst = -1;
|
2007-05-08 15:34:07 +08:00
|
|
|
|
|
|
|
/* RTC_ALM_SET alarms may be up to 24 hours in the future.
|
|
|
|
* Rather than expecting every RTC to implement "don't care"
|
|
|
|
* for day/month/year fields, just force the alarm to have
|
|
|
|
* the right values for those fields.
|
|
|
|
*
|
|
|
|
* RTC_WKALM_SET should be used instead. Not only does it
|
|
|
|
* eliminate the need for a separate RTC_AIE_ON call, it
|
|
|
|
* doesn't have the "alarm 23:59:59 in the future" race.
|
|
|
|
*
|
|
|
|
* NOTE: some legacy code may have used invalid fields as
|
|
|
|
* wildcards, exposing hardware "periodic alarm" capabilities.
|
|
|
|
* Not supported here.
|
|
|
|
*/
|
|
|
|
{
|
2015-01-22 10:31:52 +08:00
|
|
|
time64_t now, then;
|
2007-05-08 15:34:07 +08:00
|
|
|
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2015-01-22 10:31:52 +08:00
|
|
|
now = rtc_tm_to_time64(&tm);
|
2007-05-08 15:34:07 +08:00
|
|
|
|
|
|
|
alarm.time.tm_mday = tm.tm_mday;
|
|
|
|
alarm.time.tm_mon = tm.tm_mon;
|
|
|
|
alarm.time.tm_year = tm.tm_year;
|
|
|
|
err = rtc_valid_tm(&alarm.time);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2015-01-22 10:31:52 +08:00
|
|
|
then = rtc_tm_to_time64(&alarm.time);
|
2007-05-08 15:34:07 +08:00
|
|
|
|
|
|
|
/* alarm may need to wrap into tomorrow */
|
|
|
|
if (then < now) {
|
2015-01-22 10:31:52 +08:00
|
|
|
rtc_time64_to_tm(now + 24 * 60 * 60, &tm);
|
2007-05-08 15:34:07 +08:00
|
|
|
alarm.time.tm_mday = tm.tm_mday;
|
|
|
|
alarm.time.tm_mon = tm.tm_mon;
|
|
|
|
alarm.time.tm_year = tm.tm_year;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
return rtc_set_alarm(rtc, &alarm);
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
case RTC_RD_TIME:
|
2008-07-24 12:30:33 +08:00
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
|
2007-05-08 15:33:30 +08:00
|
|
|
err = rtc_read_time(rtc, &tm);
|
2006-03-27 17:16:41 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (copy_to_user(uarg, &tm, sizeof(tm)))
|
2008-07-24 12:30:33 +08:00
|
|
|
err = -EFAULT;
|
|
|
|
return err;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
case RTC_SET_TIME:
|
2008-07-24 12:30:33 +08:00
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
|
2006-03-27 17:16:41 +08:00
|
|
|
if (copy_from_user(&tm, uarg, sizeof(tm)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
return rtc_set_time(rtc, &tm);
|
2006-11-26 03:09:27 +08:00
|
|
|
|
2007-10-16 16:28:15 +08:00
|
|
|
case RTC_PIE_ON:
|
2018-07-25 21:07:09 +08:00
|
|
|
err = rtc_irq_set_state(rtc, 1);
|
2007-10-16 16:28:15 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RTC_PIE_OFF:
|
2018-07-25 21:07:09 +08:00
|
|
|
err = rtc_irq_set_state(rtc, 0);
|
2006-11-26 03:09:27 +08:00
|
|
|
break;
|
|
|
|
|
2009-01-05 04:00:54 +08:00
|
|
|
case RTC_AIE_ON:
|
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
return rtc_alarm_irq_enable(rtc, 1);
|
|
|
|
|
|
|
|
case RTC_AIE_OFF:
|
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
return rtc_alarm_irq_enable(rtc, 0);
|
|
|
|
|
|
|
|
case RTC_UIE_ON:
|
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
return rtc_update_irq_enable(rtc, 1);
|
|
|
|
|
|
|
|
case RTC_UIE_OFF:
|
|
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
return rtc_update_irq_enable(rtc, 0);
|
|
|
|
|
2006-11-26 03:09:27 +08:00
|
|
|
case RTC_IRQP_SET:
|
2018-07-25 21:07:09 +08:00
|
|
|
err = rtc_irq_set_freq(rtc, arg);
|
2007-10-16 16:28:15 +08:00
|
|
|
break;
|
|
|
|
case RTC_IRQP_READ:
|
|
|
|
err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
|
2006-11-26 03:09:27 +08:00
|
|
|
break;
|
|
|
|
|
2006-03-27 17:16:41 +08:00
|
|
|
case RTC_WKALM_SET:
|
2008-07-24 12:30:33 +08:00
|
|
|
mutex_unlock(&rtc->ops_lock);
|
2006-03-27 17:16:41 +08:00
|
|
|
if (copy_from_user(&alarm, uarg, sizeof(alarm)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
return rtc_set_alarm(rtc, &alarm);
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
case RTC_WKALM_RD:
|
2008-07-24 12:30:33 +08:00
|
|
|
mutex_unlock(&rtc->ops_lock);
|
2007-05-08 15:33:30 +08:00
|
|
|
err = rtc_read_alarm(rtc, &alarm);
|
2006-03-27 17:16:41 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (copy_to_user(uarg, &alarm, sizeof(alarm)))
|
2008-07-24 12:30:33 +08:00
|
|
|
err = -EFAULT;
|
|
|
|
return err;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
default:
|
2011-02-03 08:55:19 +08:00
|
|
|
/* Finally try the driver's ioctl interface */
|
|
|
|
if (ops->ioctl) {
|
|
|
|
err = ops->ioctl(rtc->dev.parent, cmd, arg);
|
|
|
|
if (err == -ENOIOCTLCMD)
|
|
|
|
err = -ENOTTY;
|
2019-03-20 19:59:09 +08:00
|
|
|
} else {
|
2011-06-01 14:26:11 +08:00
|
|
|
err = -ENOTTY;
|
2019-03-20 19:59:09 +08:00
|
|
|
}
|
2006-03-27 17:16:41 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2008-07-24 12:30:33 +08:00
|
|
|
done:
|
|
|
|
mutex_unlock(&rtc->ops_lock);
|
2006-03-27 17:16:41 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
compat_ioctl: move rtc handling into drivers/rtc/dev.c
We no longer need the rtc compat handling to be in common code, now that
all drivers are either moved to the rtc-class framework, or (rarely)
exist in drivers/char for architectures without compat mode (m68k,
alpha and ia64, respectively).
I checked the list of ioctl commands in drivers, and the ones that are
not already handled are all compatible, again with the one exception of
m68k driver, which implements RTC_PLL_GET and RTC_PLL_SET, but has no
compat mode.
Unlike earlier versions of this patch, I'm now adding a separate
compat_ioctl handler that takes care of RTC_IRQP_READ32/RTC_IRQP_SET32
and treats all other commands as compatible, leaving the native
behavior unchanged.
The old conversion handler also deals with RTC_EPOCH_READ and
RTC_EPOCH_SET, which are not handled in rtc-dev.c but only in a single
device driver (rtc-vr41xx), so I'm adding the compat version in the same
place. I don't expect other drivers to need those commands in the future.
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Reviewed-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
v4: handle RTC_EPOCH_SET32 in rtc_dev_compat_ioctl
v3: handle RTC_IRQP_READ32/RTC_IRQP_SET32 in rtc_dev_compat_ioctl
v2: merge compat handler into ioctl function to avoid the
compat_alloc_user_space() roundtrip, based on feedback
from Al Viro.
2018-08-24 06:11:19 +08:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
#define RTC_IRQP_SET32 _IOW('p', 0x0c, __u32)
|
|
|
|
#define RTC_IRQP_READ32 _IOR('p', 0x0b, __u32)
|
|
|
|
#define RTC_EPOCH_SET32 _IOW('p', 0x0e, __u32)
|
|
|
|
|
|
|
|
static long rtc_dev_compat_ioctl(struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
|
|
|
struct rtc_device *rtc = file->private_data;
|
|
|
|
void __user *uarg = compat_ptr(arg);
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case RTC_IRQP_READ32:
|
|
|
|
return put_user(rtc->irq_freq, (__u32 __user *)uarg);
|
|
|
|
|
|
|
|
case RTC_IRQP_SET32:
|
|
|
|
/* arg is a plain integer, not pointer */
|
|
|
|
return rtc_dev_ioctl(file, RTC_IRQP_SET, arg);
|
|
|
|
|
|
|
|
case RTC_EPOCH_SET32:
|
|
|
|
/* arg is a plain integer, not pointer */
|
|
|
|
return rtc_dev_ioctl(file, RTC_EPOCH_SET, arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rtc_dev_ioctl(file, cmd, (unsigned long)uarg);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-10-04 06:23:36 +08:00
|
|
|
static int rtc_dev_fasync(int fd, struct file *file, int on)
|
|
|
|
{
|
|
|
|
struct rtc_device *rtc = file->private_data;
|
2019-03-20 19:59:09 +08:00
|
|
|
|
2008-10-04 06:23:36 +08:00
|
|
|
return fasync_helper(fd, file, on, &rtc->async_queue);
|
|
|
|
}
|
|
|
|
|
2006-03-27 17:16:41 +08:00
|
|
|
static int rtc_dev_release(struct inode *inode, struct file *file)
|
|
|
|
{
|
2007-10-16 16:28:17 +08:00
|
|
|
struct rtc_device *rtc = file->private_data;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
2008-10-16 13:03:04 +08:00
|
|
|
/* We shut down the repeating IRQs that userspace enabled,
|
|
|
|
* since nothing is listening to them.
|
|
|
|
* - Update (UIE) ... currently only managed through ioctls
|
|
|
|
* - Periodic (PIE) ... also used through rtc_*() interface calls
|
|
|
|
*
|
|
|
|
* Leave the alarm alone; it may be set to trigger a system wakeup
|
|
|
|
* later, or be used by kernel code, and is a one-shot event anyway.
|
|
|
|
*/
|
2009-01-05 04:00:54 +08:00
|
|
|
|
|
|
|
/* Keep ioctl until all drivers are converted */
|
2008-10-16 13:03:04 +08:00
|
|
|
rtc_dev_ioctl(file, RTC_UIE_OFF, 0);
|
2009-01-05 04:00:54 +08:00
|
|
|
rtc_update_irq_enable(rtc, 0);
|
2018-07-25 21:07:09 +08:00
|
|
|
rtc_irq_set_state(rtc, 0);
|
2008-07-30 13:33:51 +08:00
|
|
|
|
2007-12-05 15:45:05 +08:00
|
|
|
clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);
|
2006-03-27 17:16:41 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:34 +08:00
|
|
|
static const struct file_operations rtc_dev_fops = {
|
2006-03-27 17:16:41 +08:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.llseek = no_llseek,
|
|
|
|
.read = rtc_dev_read,
|
|
|
|
.poll = rtc_dev_poll,
|
2008-07-24 12:30:33 +08:00
|
|
|
.unlocked_ioctl = rtc_dev_ioctl,
|
compat_ioctl: move rtc handling into drivers/rtc/dev.c
We no longer need the rtc compat handling to be in common code, now that
all drivers are either moved to the rtc-class framework, or (rarely)
exist in drivers/char for architectures without compat mode (m68k,
alpha and ia64, respectively).
I checked the list of ioctl commands in drivers, and the ones that are
not already handled are all compatible, again with the one exception of
m68k driver, which implements RTC_PLL_GET and RTC_PLL_SET, but has no
compat mode.
Unlike earlier versions of this patch, I'm now adding a separate
compat_ioctl handler that takes care of RTC_IRQP_READ32/RTC_IRQP_SET32
and treats all other commands as compatible, leaving the native
behavior unchanged.
The old conversion handler also deals with RTC_EPOCH_READ and
RTC_EPOCH_SET, which are not handled in rtc-dev.c but only in a single
device driver (rtc-vr41xx), so I'm adding the compat version in the same
place. I don't expect other drivers to need those commands in the future.
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Reviewed-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
v4: handle RTC_EPOCH_SET32 in rtc_dev_compat_ioctl
v3: handle RTC_IRQP_READ32/RTC_IRQP_SET32 in rtc_dev_compat_ioctl
v2: merge compat handler into ioctl function to avoid the
compat_alloc_user_space() roundtrip, based on feedback
from Al Viro.
2018-08-24 06:11:19 +08:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
.compat_ioctl = rtc_dev_compat_ioctl,
|
|
|
|
#endif
|
2006-03-27 17:16:41 +08:00
|
|
|
.open = rtc_dev_open,
|
|
|
|
.release = rtc_dev_release,
|
|
|
|
.fasync = rtc_dev_fasync,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* insertion/removal hooks */
|
|
|
|
|
2007-05-08 15:33:46 +08:00
|
|
|
void rtc_dev_prepare(struct rtc_device *rtc)
|
2006-03-27 17:16:41 +08:00
|
|
|
{
|
2007-05-08 15:33:27 +08:00
|
|
|
if (!rtc_devt)
|
|
|
|
return;
|
2006-03-27 17:16:41 +08:00
|
|
|
|
|
|
|
if (rtc->id >= RTC_DEV_MAX) {
|
2017-06-02 20:01:37 +08:00
|
|
|
dev_dbg(&rtc->dev, "too many RTC devices\n");
|
2007-05-08 15:33:27 +08:00
|
|
|
return;
|
2006-03-27 17:16:41 +08:00
|
|
|
}
|
|
|
|
|
2007-05-08 15:33:40 +08:00
|
|
|
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
|
2007-05-08 15:33:27 +08:00
|
|
|
|
2011-02-12 09:45:40 +08:00
|
|
|
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
|
|
|
INIT_WORK(&rtc->uie_task, rtc_uie_task);
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 05:43:17 +08:00
|
|
|
timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
|
2011-02-12 09:45:40 +08:00
|
|
|
#endif
|
|
|
|
|
2006-03-27 17:16:41 +08:00
|
|
|
cdev_init(&rtc->char_dev, &rtc_dev_fops);
|
|
|
|
rtc->char_dev.owner = rtc->owner;
|
|
|
|
}
|
|
|
|
|
2007-05-08 15:33:27 +08:00
|
|
|
void __init rtc_dev_init(void)
|
2006-03-27 17:16:41 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
|
2007-05-08 15:33:27 +08:00
|
|
|
if (err < 0)
|
2013-02-22 08:45:23 +08:00
|
|
|
pr_err("failed to allocate char dev region\n");
|
2006-03-27 17:16:41 +08:00
|
|
|
}
|
|
|
|
|
2007-05-08 15:33:27 +08:00
|
|
|
void __exit rtc_dev_exit(void)
|
2006-03-27 17:16:41 +08:00
|
|
|
{
|
2007-05-08 15:33:27 +08:00
|
|
|
if (rtc_devt)
|
|
|
|
unregister_chrdev_region(rtc_devt, RTC_DEV_MAX);
|
2006-03-27 17:16:41 +08:00
|
|
|
}
|