[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [RFC PATCH v2] Add rtc server, rtc-read and rtc-set operations
From: |
Samuel Thibault |
Subject: |
Re: [RFC PATCH v2] Add rtc server, rtc-read and rtc-set operations |
Date: |
Mon, 25 Nov 2024 01:57:44 +0100 |
Hello,
Zhaoming Luo, le mer. 20 nov. 2024 20:06:19 +0800, a ecrit:
> Some coding style fixes. The rtc read operation can calculate day of year
> based on month, day of month and year. Use only one thread for accessing
> rtc to avoid data race.
This looks good. The last step is then to update util-linux to use
it. The code from sys-utils/hwclock-rtc.c (read_hardware_clock_rtc,
set_hardware_clock_rtc) can be moved out to another file, that a hurd
version could use (you'd add a "HURD =" line along the LINUX and BSD
ones in meson.build:130). And you should then be able to play with
hwclock to manipulate the RTC.
Samuel
> * Makefile: add rtc server into the compile chain
> * hurd/pioctl.defs: new file. Interfaces for rtc ioctl operations
> * hurd/rtc.h: new file. Interfaces for rtc device
> * rtc/Makefile: new file. Makefile for rtc server
> * rtc/main.c: new file. Initialisation for rtc translator
> * rtc/mig-mutate.h: new file. Type translation for rtc server
> * rtc/pioctl-ops.c: new file. The rtc server side implementation
> * sutils/MAKEDEV.sh: create rtc device at startup
>
> ---
> Makefile | 3 +-
> hurd/pioctl.defs | 52 +++++++++
> hurd/rtc.h | 45 ++++++++
> rtc/Makefile | 39 +++++++
> rtc/main.c | 104 ++++++++++++++++++
> rtc/mig-mutate.h | 24 +++++
> rtc/pioctl-ops.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++
> sutils/MAKEDEV.sh | 4 +-
> 8 files changed, 537 insertions(+), 2 deletions(-)
> create mode 100644 hurd/pioctl.defs
> create mode 100644 hurd/rtc.h
> create mode 100644 rtc/Makefile
> create mode 100644 rtc/main.c
> create mode 100644 rtc/mig-mutate.h
> create mode 100644 rtc/pioctl-ops.c
>
> diff --git a/Makefile b/Makefile
> index 4d848221..9d9e33c3 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -47,7 +47,8 @@ prog-subdirs = auth proc exec term \
> init \
> devnode \
> eth-multiplexer \
> - shutdown
> + shutdown \
> + rtc
>
> ifeq ($(HAVE_LIBRUMP),yes)
> prog-subdirs += rumpdisk
> diff --git a/hurd/pioctl.defs b/hurd/pioctl.defs
> new file mode 100644
> index 00000000..307d9ee9
> --- /dev/null
> +++ b/hurd/pioctl.defs
> @@ -0,0 +1,52 @@
> +/* Definitions for /dev/rtc ioctls
> +
> + Copyright (C) 2024 Free Software Foundation, Inc.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or
> + modify it under the terms of the GNU General Public License as
> + published by the Free Software Foundation; either version 2, or (at
> + your option) any later version.
> +
> + The GNU Hurd is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +/* Ioctl group 'p'; the subsystem is derived from calculations in
> + hurd/ioctls.defs. */
> +subsystem pioctl 140000;
> +
> +#include <hurd/ioctl_types.defs>
> +
> +import <hurd/rtc.h>;
> +
> +#ifdef PIOCTL_IMPORTS
> +PIOCTL_IMPORTS
> +#endif
> +
> +INTR_INTERFACE
> +
> +/* This is the arg for a struct rtc_time as specified by the
> + definition of _IOT_rtc_time in $(hurd)/hurd/rtc.h. */
> +type rtc_time_t = struct[9] of int;
> +
> +/* TODO: adding other ioctl calls for /dev/rtc in mc146818rtc.h */
> +skip; skip; skip; skip; /* 0 1 2 3 */
> +skip; skip; skip; skip; /* 4 5 6 7 */
> +skip; /* 8 */
> +
> +/* 9 RTC_RD_TIME */
> +routine pioctl_rtc_rd_time (
> + reqport: io_t;
> + out tm: rtc_time_t);
> +
> +/* 10 RTC_SET_TIME */
> +routine pioctl_rtc_set_time (
> + reqport: io_t;
> + tm: rtc_time_t);
> diff --git a/hurd/rtc.h b/hurd/rtc.h
> new file mode 100644
> index 00000000..12ca7531
> --- /dev/null
> +++ b/hurd/rtc.h
> @@ -0,0 +1,45 @@
> +/* GNU Hurd RTC interface
> +
> + Copyright (C) 2024 Free Software Foundation, Inc.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or
> + modify it under the terms of the GNU General Public License as
> + published by the Free Software Foundation; either version 2, or (at
> + your option) any later version.
> +
> + The GNU Hurd is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +#ifndef _RTC_H
> +#define _RTC_H 1
> +
> +#include <hurd/ioctl.h>
> +
> +struct rtc_time
> +{
> + int tm_sec;
> + int tm_min;
> + int tm_hour;
> + int tm_mday;
> + int tm_mon;
> + int tm_year;
> + int tm_wday;
> + int tm_yday;
> + int tm_isdst;
> +};
> +typedef struct rtc_time rtc_time_t;
> +
> +#define _IOT_rtc_time _IOT(_IOTS(int),9,0,0,0,0)
> +
> +#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* Read RTC time. */
> +#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* Set RTC time. */
> +
> +#endif /* rtc.h */
> diff --git a/rtc/Makefile b/rtc/Makefile
> new file mode 100644
> index 00000000..e84d9c14
> --- /dev/null
> +++ b/rtc/Makefile
> @@ -0,0 +1,39 @@
> +# Makefile for rtc server
> +#
> +# Copyright (C) 2024 Free Software Foundation, Inc.
> +#
> +# This file is part of the GNU Hurd.
> +#
> +# The GNU Hurd is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU General Public License as
> +# published by the Free Software Foundation; either version 2, or (at
> +# your option) any later version.
> +#
> +# The GNU Hurd is distributed in the hope that it will be useful, but
> +# WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +# General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; if not, write to the Free Software
> +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +dir := rtc
> +makemode := server
> +
> +SRCS = main.c pioctl-ops.c
> +MIGSRCS = pioctlServer.c
> +
> +OBJS = main.o pioctlServer.o pioctl-ops.o
> +
> +HURDLIBS = trivfs shouldbeinlibc ports
> +
> +target = rtc
> +
> +include ../Makeconf
> +
> +MIGCOMSFLAGS += -prefix rtc_
> +mig-sheader-prefix = rtc_
> +pioctl-MIGSFLAGS = -imacros $(srcdir)/mig-mutate.h
> +
> +rtc_pioctl_S.h pioctlServer.c: mig-mutate.h
> diff --git a/rtc/main.c b/rtc/main.c
> new file mode 100644
> index 00000000..19bf73b9
> --- /dev/null
> +++ b/rtc/main.c
> @@ -0,0 +1,104 @@
> +/* A translator for accessing rtc
> +
> + Copyright (C) 2024 Free Software Foundation, Inc.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or
> + modify it under the terms of the GNU General Public License as
> + published by the Free Software Foundation; either version 2, or (at
> + your option) any later version.
> +
> + The GNU Hurd is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +#include <version.h>
> +
> +#include <error.h>
> +#include <stdbool.h>
> +#include <argp.h>
> +#include <hurd/trivfs.h>
> +#include <hurd/ports.h>
> +#include <hurd/rtc.h>
> +#include <sys/io.h>
> +
> +#include "rtc_pioctl_S.h"
> +
> +const char *argp_program_version = STANDARD_HURD_VERSION (rtc);
> +
> +static struct trivfs_control *rtccntl;
> +
> +int trivfs_fstype = FSTYPE_DEV;
> +int trivfs_fsid = 0;
> +int trivfs_support_read = 1;
> +int trivfs_support_write = 0;
> +int trivfs_support_exec = 0;
> +int trivfs_allow_open = O_READ | O_WRITE;
> +
> +static const struct argp rtc_argp =
> +{ NULL, NULL, NULL, "Real-Time Clock device" };
> +
> +static int
> +demuxer (mach_msg_header_t *inp, mach_msg_header_t *outp)
> +{
> + mig_routine_t routine;
> + if ((routine = rtc_pioctl_server_routine (inp)) ||
> + (routine = NULL, trivfs_demuxer (inp, outp)))
> + {
> + if (routine)
> + (*routine) (inp, outp);
> + return TRUE;
> + }
> + else
> + return FALSE;
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> + error_t err;
> + mach_port_t bootstrap;
> +
> + argp_parse (&rtc_argp, argc, argv, 0, 0, 0);
> +
> + task_get_bootstrap_port (mach_task_self (), &bootstrap);
> + if (bootstrap == MACH_PORT_NULL)
> + error (1, 0, "Must be started as a translator");
> +
> + /* Request for permission to do i/o on port numbers 0x70 and 0x71 for
> + accessing RTC registers. Do this before replying to our parent, so
> + we don't end up saying "I'm ready!" and then immediately exit with
> + an error. */
> + err = ioperm (0x70, 2, true);
> + if (err)
> + error (1, err, "Request IO permission failed");
> +
> + /* Reply to our parent. */
> + err = trivfs_startup (bootstrap, O_NORW, NULL, NULL, NULL, NULL, &rtccntl);
> + mach_port_deallocate (mach_task_self (), bootstrap);
> + if (err)
> + error (1, err, "trivfs_startup failed");
> +
> + /* Launch. */
> + ports_manage_port_operations_one_thread (rtccntl->pi.bucket, demuxer,
> + 2 * 60 * 1000);
> +
> + return 0;
> +}
> +
> +void
> +trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st)
> +{
> +}
> +
> +error_t
> +trivfs_goaway (struct trivfs_control *fsys, int flags)
> +{
> + exit (EXIT_SUCCESS);
> +}
> diff --git a/rtc/mig-mutate.h b/rtc/mig-mutate.h
> new file mode 100644
> index 00000000..ddead5be
> --- /dev/null
> +++ b/rtc/mig-mutate.h
> @@ -0,0 +1,24 @@
> +/* Type translation for rtc operations
> +
> + Copyright (C) 2024 Free Software Foundation, Inc.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or
> + modify it under the terms of the GNU General Public License as
> + published by the Free Software Foundation; either version 2, or (at
> + your option) any later version.
> +
> + The GNU Hurd is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +#define IO_INTRAN trivfs_protid_t trivfs_begin_using_protid (io_t)
> +#define IO_INTRAN_PAYLOAD trivfs_protid_t trivfs_begin_using_protid_payload
> +#define IO_DESTRUCTOR trivfs_end_using_protid (trivfs_protid_t)
> +#define PIOCTL_IMPORTS import "../libtrivfs/mig-decls.h";
> diff --git a/rtc/pioctl-ops.c b/rtc/pioctl-ops.c
> new file mode 100644
> index 00000000..cb347db0
> --- /dev/null
> +++ b/rtc/pioctl-ops.c
> @@ -0,0 +1,268 @@
> +/* Server side implementation for rtc server
> +
> + Copyright (C) 2024 Free Software Foundation, Inc.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or
> + modify it under the terms of the GNU General Public License as
> + published by the Free Software Foundation; either version 2, or (at
> + your option) any later version.
> +
> + The GNU Hurd is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +/* This implementation is largely based on sys-utils/hwclock-cmos.c from
> + util-linux. */
> +
> +/* A struct tm has int fields (it is defined in POSIX)
> + tm_sec 0-59, 60 or 61 only for leap seconds
> + tm_min 0-59
> + tm_hour 0-23
> + tm_mday 1-31
> + tm_mon 0-11
> + tm_year number of years since 1900
> + tm_wday 0-6, 0=Sunday
> + tm_yday 0-365
> + tm_isdst >0: yes, 0: no, <0: unknown */
> +
> +#include "rtc_pioctl_S.h"
> +#include <hurd/rtc.h>
> +#include <hurd/hurd_types.h>
> +#include <sys/io.h>
> +#include <stdbool.h>
> +
> +/* Conversions to and from RTC internal format. */
> +#define BCD_TO_BIN(val) ((val)=((val)&15) + (((val)>>4)&15)*10 + \
> + ((val)>>8)*100)
> +#define BIN_TO_BCD(val) ((val)=(((val)/100)<<8) + \
> + ((((val)/10)%10)<<4) + (val)%10)
> +
> +/* POSIX uses 1900 as epoch for a struct tm, and 1970 for a time_t. */
> +#define TM_EPOCH 1900
> +
> +#define CLOCK_CTL_ADDR 0x70
> +#define CLOCK_DATA_ADDR 0x71
> +
> +static int month_to_days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,
> 31};
> +
> +static inline unsigned char
> +cmos_read (unsigned char reg)
> +{
> + outb_p (reg, CLOCK_CTL_ADDR);
> + return inb_p (CLOCK_DATA_ADDR);
> +}
> +
> +static inline void
> +cmos_write (unsigned char reg, unsigned char val)
> +{
> + outb_p (reg, CLOCK_CTL_ADDR);
> + outb_p (val, CLOCK_DATA_ADDR);
> +}
> +
> +static inline int
> +cmos_clock_busy (void)
> +{
> + /* Poll bit 7 (UIP) of Control Register A. */
> + return (cmos_read (10) & 0x80);
> +}
> +
> +/* Calculate day of year based on month, day of month, and year. The value
> + it returns is in binary format. The values of mon, mday, and year are in
> + binary format as well. */
> +static int
> +calculate_yday (int mon, int mday, int year)
> +{
> + int yday = 0;
> + int i;
> +
> + for (i = 0; i < mon; ++i)
> + yday += month_to_days[i];
> + yday += mday;
> +
> + /* If the year is a leap year and February is added in yday, one more day
> + is added because February day number is 29 in a leap year. */
> + if ((((TM_EPOCH + year) % 4) == 0) && mon > 1)
> + yday++;
> +
> + /* Convert the range of yday from [1,366] to [0,365]. */
> + yday--;
> +
> + return yday;
> +}
> +
> +/* 9 RTC_RD_TIME -- Read RTC time. */
> +kern_return_t
> +rtc_S_pioctl_rtc_rd_time (struct trivfs_protid *cred, struct rtc_time *tm)
> +{
> + unsigned char status = 0;
> + unsigned char pmbit = 0;
> + int time_passed_in_milliseconds = 0;
> + bool read_rtc_successfully = false;
> +
> + if (!cred)
> + return EOPNOTSUPP;
> + if (!(cred->po->openmodes & O_READ))
> + return EBADF;
> +
> + /* When we wait for 100 ms (it takes too long), we exit with error. */
> + while (time_passed_in_milliseconds < 100)
> + {
> + if (!cmos_clock_busy ())
> + {
> + tm->tm_sec = cmos_read (0);
> + tm->tm_min = cmos_read (2);
> + tm->tm_hour = cmos_read (4);
> + tm->tm_wday = cmos_read (6);
> + tm->tm_mday = cmos_read (7);
> + tm->tm_mon = cmos_read (8);
> + tm->tm_year = cmos_read (9);
> + status = cmos_read (11);
> + /* Unless the clock changed while we were reading, consider this
> + a good clock read. */
> + if (tm->tm_sec == cmos_read (0))
> + {
> + read_rtc_successfully = true;
> + break;
> + }
> + }
> + usleep (1000);
> + time_passed_in_milliseconds++;
> + }
> +
> + if (!read_rtc_successfully)
> + return EBUSY;
> +
> + /* If the data we just read is in BCD format, convert it to binary
> + format. */
> + if (!(status & 0x04))
> + {
> + BCD_TO_BIN (tm->tm_sec);
> + BCD_TO_BIN (tm->tm_min);
> + pmbit = (tm->tm_hour & 0x80);
> + tm->tm_hour &= 0x7f;
> + BCD_TO_BIN (tm->tm_hour);
> + BCD_TO_BIN (tm->tm_wday);
> + BCD_TO_BIN (tm->tm_mday);
> + BCD_TO_BIN (tm->tm_mon);
> + BCD_TO_BIN (tm->tm_year);
> + }
> +
> + /* We don't use the century byte of the Hardware Clock since we
> + don't know its address (usually 50 or 55). Here, we follow the
> + advice of the X/Open Base Working Group: "if century is not
> + specified, then values in the range [69-99] refer to years in the
> + twentieth century (1969 to 1999 inclusive), and values in the
> + range [00-68] refer to years in the twenty-first century (2000 to
> + 2068 inclusive)". */
> + tm->tm_wday -= 1;
> + tm->tm_mon -= 1;
> + if (tm->tm_year < 69)
> + tm->tm_year += 100;
> +
> + /* Calculate day of year. */
> + tm->tm_yday = calculate_yday (tm->tm_mon, tm->tm_mday, tm->tm_year);
> +
> + if (pmbit)
> + {
> + tm->tm_hour += 12;
> + if (tm->tm_hour == 24)
> + tm->tm_hour = 0;
> + }
> +
> + /* We don't know whether it's daylight. */
> + tm->tm_isdst = -1;
> +
> + return KERN_SUCCESS;
> +}
> +
> +/* 10 RTC_SET_TIME -- Set RTC time. */
> +kern_return_t
> +rtc_S_pioctl_rtc_set_time (struct trivfs_protid *cred, struct rtc_time tm)
> +{
> + unsigned char save_control, save_freq_select, pmbit = 0;
> +
> + if (!cred)
> + return EOPNOTSUPP;
> + if (!(cred->po->openmodes & O_WRITE))
> + return EBADF;
> +
> + /* CMOS byte 10 (clock status register A) has 3 bitfields:
> + bit 7: 1 if data invalid, update in progress (read-only bit)
> + (this is raised 224 us before the actual update starts)
> + 6-4 select base frequency
> + 010: 32768 Hz time base (default)
> + 111: reset
> + all other combinations are manufacturer-dependent
> + (e.g.: DS1287: 010 = start oscillator, anything else = stop)
> + 3-0 rate selection bits for interrupt
> + 0000 none (may stop RTC)
> + 0001, 0010 give same frequency as 1000, 1001
> + 0011 122 microseconds (minimum, 8192 Hz)
> + .... each increase by 1 halves the frequency, doubles the period
> + 1111 500 milliseconds (maximum, 2 Hz)
> + 0110 976.562 microseconds (default 1024 Hz). */
> +
> + /* Tell the clock it's being set. */
> + save_control = cmos_read (11);
> + cmos_write (11, (save_control | 0x80));
> + /* Stop and reset prescaler. */
> + save_freq_select = cmos_read (10);
> + cmos_write (10, (save_freq_select | 0x70));
> +
> + tm.tm_year %= 100;
> + tm.tm_mon += 1;
> + tm.tm_wday += 1;
> +
> + /* 12hr mode; the default is 24hr mode. */
> + if (!(save_control & 0x02))
> + {
> + if (tm.tm_hour == 0)
> + tm.tm_hour = 24;
> + if (tm.tm_hour > 12)
> + {
> + tm.tm_hour -= 12;
> + pmbit = 0x80;
> + }
> + }
> +
> + /* BCD mode - the default. */
> + if (!(save_control & 0x04))
> + {
> + BIN_TO_BCD (tm.tm_sec);
> + BIN_TO_BCD (tm.tm_min);
> + BIN_TO_BCD (tm.tm_hour);
> + BIN_TO_BCD (tm.tm_wday);
> + BIN_TO_BCD (tm.tm_mday);
> + BIN_TO_BCD (tm.tm_mon);
> + BIN_TO_BCD (tm.tm_year);
> + }
> +
> + cmos_write (0, tm.tm_sec);
> + cmos_write (2, tm.tm_min);
> + cmos_write (4, tm.tm_hour | pmbit);
> + cmos_write (6, tm.tm_wday);
> + cmos_write (7, tm.tm_mday);
> + cmos_write (8, tm.tm_mon);
> + cmos_write (9, tm.tm_year);
> +
> + /* The kernel sources, linux/arch/i386/kernel/time.c, have the
> + following comment:
> +
> + The following flags have to be released exactly in this order,
> + otherwise the DS12887 (popular MC146818A clone with integrated
> + battery and quartz) will not reset the oscillator and will not
> + update precisely 500 ms later. You won't find this mentioned in
> + the Dallas Semiconductor data sheets, but who believes data
> + sheets anyway ... -- Markus Kuhn. */
> + cmos_write (11, save_control);
> + cmos_write (10, save_freq_select);
> +
> + return KERN_SUCCESS;
> +}
> diff --git a/sutils/MAKEDEV.sh b/sutils/MAKEDEV.sh
> index c3d7d112..c1ef4f92 100644
> --- a/sutils/MAKEDEV.sh
> +++ b/sutils/MAKEDEV.sh
> @@ -120,10 +120,12 @@ mkdev() {
> ;;
>
> std)
> - mkdev console tty random urandom null zero full fd time mem klog shm
> + mkdev console tty random urandom null zero full fd time mem klog shm rtc
> ;;
> console|com[0-9])
> st $I root 600 c /hurd/term ${DEVDIR}/$I device $I;;
> + rtc)
> + st $I root 644 c /hurd/rtc;;
> vcs)
> st $I root 600 d /hurd/console;;
> tty[1-9][0-9]|tty[1-9])
> --
> 2.47.0
>
>
--
Samuel
<b> j'en ai parlé à xavier, il n'est pas interdit qu'il le change un jour
-+- #sos - a le bras long vers le chameau -+-