Discussion:
Kernel module: return a number from a device
(too old to reply)
Rocky Hotas
2024-04-06 20:27:12 UTC
Permalink
Hello!
I'm trying to write a simple kernel module, using as a model the example
in

<https://docs.freebsd.org/en/books/arch-handbook/driverbasics/>

I am a newbie. My module should be simpler than the one in the link: it
should just create a read-only /dev/rolld file; each time it is read
by the user (for example through `cat'), the file should provide a
random number mod d_size. So, the "output" should always be 1 character.

I modified the echo kernel module presented in the link. My module
can successfully be loaded into the kernel and the device is created,
but if I run as a user `cat /dev/rolld':

$ cat /dev/rolld
Opened device "rolld" successfully.

and it hangs, giving no more output and without generating an error.

May be this due to the fact that uiomove receives a pointer &random_out,
which is a pointer to a uint32_t instead of for example a char? (And if
this is the issue, how to convert a uint32_t to char inside the kernel?)

Or is there some other error that I made?

I paste my code below.

Bye!

Rocky



#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/libkern.h>

static d_open_t rolld_open;
static d_close_t rolld_close;
static d_read_t rolld_read;

static struct cdevsw rolld_cdevsw = {
.d_version = D_VERSION,
.d_open = rolld_open,
.d_close = rolld_close,
.d_read = rolld_read,
.d_name = "rolld",
};

/* vars */
static struct cdev *rolld_dev;
static uint32_t d_size = 6;

static int
rolld_loader(struct module *m __unused, int what, void *arg __unused)
{
int error = 0;

switch (what) {
case MOD_LOAD: /* kldload */
error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK,
&rolld_dev,
&rolld_cdevsw,
0,
UID_ROOT,
GID_WHEEL,
0444,
"rolld");
if (error != 0)
break;

printf("Roll device loaded.\n");
break;
case MOD_UNLOAD:
destroy_dev(rolld_dev);
printf("Roll device unloaded.\n");
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}

static int
rolld_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused,
struct thread *td __unused)
{
int error = 0;

uprintf("Opened device \"rolld\" successfully.\n");
return (error);
}

static int
rolld_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused,
struct thread *td __unused)
{
uprintf("Closing device \"rolld\".\n");
return (0);
}

static int
rolld_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
{
uint32_t random_out;
uint32_t random_item;
int error;

random_item = arc4random();
random_out = random_item % d_size;

if ((error = uiomove(&random_out, 1, uio)) != 0)
uprintf("uiomove failed!\n");

return (error);
}

DEV_MODULE(rolld, rolld_loader, NULL);


--
Posted automagically by a mail2news gateway at muc.de e.V.
Please direct questions, flames, donations, etc. to news-***@muc.de
Warner Losh
2024-04-06 20:39:26 UTC
Permalink
When this happens, hit ^t (control t). That will give a traceback of the
call stack which may help you track down where it is hanging (most likely
something is sleeping waiting for an event).

Warner
Post by Rocky Hotas
Hello!
I'm trying to write a simple kernel module, using as a model the example
in
<https://docs.freebsd.org/en/books/arch-handbook/driverbasics/>
I am a newbie. My module should be simpler than the one in the link: it
should just create a read-only /dev/rolld file; each time it is read
by the user (for example through `cat'), the file should provide a
random number mod d_size. So, the "output" should always be 1 character.
I modified the echo kernel module presented in the link. My module
can successfully be loaded into the kernel and the device is created,
$ cat /dev/rolld
Opened device "rolld" successfully.
and it hangs, giving no more output and without generating an error.
May be this due to the fact that uiomove receives a pointer &random_out,
which is a pointer to a uint32_t instead of for example a char? (And if
this is the issue, how to convert a uint32_t to char inside the kernel?)
Or is there some other error that I made?
I paste my code below.
Bye!
Rocky
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/libkern.h>
static d_open_t rolld_open;
static d_close_t rolld_close;
static d_read_t rolld_read;
static struct cdevsw rolld_cdevsw = {
.d_version = D_VERSION,
.d_open = rolld_open,
.d_close = rolld_close,
.d_read = rolld_read,
.d_name = "rolld",
};
/* vars */
static struct cdev *rolld_dev;
static uint32_t d_size = 6;
static int
rolld_loader(struct module *m __unused, int what, void *arg __unused)
{
int error = 0;
switch (what) {
case MOD_LOAD: /* kldload */
error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK,
&rolld_dev,
&rolld_cdevsw,
0,
UID_ROOT,
GID_WHEEL,
0444,
"rolld");
if (error != 0)
break;
printf("Roll device loaded.\n");
break;
destroy_dev(rolld_dev);
printf("Roll device unloaded.\n");
break;
error = EOPNOTSUPP;
break;
}
return (error);
}
static int
rolld_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused,
struct thread *td __unused)
{
int error = 0;
uprintf("Opened device \"rolld\" successfully.\n");
return (error);
}
static int
rolld_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused,
struct thread *td __unused)
{
uprintf("Closing device \"rolld\".\n");
return (0);
}
static int
rolld_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
{
uint32_t random_out;
uint32_t random_item;
int error;
random_item = arc4random();
random_out = random_item % d_size;
if ((error = uiomove(&random_out, 1, uio)) != 0)
uprintf("uiomove failed!\n");
return (error);
}
DEV_MODULE(rolld, rolld_loader, NULL);
Rocky Hotas
2024-04-06 21:00:20 UTC
Permalink
Post by Warner Losh
When this happens, hit ^t (control t). That will give a traceback of the
call stack which may help you track down where it is hanging (most likely
something is sleeping waiting for an event).
Thanks! It seems that cat itself is hanging (so, uiomove can still be
the
culprit...):

$ cat rolld
Opened device "rolld" successfully.
load: 0.44 cmd: cat 13392 [running] 7.67r 1.25u 6.39s 38% 1936k

I also tried to modify rolld_read using only char variables:

static int
rolld_read(struct cdev *dev __unused, struct uio *uio, int ioflag
__unused)
{
char random_out;
char random_item;
int error;

random_item = (char) arc4random();
random_out = random_item % d_size;

if ((error = uiomove(&random_out, 1, uio)) != 0)
uprintf("uiomove failed!\n");

return (error);
}


But nothing changed with respect to the first version.

Rocky



--
Posted automagically by a mail2news gateway at muc.de e.V.
Please direct questions, flames, donations, etc. to news-***@muc.de
Rocky Hotas
2024-04-09 15:46:35 UTC
Permalink
Post by Warner Losh
running means there's a tight loop somewhere... uiomove doesn't do that. It
is a bunch of ifs that go to a copyout. Arc4random shouldn't either. I'd
add printf to see where.
Using some uprintf along the code of rolld_read, I confirm that
there's a loop involving cat.

[...]
Post by Warner Losh
This should produce an infinite number of chars... maybe it is and d_size
is 1 and they are all NULs. Try cat -v.
Yes: there was an infinite number of chars, but they were non-printable,
so invisibile, because they are the raw values 0, 1, 2, 3, 4 or 5 resulting
from `random_item % d_size'.

Only `cat -v' could show them.

The infinite `cat' output is provoked by the `1' value provided as
second parameter of `uiomove(&random_out, 1, uio)' without any other
comparison involving uio_offset, so never signaling the end of "file",
as mentioned by Dag-Erling.

Thank you!

Rocky


--
Posted automagically by a mail2news gateway at muc.de e.V.
Please direct questions, flames, donations, etc. to news-***@muc.de
Warner Losh
2024-04-07 04:21:12 UTC
Permalink
Post by Rocky Hotas
Post by Warner Losh
When this happens, hit ^t (control t). That will give a traceback of the
call stack which may help you track down where it is hanging (most likely
something is sleeping waiting for an event).
Thanks! It seems that cat itself is hanging (so, uiomove can still be
the
$ cat rolld
Opened device "rolld" successfully.
load: 0.44 cmd: cat 13392 [running] 7.67r 1.25u 6.39s 38% 1936k
running means there's a tight loop somewhere... uiomove doesn't do that. It
is a bunch of ifs that go to a copyout. Arc4random shouldn't either. I'd
add printf to see where.
Post by Rocky Hotas
static int
rolld_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
{
char random_out;
char random_item;
int error;
random_item = (char) arc4random();
random_out = random_item % d_size;
if ((error = uiomove(&random_out, 1, uio)) != 0)
uprintf("uiomove failed!\n");
return (error);
}
But nothing changed with respect to the first version.
This should produce an infinite number of chars... maybe it is and d_size
is 1 and they are all NULs. Try cat -v.

Warner
Post by Rocky Hotas
Rocky
Dag-Erling Smørgrav
2024-04-07 10:50:00 UTC
Permalink
Post by Rocky Hotas
static int
rolld_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
{
uint32_t random_out;
uint32_t random_item;
int error;
random_item = arc4random();
random_out = random_item % d_size;
if ((error = uiomove(&random_out, 1, uio)) != 0)
uprintf("uiomove failed!\n");
return (error);
}
Using a uint32_t will work on little-endian systems (such as amd64)
because the least-significant byte, which is the only non-zero byte,
comes first. On big-endian systems, it would simply always return 0.

Furthermore, this won't only return one byte; rather, it will return one
byte _at a time_, very inefficiently. This is why cat appears to hang.
To truly only return one byte, you need to look at uio->uio_offset and
return 0 without calling uiomove(), signaling EOF, if it is non-zero.

In summary, you should write rolld_read() as:

uint8_t roll = arc4random() % d_size;
if (uio->uio_offset > 0)
return (0);
return (uiomove(&roll, 1, uio));

You can also use uiomove_frombuf(), which will take care of that check
for you. It's a bit overkill when you're only writing a single byte,
but if you wanted to output text instead of binary, you could use this:

char roll[2];
roll[0] = '0' + arc4random() % d_size;
roll[1] = '\n';
return (uiomove_frombuf(roll, sizeof(roll), uio));

Obviously, this will only work for d_size <= 9. For larger values, you
will want to use snprintf():

char roll[16];
int len = snprintf(roll, sizeof(roll), "%d\n", arc4random() % d_size);
return (uiomove_frombuf(roll, len, uio));

Have fun,

DES
--
Dag-Erling Smørgrav - ***@FreeBSD.org


--
Posted automagically by a mail2news gateway at muc.de e.V.
Please direct questions, flames, donations, etc. to news-***@muc.de
Loading...