0

I'm writing my first serial driver for a UART (AXI UART Lite from Xilinx). I know there's one in the kernel but I'm not working in an embedded Linux environment.

Using the documentation and the existing code for references, I've implemented my driver. However, I cannot validate the my startup or request_port functions are being called. The startup function basically does what this does. However, not using an IRQ I'm polling. Likewise, the request_port function I have is quite similar to this function.

The documentation states that the startup function is called once when the port is opened. Since I do have the device node in /dev that I'm expecting, I thought I needed to open the port. In steps minicom and I open the file (verified with lsof). Yet, I can find none of the messages in dmesg that I've placed in either my startup or request_port functions.

Sorry for the delayed response but I had to jump through some hoops before posting code. This is what I have:

#define MAX_SUPPORTED_UARTS 1
#define DRIVER_MAJOR_NUM  243  /* local/experimental */
#define DRIVER_MINOR_NUM  0

static int request_port(struct uart_port *);
static void release_port(struct uart_port *);
static int startup(struct uart_port *);
static void shutdown(struct uart_port *);

static struct uart_ops axilite_ops = {
    .release_port = release_port,
    .request_port = request_port,
    .startup = startup,
    .shutdown = shutdown,
};

static struct uart_port axilite_ports[MAX_SUPPORTED_UARTS] = {
    {
        .mapbase = 0, /* determined during discovery */
        .membase = 0, /* calcuated after learning mapbase */
        .iotype = UPIO_MEM, /* from serial_core.c */
        .irq = 0,  /* currently, device doesn't use an IRQ, just polling */
        .fifosize = 16,  /* directly Xilinx documentation */
        .ops = &axilite_ops,
        .flags = UPF_FIXED_TYPE | UPF_FIXED_PORT | UPF_SPD_VHI,
        .mctrl = TIOCM_CTS | TIOCM_DSR | TIOCM_CAR, /* from uartlite.c in kernel tree */
    },
};

static struct uart_driver axilite_driver = {
    .owner = THIS_MODULE,
    .driver_name = "axilite",
    .dev_name = "ttyAUL",
    .major = DRIVER_MAJOR_NUM,
    .minor = DRIVER_MINOR_NUM,
    .nr = MAX_SUPPORTED_UARTS,
};

static int startup(struct uart_port *pup)
{
    void __iomem* pmem = pup->membase;
    uint32_t ctlreg = 0;
    pr_info("%s:%d entry point\n", __func__, __LINE__);

    ctlreg = ioread32(CTL_REG_ADDR(pmem));
    pr_info("DEBUG: %s:%d CTL Reg is 0x%08x\n",
            __func__, __LINE__, ctlreg);
    /* clear the FIFO's */
    ctlreg |= CTL_REG_RST_TX | CTL_REG_RST_RX;
    iowrite32(ctlreg, CTL_REG_ADDR(pmem));

    return 0;
}

static void shutdown(struct uart_port *pup)
{
    void __iomem* pmem = pup->membase;

    pr_info("%s:%d entry point\n", __func__, __LINE__);

    /* Using a queue from the axilite driver in the kernel */
    iowrite32(0, CTL_REG_ADDR(pmem));
}

static void release_port(struct uart_port *pup)
{
    pr_info("%s:%d entry point\n", __func__, __LINE__);

    if (pup->membase)
        iounmap(pup->membase);

    pup->membase = NULL;
}

static int request_port(struct uart_port *pup)
{
    int result = 0;
    uint32_t bus_addr = pup->mapbase;
    void __iomem* pmem = NULL;

    pr_info("%s:%d mapping bus addr to virtual space\n",
            __func__, __LINE__);
    pmem = ioremap_nocache(bus_addr, UART_REGISTER_REGION);
    if (IS_ERR_OR_NULL(pmem)) {
        pr_err("%s:%d ioremap_nocache failed\n",
                __func__, __LINE__);
        result = PTR_ERR(pmem);
        goto startup_exit;
    }

    pup->membase = pmem;
    pr_info("%s:%d busaddr 0x%08x mapped to %p\n",
            __func__, __LINE__, bus_addr, pmem);

startup_exit:
    return result;
}

