Writing kernel code that can talk to 64 bit processes (was Re: Mac OS X 10.4 (Tiger) compilation errors ( GCC v3.3 ) with X Code 2.0 tool)
Writing kernel code that can talk to 64 bit processes (was Re: Mac OS X 10.4 (Tiger) compilation errors ( GCC v3.3 ) with X Code 2.0 tool)
- Subject: Writing kernel code that can talk to 64 bit processes (was Re: Mac OS X 10.4 (Tiger) compilation errors ( GCC v3.3 ) with X Code 2.0 tool)
- From: Terry Lambert <email@hidden>
- Date: Tue, 9 Aug 2005 23:32:03 -0700
In general, the approach you need to take to support 64 bit processes
talking to your kexts depends on the method of passing information
into the kernel:
o If you are using Mach messages, "mig" knows how to generate the
correct converter code.
o If you are using ioctl()'s, then you will have to do it manually.
My general recommendation is to define a pseudo-device, and just let
rad/write/mmap(0 take care of it for you; if you insist on moving the
data yourself, your best bet is via ioctl()'s.
To take an ioctl as an example, there are two methods available for
passing data into an ioctl(); you either pass in your data by value,
which only works portably with 32 bit or smaller data types (integer,
short, char, but not long or pointer), or you pass it in by
reference. If passing by reference, the passed in value might be a
simple address, or it might be the address of a structure.
For structure containing large objects, you need to define a
structure that looks the same in 32 bits as in 64 bits, so that you
can copy into/out of it. Here is an example from termios for the
TCGETA ioctl:
In termios.h, we have this structure:
struct termios {
tcflag_t c_iflag; /* input flags */
tcflag_t c_oflag; /* output flags */
tcflag_t c_cflag; /* control flags */
tcflag_t c_lflag; /* local flags */
cc_t c_cc[NCCS]; /* control chars */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
This is a size variant structure for two reasons:
1) tcflag_t is defined as unsigned long
2) speed_t is defined as unsigned long
This is size variant because the 32 bit world is ILP32 - integer,
long, and pointers are 32 bits - and the 64 bit world is LP64 -
integeras are 32 bit, and longs and pointers are 64 bits.
You don't need to worry about cc_t, as it's defined as unsigned char.
So this structure is at least size variant, and possibly also
alignment variant. Why didn't we just change
the definitions of tcflag_t and speed_t to be int's? The main reason
is symbol decoration in C++; if either of these types were used in C+
+ code as parameters or return values, then you would lose binary
compatibility by having different symbol decoration if they were
changed from long to int. This is a long standing problem in C++
compilation environaments, and pretty much everyone has he problem,
because at the time that C++ was introduced, the symbol table format
was not expanded to include type information separately from symbol
name information. So we live with it. Sometimes it's handy: for
time_t, for example, the 64 bit version doesn't have the Y2038
problem with the UNIX system clock rollover.
So how do you write this ioctl() to work with both 64 bit and 32 bit
versions of long?
You define two more types and another structure:
typedef unsigned long long user_tcflag_t;
typedef unsigned long long user_speed_t;
#if __DARWIN_ALIGN_NATURAL
#pragma options align=natural
#endif
struct user_termios {
user_tcflag_t c_iflag; /* input flags */
user_tcflag_t c_oflag; /* output flags */
user_tcflag_t c_cflag; /* control flags */
user_tcflag_t c_lflag; /* local flags */
cc_t c_cc[NCCS]; /* control chars */
user_speed_t c_ispeed; /* input speed */
user_speed_t c_ospeed; /* output speed */
};
#if __DARWIN_ALIGN_NATURAL
#pragma options align=reset
#endif
The pragma's ensure that the packing will be the same as in user
space in a 64 bit process. Then you write your ioctl code that lives
in the 32 bit kernel:
case TIOCGETA: /* get termios struct */
case TIOCGETA_64: { /* get termios struct */
if (IS_64BIT_PROCESS(p)) {
termios32to64(&tp->t_termios, (struct
user_termios *)data);
} else {
bcopy(&tp->t_termios, data, sizeof(struct
termios));
}
break;
-- one of the decisions you have to make in doing this is whether
your kernel internal representation is going to be default for 32 bit
processes, or default for 64 bit processes. Here, we picked 32 bit
processes, so that people would not attempt to squirrel away
information in the additional space, which would later not be visible
to 32 bit programs.
So tp->t_termios is of type struct termios, not type struct
user_termios.
We have also written a conversion function which converts between
internal and external representation (in this case, we do a field-by-
field assignment with proper type conversion to ensure we are not
bitten by sign extension). The function for this ioctl(), which
obtains data and copies it to user space, is to copy it out, so we
need to convert from internal (32 bit) to external (64 bit)
representation.
The way this ends up working is that the structure size is encoded
into the cmd parameter - this is also why, when using ioctl()'s, you
rarely have to copy the data yourself, unless you are trying to move
a large amount of it, at which point it's better to be a device or
pesudodevice and jusst use read/write - and so we have separate
TIOCGETA and TIOCGETA_64 entry points.
#define TIOCGETA _IOR('t', 19, struct termios) /* get
termios struct */
#ifdef KERNEL
#define TIOCGETA_64 _IOR('t', 19, struct user_termios)
#endif
Note that the TIOCGETA_64 is only defined for kernel use: it's not
implemented in 32 bit processes; instead, it's only implemented in
the 32 bit kernel, which only uses it to communicate with 64 bit
processes.
When the TIOCGETA is defined in the 64 bit process, the value changes
based on the size of the structure changing, and so it's seen by the
user process as TIOCGETA, but by the kernel as TIOCGETA_64.
If you were only doing a pointer, for a 64 bit process, you would
take either a 64 bit value that you would bcopy into a user_addr_t,
or for a 32 bit process, you would bcopy a 32 bit value into a void *
(the ioctl takes care of making the data available, but not about
changing the size of it one way or another).
Then for the 32 bit process case, whether your pointer was in a
structure or in a simple void *, you would convert it to a
user_addr_t using:
ptr64 = CAST_USER_ADDR_T(ptr_32)
....and then use that as your argument to copyin/copyout.
Obviously, if you are copying differently alighned/paced data items,
then you could convert the 32 bit pointer in place in the first
argument to the copyin() call itself, etc..
You can see the code that's described in this example at:
TIOCGETA/TIOCGETA64:
http://darwinsource.opendarwin.org/10.4.2/xnu-792.2.4/bsd/sys/
ttycom.h
struct termios/struct user_termios:
http://darwinsource.opendarwin.org/10.4.2/xnu-792.2.4/bsd/sys/
termios.h
ttioctl() code itself:
http://darwinsource.opendarwin.org/10.4.2/xnu-792.2.4/bsd/kern/
tty.c
-- Terry
On Aug 9, 2005, at 1:59 AM, Karunakar Reddy G wrote:
Thank you Andrew, Terry.
Can you give some example how to use of them, as I am facing
problem using structures,
In my case structure pointer comes from the application to driver ,
previously I handled using void *.
How to handle now since I cannot convet them as user_addr_t to use
for copyin() and copyout() functions.
Regards,
Karunakar
If you are using copyin()/copyout()/copyinstr() directly, rather than
using uiomove()/uiomove64(), then you will need to make changes to
your code.
The reason for the change to the parameters was to support copying in/
out data with 64 bit user space addresses for 64 bit processes.
Without the change, you would be limited to only passing data from
the first 4G of process memory into the kernel when using a 64 bit
process, and that wouldn't really be useful.
-- Terry
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Darwin-kernel mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
40apple.com
This email sent to email@hidden
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Darwin-kernel mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden