HelenOS sources
This source file includes following definitions.
- pl050_from_fun
- pl050_interrupt
- pl050_init
- pl050_read
- pl050_write
- pl050_char_conn
- pl050_dev_add
- pl050_fun_online
- pl050_fun_offline
- main
#include <assert.h>
#include <bitops.h>
#include <stdio.h>
#include <errno.h>
#include <str_error.h>
#include <ddf/driver.h>
#include <ddf/interrupt.h>
#include <ddf/log.h>
#include <device/hw_res.h>
#include <device/hw_res_parsed.h>
#include <io/chardev_srv.h>
#include "pl050_hw.h"
#define NAME "pl050"
enum {
buffer_size = 64
};
static errno_t pl050_dev_add(ddf_dev_t *);
static errno_t pl050_fun_online(ddf_fun_t *);
static errno_t pl050_fun_offline(ddf_fun_t *);
static void pl050_char_conn(ipc_call_t *, void *);
static errno_t pl050_read(chardev_srv_t *, void *, size_t, size_t *,
chardev_flags_t);
static errno_t pl050_write(chardev_srv_t *, const void *, size_t, size_t *);
static driver_ops_t driver_ops = {
.dev_add = &pl050_dev_add,
.fun_online = &pl050_fun_online,
.fun_offline = &pl050_fun_offline
};
static driver_t pl050_driver = {
.name = NAME,
.driver_ops = &driver_ops
};
static chardev_ops_t pl050_chardev_ops = {
.read = pl050_read,
.write = pl050_write
};
typedef struct {
async_sess_t *parent_sess;
ddf_dev_t *dev;
char *name;
ddf_fun_t *fun_a;
chardev_srvs_t cds;
uintptr_t iobase;
size_t iosize;
kmi_regs_t *regs;
uint8_t buffer[buffer_size];
size_t buf_rp;
size_t buf_wp;
fibril_condvar_t buf_cv;
fibril_mutex_t buf_lock;
} pl050_t;
static irq_pio_range_t pl050_ranges[] = {
{
.base = 0,
.size = 9,
}
};
static irq_cmd_t pl050_cmds[] = {
{
.cmd = CMD_PIO_READ_8,
.addr = NULL,
.dstarg = 1
},
{
.cmd = CMD_AND,
.value = BIT_V(uint8_t, kmi_stat_rxfull),
.srcarg = 1,
.dstarg = 3
},
{
.cmd = CMD_PREDICATE,
.value = 2,
.srcarg = 3
},
{
.cmd = CMD_PIO_READ_8,
.addr = NULL,
.dstarg = 2
},
{
.cmd = CMD_ACCEPT
}
};
static irq_code_t pl050_irq_code = {
sizeof(pl050_ranges) / sizeof(irq_pio_range_t),
pl050_ranges,
sizeof(pl050_cmds) / sizeof(irq_cmd_t),
pl050_cmds
};
static pl050_t *pl050_from_fun(ddf_fun_t *fun)
{
return (pl050_t *)ddf_dev_data_get(ddf_fun_get_dev(fun));
}
static void pl050_interrupt(ipc_call_t *call, void *arg)
{
pl050_t *pl050 = (pl050_t *)arg;
size_t nidx;
fibril_mutex_lock(&pl050->buf_lock);
nidx = (pl050->buf_wp + 1) % buffer_size;
if (nidx == pl050->buf_rp) {
ddf_msg(LVL_WARN, "Buffer overrun.");
fibril_mutex_unlock(&pl050->buf_lock);
return;
}
pl050->buffer[pl050->buf_wp] = ipc_get_arg2(call);
pl050->buf_wp = nidx;
fibril_condvar_broadcast(&pl050->buf_cv);
fibril_mutex_unlock(&pl050->buf_lock);
}
static errno_t pl050_init(pl050_t *pl050)
{
hw_res_list_parsed_t res;
void *regs;
errno_t rc;
fibril_mutex_initialize(&pl050->buf_lock);
fibril_condvar_initialize(&pl050->buf_cv);
pl050->buf_rp = pl050->buf_wp = 0;
pl050->parent_sess = ddf_dev_parent_sess_get(pl050->dev);
if (pl050->parent_sess == NULL) {
ddf_msg(LVL_ERROR, "Failed connecitng parent driver.");
rc = ENOMEM;
goto error;
}
hw_res_list_parsed_init(&res);
rc = hw_res_get_list_parsed(pl050->parent_sess, &res, 0);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed getting resource list.");
goto error;
}
if (res.mem_ranges.count != 1) {
ddf_msg(LVL_ERROR, "Expected exactly one memory range.");
rc = EINVAL;
goto error;
}
pl050->iobase = RNGABS(res.mem_ranges.ranges[0]);
pl050->iosize = RNGSZ(res.mem_ranges.ranges[0]);
pl050_irq_code.ranges[0].base = pl050->iobase;
kmi_regs_t *regsphys = (kmi_regs_t *) pl050->iobase;
pl050_irq_code.cmds[0].addr = ®sphys->stat;
pl050_irq_code.cmds[3].addr = ®sphys->data;
if (res.irqs.count != 1) {
ddf_msg(LVL_ERROR, "Expected exactly one IRQ.");
rc = EINVAL;
goto error;
}
ddf_msg(LVL_DEBUG, "iobase=%p irq=%d", (void *)pl050->iobase,
res.irqs.irqs[0]);
rc = pio_enable((void *)pl050->iobase, sizeof(kmi_regs_t), ®s);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Error enabling PIO");
goto error;
}
pl050->regs = regs;
cap_irq_handle_t ihandle;
rc = register_interrupt_handler(pl050->dev,
res.irqs.irqs[0], pl050_interrupt, (void *)pl050, &pl050_irq_code,
&ihandle);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed registering interrupt handler. (%s)",
str_error_name(rc));
goto error;
}
rc = hw_res_enable_interrupt(pl050->parent_sess, res.irqs.irqs[0]);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed enabling interrupt: %s", str_error(rc));
goto error;
}
pio_write_8(&pl050->regs->cr,
BIT_V(uint8_t, kmi_cr_enable) |
BIT_V(uint8_t, kmi_cr_rxintr));
return EOK;
error:
return rc;
}
static errno_t pl050_read(chardev_srv_t *srv, void *buffer, size_t size,
size_t *nread, chardev_flags_t flags)
{
pl050_t *pl050 = (pl050_t *)srv->srvs->sarg;
uint8_t *bp = buffer;
size_t left;
fibril_mutex_lock(&pl050->buf_lock);
left = size;
while (left > 0) {
while ((flags & chardev_f_nonblock) == 0 &&
left == size && pl050->buf_rp == pl050->buf_wp)
fibril_condvar_wait(&pl050->buf_cv, &pl050->buf_lock);
if (pl050->buf_rp == pl050->buf_wp)
break;
*bp++ = pl050->buffer[pl050->buf_rp];
--left;
pl050->buf_rp = (pl050->buf_rp + 1) % buffer_size;
}
fibril_mutex_unlock(&pl050->buf_lock);
*nread = size - left;
return EOK;
}
static errno_t pl050_write(chardev_srv_t *srv, const void *data, size_t size,
size_t *nwritten)
{
pl050_t *pl050 = (pl050_t *)srv->srvs->sarg;
uint8_t *dp = (uint8_t *)data;
uint8_t status;
size_t i;
ddf_msg(LVL_NOTE, "%s/pl050_write(%zu bytes)", pl050->name, size);
for (i = 0; i < size; i++) {
while (true) {
status = pio_read_8(&pl050->regs->stat);
if ((status & BIT_V(uint8_t, kmi_stat_txempty)) != 0)
break;
}
pio_write_8(&pl050->regs->data, dp[i]);
}
ddf_msg(LVL_NOTE, "%s/pl050_write() success", pl050->name);
*nwritten = size;
return EOK;
}
void pl050_char_conn(ipc_call_t *icall, void *arg)
{
pl050_t *pl050 = pl050_from_fun((ddf_fun_t *)arg);
chardev_conn(icall, &pl050->cds);
}
static errno_t pl050_dev_add(ddf_dev_t *dev)
{
ddf_fun_t *fun_a;
pl050_t *pl050 = NULL;
const char *mname;
errno_t rc;
ddf_msg(LVL_DEBUG, "pl050_dev_add()");
pl050 = ddf_dev_data_alloc(dev, sizeof(pl050_t));
if (pl050 == NULL) {
ddf_msg(LVL_ERROR, "Failed allocating soft state.\n");
rc = ENOMEM;
goto error;
}
pl050->name = (char *)ddf_dev_get_name(dev);
if (pl050->name == NULL) {
rc = ENOMEM;
goto error;
}
fun_a = ddf_fun_create(dev, fun_inner, "a");
if (fun_a == NULL) {
ddf_msg(LVL_ERROR, "Failed creating function 'a'.");
rc = ENOMEM;
goto error;
}
pl050->fun_a = fun_a;
pl050->dev = dev;
rc = pl050_init(pl050);
if (rc != EOK)
goto error;
if (str_cmp(pl050->name, "kbd") == 0)
mname = "char/atkbd";
else
mname = "char/ps2mouse";
rc = ddf_fun_add_match_id(fun_a, mname, 10);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed adding match IDs to function %s",
"char/xtkbd");
goto error;
}
chardev_srvs_init(&pl050->cds);
pl050->cds.ops = &pl050_chardev_ops;
pl050->cds.sarg = pl050;
ddf_fun_set_conn_handler(fun_a, pl050_char_conn);
rc = ddf_fun_bind(fun_a);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed binding function 'a': %s", str_error(rc));
ddf_fun_destroy(fun_a);
goto error;
}
ddf_msg(LVL_DEBUG, "Device added.");
return EOK;
error:
if (pl050 != NULL)
free(pl050->name);
return rc;
}
static errno_t pl050_fun_online(ddf_fun_t *fun)
{
ddf_msg(LVL_DEBUG, "pl050_fun_online()");
return ddf_fun_online(fun);
}
static errno_t pl050_fun_offline(ddf_fun_t *fun)
{
ddf_msg(LVL_DEBUG, "pl050_fun_offline()");
return ddf_fun_offline(fun);
}
int main(int argc, char *argv[])
{
errno_t rc;
printf(NAME ": HelenOS pl050 serial device driver\n");
rc = ddf_log_init(NAME);
if (rc != EOK) {
printf(NAME ": Error connecting logging service.");
return 1;
}
return ddf_driver_main(&pl050_driver);
}
HelenOS homepage, sources at GitHub