static int add_uart_ports(void)
{
    int result = 0;
    uint32_t i = 0, baseaddr = 0;

    pr_info("%s:%d adding %d port(s) to the system\n",
            __func__, __LINE__, uart_count);

    /* calls into code which learns the base address,
     * verified that it works
     */
    baseaddr = get_uart_base(i);

    if ((int)baseaddr == -EINVAL) {
        pr_err("%s:%d Invalid index used\n", __func__, __LINE__);
        result = (int)baseaddr; 
        goto add_ports_exit;
    }
    else if (0 == baseaddr) {
        pr_err("%s:%d Invalid bus address encountered, port add stopped\n",
                __func__, __LINE__);
        result = -ENODEV;
        goto add_ports_exit;
    }
    else
        axilite_port[i].mapbase = baseaddr;

    result = uart_add_one_port(&axilite_driver, &axilite_port[i]);
    if (result) {
        pr_err("%s:%d uart_add_one_port failed for port %d\n",
                __func__, __LINE__, i);
        goto add_ports_exit;
    }

    /* TODO think of a better way to track whether the port
     * is registered.  this will work for now.
     */
    axilite_port[i].private_data = &uart_count;

    init_stage |= UART_PORT_REGISTERED;

add_ports_exit:

    return result;
}

static int add_uart_driver(void)
{
    int result = 0;
    pr_info("%s:%d adding the driver\n", __func__, __LINE__);

    uart_count = get_uart_count();
    axilite_driver.nr = (int)uart_count;

    result = uart_register_driver(&axilite_driver);
    if (result) {
        pr_err("%s:%d uart_register_driver failed\n",
                __func__, __LINE__);
        goto add_driver_exit;
    }

    init_stage |= DRIVER_REGISTERED;

add_driver_exit:
    return result;
}

EDIT

I am not adding a platform driver. Although this is a Xilinx system, my driver will not be running in embedded Linux. Given this, I thought the driver bring-up would similar to the driver for 21285. Is this in error?

END EDIT

What could be causing this? What should I be doing, or checking?

Andy

Andrew Falanga
  • 2,274
  • 4
  • 26
  • 51
  • Can you prove your driver is registered correctly? I'd post some more of your module code, the struct, the module init function, etc. as well as the exact calling sequence for uart_register_driver. What you've posted is a bit sparse to be able to help. Also, what device major/minor [and how do you get them]? In short, check for _any_ error and do `printk` at _every_ step. If you post more, I may be able to help more. – Craig Estey May 05 '16 at 19:20
  • I'm defining a MAJOR of 243 and MINOR starting with 0. 243 should be in the experimental/local range. `cat`ing */sys/class/tty/ttyAUL0/dev* shows `243:0`. The device name that I've assigned is "ttyAUL". I am checking the return values of things like `uart_add_one_port` and `uart_register_driver`. – Andrew Falanga May 05 '16 at 20:26
  • Add an unconditional `printk` to your module init. This proves it gets invoked and proves that your `printk` call is working. It may [probably will] show up in dmesg, but also check /var/log/messages and journalctl. BTW, did you register your platform driver as well? – Craig Estey May 05 '16 at 20:37
  • @CraigEstey The question now includes code from my driver. Yes, my driver is loading "properly." I have several *pr_info* calls showing in /var/log/messages. I am also adding `platform_device` objects now. I'm not working in embedded Linux. The board is simply PCIe. Should I still require a platform driver? – Andrew Falanga May 13 '16 at 20:31

2 Answers2

1

In serial_core.c uart_port_startup method, there is code in beginning:

 if (uport->type == PORT_UNKNOWN)
    return 1;

This is the reason why startup method isn't called. You need to set struct uart_port.type to a non-unkown value. At least it worked for me. You probably need to add new PORT_ type or use existing one - not sure about conflicts. For my virutal device this works:

static struct uart_port tiny_port = {
    .ops        = &tiny_ops,
    .type       = PORT_8250,
};
Janis Coders
  • 157
  • 1
  • 6
  • I'm making this as the answer. Although I solved this a few months ago, sorry for not posing that here, I'm sure this was the solution to my question. Thank you Janis Coders. – Andrew Falanga Sep 08 '16 at 14:53
0

Are you providing the platform data? The driver by itself is not enough, you have to provide to the kernel the information that your hardware is there, memory mapped at a specific base address, connected to a specific interrupt, and so on... On modern ARM systems you do that through the device tree, other systems have different means. The driver is loaded and started only after providing these data.