/* * pti.c - PTI driver for cJTAG data extration * * Copyright (C) Intel 2010 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * The PTI (Parallel Trace Interface) driver directs trace data routed from * various parts in the system out through the Intel Penwell PTI port and * out of the mobile device for analysis with a debugging tool * (Lauterbach, Fido). This is part of a solution for the MIPI P1149.7, * compact JTAG, standard. */ #include <linux/init.h> #include <linux/sched.h> #include <linux/interrupt.h> #include <linux/console.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/tty.h> #include <linux/tty_driver.h> #include <linux/pci.h> #include <linux/mutex.h> #include <linux/miscdevice.h> #include <linux/pti.h> #include <linux/slab.h> #include <linux/uaccess.h> #define DRIVERNAME "pti" #define PCINAME "pciPTI" #define TTYNAME "ttyPTI" #define CHARNAME "pti" #define PTITTY_MINOR_START 0 #define PTITTY_MINOR_NUM 2 #define MAX_APP_IDS 16 /* 128 channel ids / u8 bit size */ #define MAX_OS_IDS 16 /* 128 channel ids / u8 bit size */ #define MAX_MODEM_IDS 16 /* 128 channel ids / u8 bit size */ #define MODEM_BASE_ID 71 /* modem master ID address */ #define CONTROL_ID 72 /* control master ID address */ #define CONSOLE_ID 73 /* console master ID address */ #define OS_BASE_ID 74 /* base OS master ID address */ #define APP_BASE_ID 80 /* base App master ID address */ #define CONTROL_FRAME_LEN 32 /* PTI control frame maximum size */ #define USER_COPY_SIZE 8192 /* 8Kb buffer for user space copy */ #define APERTURE_14 0x3800000 /* offset to first OS write addr */ #define APERTURE_LEN 0x400000 /* address length */ struct pti_tty { struct pti_masterchannel *mc; }; struct pti_dev { struct tty_port port; unsigned long pti_addr; unsigned long aperture_base; void __iomem *pti_ioaddr; u8 ia_app[MAX_APP_IDS]; u8 ia_os[MAX_OS_IDS]; u8 ia_modem[MAX_MODEM_IDS]; }; /* * This protects access to ia_app, ia_os, and ia_modem, * which keeps track of channels allocated in * an aperture write id. */ static DEFINE_MUTEX(alloclock); static struct pci_device_id pci_ids[] __devinitconst = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x82B)}, {0} }; static struct tty_driver *pti_tty_driver; static struct pti_dev *drv_data; static unsigned int pti_console_channel; static unsigned int pti_control_channel; /** * pti_write_to_aperture()- The private write function to PTI HW. * * @mc: The 'aperture'. It's part of a write address that holds * a master and channel ID. * @buf: Data being written to the HW that will ultimately be seen * in a debugging tool (Fido, Lauterbach). * @len: Size of buffer. * * Since each aperture is specified by a unique * master/channel ID, no two processes will be writing * to the same aperture at the same time so no lock is required. The * PTI-Output agent will send these out in the order that they arrived, and * thus, it will intermix these messages. The debug tool can then later * regroup the appropriate message segments together reconstituting each * message. */ static void pti_write_to_aperture(struct pti_masterchannel *mc, u8 *buf, int len) { int dwordcnt; int final; int i; u32 ptiword; u32 __iomem *aperture; u8 *p = buf; /* * calculate the aperture offset from the base using the master and * channel id's. */ aperture = drv_data->pti_ioaddr + (mc->master << 15) + (mc->channel << 8); dwordcnt = len >> 2; final = len - (dwordcnt << 2); /* final = trailing bytes */ if (final == 0 && dwordcnt != 0) { /* always need a final dword */ final += 4; dwordcnt--; } for (i = 0; i < dwordcnt; i++) { ptiword = be32_to_cpu(*(u32 *)p); p += 4; iowrite32(ptiword, aperture); } aperture += PTI_LASTDWORD_DTS; /* adding DTS signals that is EOM */ ptiword = 0; for (i = 0; i < final; i++) ptiword |= *p++ << (24-(8*i)); iowrite32(ptiword, aperture); return; } /** * pti_control_frame_built_and_sent()- control frame build and send function. * * @mc: The master / channel structure on which the function * built a control frame. * @thread_name: The thread name associated with the master / channel or * 'NULL' if using the 'current' global variable. * * To be able to post process the PTI contents on host side, a control frame * is added before sending any PTI content. So the host side knows on * each PTI frame the name of the thread using a dedicated master / channel. * The thread name is retrieved from 'current' global variable if 'thread_name' * is 'NULL', else it is retrieved from 'thread_name' parameter. * This function builds this frame and sends it to a master ID CONTROL_ID. * The overhead is only 32 bytes since the driver only writes to HW * in 32 byte chunks. */ static void pti_control_frame_built_and_sent(struct pti_masterchannel *mc, const char *thread_name) { /* * Since we access the comm member in current's task_struct, we only * need to be as large as what 'comm' in that structure is. */ char comm[TASK_COMM_LEN]; struct pti_masterchannel mccontrol = {.master = CONTROL_ID, .channel = 0}; const char *thread_name_p; const char *control_format = "%3d %3d %s"; u8 control_frame[CONTROL_FRAME_LEN]; if (!thread_name) { if (!in_interrupt()) get_task_comm(comm, current); else strncpy(comm, "Interrupt", TASK_COMM_LEN); /* Absolutely ensure our buffer is zero terminated. */ comm[TASK_COMM_LEN-1] = 0; thread_name_p = comm; } else { thread_name_p = thread_name; } mccontrol.channel = pti_control_channel; pti_control_channel = (pti_control_channel + 1) & 0x7f; snprintf(control_frame, CONTROL_FRAME_LEN, control_format, mc->master, mc->channel, thread_name_p); pti_write_to_aperture(&mccontrol, control_frame, strlen(control_frame)); } /** * pti_write_full_frame_to_aperture()- high level function to * write to PTI. * * @mc: The 'aperture'. It's part of a write address that holds * a master and channel ID. * @buf: Data being written to the HW that will ultimately be seen * in a debugging tool (Fido, Lauterbach). * @len: Size of buffer. * * All threads sending data (either console, user space application, ...) * are calling the high level function to write to PTI meaning that it is * possible to add a control frame before sending the content. */ static void pti_write_full_frame_to_aperture(struct pti_masterchannel *mc, const unsigned char *buf, int len) { pti_control_frame_built_and_sent(mc, NULL); pti_write_to_aperture(mc, (u8 *)buf, len); } /** * get_id()- Allocate a master and channel ID. * * @id_array: an array of bits representing what channel * id's are allocated for writing. * @max_ids: The max amount of available write IDs to use. * @base_id: The starting SW channel ID, based on the Intel * PTI arch. * @thread_name: The thread name associated with the master / channel or * 'NULL' if using the 'current' global variable. * * Returns: * pti_masterchannel struct with master, channel ID address * 0 for error * * Each bit in the arrays ia_app and ia_os correspond to a master and * channel id. The bit is one if the id is taken and 0 if free. For * every master there are 128 channel id's. */ static struct pti_masterchannel *get_id(u8 *id_array, int max_ids, int base_id, const char *thread_name) { struct pti_masterchannel *mc; int i, j, mask; mc = kmalloc(sizeof(struct pti_masterchannel), GFP_KERNEL); if (mc == NULL) return NULL; /* look for a byte with a free bit */ for (i = 0; i < max_ids; i++) if (id_array[i] != 0xff) break; if (i == max_ids) { kfree(mc); return NULL; } /* find the bit in the 128 possible channel opportunities */ mask = 0x80; for (j = 0; j < 8; j++) { if ((id_array[i] & mask) == 0) break; mask >>= 1; } /* grab it */ id_array[i] |= mask; mc->master = base_id; mc->channel = ((i & 0xf)<<3) + j; /* write new master Id / channel Id allocation to channel control */ pti_control_frame_built_and_sent(mc, thread_name); return mc; } /* * The following three functions: * pti_request_mastercahannel(), mipi_release_masterchannel() * and pti_writedata() are an API for other kernel drivers to * access PTI. */ /** * pti_request_masterchannel()- Kernel API function used to allocate * a master, channel ID address * to write to PTI HW. * * @type: 0- request Application master, channel aperture ID * write address. * 1- request OS master, channel aperture ID write * address. * 2- request Modem master, channel aperture ID * write address. * Other values, error. * @thread_name: The thread name associated with the master / channel or * 'NULL' if using the 'current' global variable. * * Returns: * pti_masterchannel struct * 0 for error */ struct pti_masterchannel *pti_request_masterchannel(u8 type, const char *thread_name) { struct pti_masterchannel *mc; mutex_lock(&alloclock); switch (type) { case 0: mc = get_id(drv_data->ia_app, MAX_APP_IDS, APP_BASE_ID, thread_name); break; case 1: mc = get_id(drv_data->ia_os, MAX_OS_IDS, OS_BASE_ID, thread_name); break; case 2: mc = get_id(drv_data->ia_modem, MAX_MODEM_IDS, MODEM_BASE_ID, thread_name); break; default: mc = NULL; } mutex_unlock(&alloclock); return mc; } EXPORT_SYMBOL_GPL(pti_request_masterchannel); /** * pti_release_masterchannel()- Kernel API function used to release * a master, channel ID address * used to write to PTI HW. * * @mc: master, channel apeture ID address to be released. This * will de-allocate the structure via kfree(). */ void pti_release_masterchannel(struct pti_masterchannel *mc) { u8 master, channel, i; mutex_lock(&alloclock); if (mc) { master = mc->master; channel = mc->channel; if (master == APP_BASE_ID) { i = channel >> 3; drv_data->ia_app[i] &= ~(0x80>>(channel & 0x7)); } else if (master == OS_BASE_ID) { i = channel >> 3; drv_data->ia_os[i] &= ~(0x80>>(channel & 0x7)); } else { i = channel >> 3; drv_data->ia_modem[i] &= ~(0x80>>(channel & 0x7)); } kfree(mc); } mutex_unlock(&alloclock); } EXPORT_SYMBOL_GPL(pti_release_masterchannel); /** * pti_writedata()- Kernel API function used to write trace * debugging data to PTI HW. * * @mc: Master, channel aperture ID address to write to. * Null value will return with no write occurring. * @buf: Trace debuging data to write to the PTI HW. * Null value will return with no write occurring. * @count: Size of buf. Value of 0 or a negative number will * return with no write occuring. */ void pti_writedata(struct pti_masterchannel *mc, u8 *buf, int count) { /* * since this function is exported, this is treated like an * API function, thus, all parameters should * be checked for validity. */ if ((mc != NULL) && (buf != NULL) && (count > 0)) pti_write_to_aperture(mc, buf, count); return; } EXPORT_SYMBOL_GPL(pti_writedata); /** * pti_pci_remove()- Driver exit method to remove PTI from * PCI bus. * @pdev: variable containing pci info of PTI. */ static void __devexit pti_pci_remove(struct pci_dev *pdev) { struct pti_dev *drv_data; drv_data = pci_get_drvdata(pdev); if (drv_data != NULL) { pci_iounmap(pdev, drv_data->pti_ioaddr); pci_set_drvdata(pdev, NULL); kfree(drv_data); pci_release_region(pdev, 1); pci_disable_device(pdev); } } /* * for the tty_driver_*() basic function descriptions, see tty_driver.h. * Specific header comments made for PTI-related specifics. */ /** * pti_tty_driver_open()- Open an Application master, channel aperture * ID to the PTI device via tty device. * * @tty: tty interface. * @filp: filp interface pased to tty_port_open() call. * * Returns: * int, 0 for success * otherwise, fail value * * The main purpose of using the tty device interface is for * each tty port to have a unique PTI write aperture. In an * example use case, ttyPTI0 gets syslogd and an APP aperture * ID and ttyPTI1 is where the n_tracesink ldisc hooks to route * modem messages into PTI. Modem trace data does not have to * go to ttyPTI1, but ttyPTI0 and ttyPTI1 do need to be distinct * master IDs. These messages go through the PTI HW and out of * the handheld platform and to the Fido/Lauterbach device. */ static int pti_tty_driver_open(struct tty_struct *tty, struct file *filp) { /* * we actually want to allocate a new channel per open, per * system arch. HW gives more than plenty channels for a single * system task to have its own channel to write trace data. This * also removes a locking requirement for the actual write * procedure. */ return tty_port_open(&drv_data->port, tty, filp); } /** * pti_tty_driver_close()- close tty device and release Application * master, channel aperture ID to the PTI device via tty device. * * @tty: tty interface. * @filp: filp interface pased to tty_port_close() call. * * The main purpose of using the tty device interface is to route * syslog daemon messages to the PTI HW and out of the handheld platform * and to the Fido/Lauterbach device. */ static void pti_tty_driver_close(struct tty_struct *tty, struct file *filp) { tty_port_close(&drv_data->port, tty, filp); } /** * pti_tty_install()- Used to set up specific master-channels * to tty ports for organizational purposes when * tracing viewed from debuging tools. * * @driver: tty driver information. * @tty: tty struct containing pti information. * * Returns: * 0 for success * otherwise, error */ static int pti_tty_install(struct tty_driver *driver, struct tty_struct *tty) { int idx = tty->index; struct pti_tty *pti_tty_data; int ret = tty_standard_install(driver, tty); if (ret == 0) { pti_tty_data = kmalloc(sizeof(struct pti_tty), GFP_KERNEL); if (pti_tty_data == NULL) return -ENOMEM; if (idx == PTITTY_MINOR_START) pti_tty_data->mc = pti_request_masterchannel(0, NULL); else pti_tty_data->mc = pti_request_masterchannel(2, NULL); if (pti_tty_data->mc == NULL) { kfree(pti_tty_data); return -ENXIO; } tty->driver_data = pti_tty_data; } return ret; } /** * pti_tty_cleanup()- Used to de-allocate master-channel resources * tied to tty's of this driver. * * @tty: tty struct containing pti information. */ static void pti_tty_cleanup(struct tty_struct *tty) { struct pti_tty *pti_tty_data = tty->driver_data; if (pti_tty_data == NULL) return; pti_release_masterchannel(pti_tty_data->mc); kfree(pti_tty_data); tty->driver_data = NULL; } /** * pti_tty_driver_write()- Write trace debugging data through the char * interface to the PTI HW. Part of the misc device implementation. * * @filp: Contains private data which is used to obtain * master, channel write ID. * @data: trace data to be written. * @len: # of byte to write. * * Returns: * int, # of bytes written * otherwise, error */ static int pti_tty_driver_write(struct tty_struct *tty, const unsigned char *buf, int len) { struct pti_tty *pti_tty_data = tty->driver_data; if ((pti_tty_data != NULL) && (pti_tty_data->mc != NULL)) { pti_write_to_aperture(pti_tty_data->mc, (u8 *)buf, len); return len; } /* * we can't write to the pti hardware if the private driver_data * and the mc address is not there. */ else return -EFAULT; } /** * pti_tty_write_room()- Always returns 2048. * * @tty: contains tty info of the pti driver. */ static int pti_tty_write_room(struct tty_struct *tty) { return 2048; } /** * pti_char_open()- Open an Application master, channel aperture * ID to the PTI device. Part of the misc device implementation. * * @inode: not used. * @filp: Output- will have a masterchannel struct set containing * the allocated application PTI aperture write address. * * Returns: * int, 0 for success * otherwise, a fail value */ static int pti_char_open(struct inode *inode, struct file *filp) { struct pti_masterchannel *mc; /* * We really do want to fail immediately if * pti_request_masterchannel() fails, * before assigning the value to filp->private_data. * Slightly easier to debug if this driver needs debugging. */ mc = pti_request_masterchannel(0, NULL); if (mc == NULL) return -ENOMEM; filp->private_data = mc; return 0; } /** * pti_char_release()- Close a char channel to the PTI device. Part * of the misc device implementation. * * @inode: Not used in this implementaiton. * @filp: Contains private_data that contains the master, channel * ID to be released by the PTI device. * * Returns: * always 0 */ static int pti_char_release(struct inode *inode, struct file *filp) { pti_release_masterchannel(filp->private_data); filp->private_data = NULL; return 0; } /** * pti_char_write()- Write trace debugging data through the char * interface to the PTI HW. Part of the misc device implementation. * * @filp: Contains private data which is used to obtain * master, channel write ID. * @data: trace data to be written. * @len: # of byte to write. * @ppose: Not used in this function implementation. * * Returns: * int, # of bytes written * otherwise, error value * * Notes: From side discussions with Alan Cox and experimenting * with PTI debug HW like Nokia's Fido box and Lauterbach * devices, 8192 byte write buffer used by USER_COPY_SIZE was * deemed an appropriate size for this type of usage with * debugging HW. */ static ssize_t pti_char_write(struct file *filp, const char __user *data, size_t len, loff_t *ppose) { struct pti_masterchannel *mc; void *kbuf; const char __user *tmp; size_t size = USER_COPY_SIZE; size_t n = 0; tmp = data; mc = filp->private_data; kbuf = kmalloc(size, GFP_KERNEL); if (kbuf == NULL) { pr_err("%s(%d): buf allocation failed\n", __func__, __LINE__); return -ENOMEM; } do { if (len - n > USER_COPY_SIZE) size = USER_COPY_SIZE; else size = len - n; if (copy_from_user(kbuf, tmp, size)) { kfree(kbuf); return n ? n : -EFAULT; } pti_write_to_aperture(mc, kbuf, size); n += size; tmp += size; } while (len > n); kfree(kbuf); return len; } static const struct tty_operations pti_tty_driver_ops = { .open = pti_tty_driver_open, .close = pti_tty_driver_close, .write = pti_tty_driver_write, .write_room = pti_tty_write_room, .install = pti_tty_install, .cleanup = pti_tty_cleanup }; static const struct file_operations pti_char_driver_ops = { .owner = THIS_MODULE, .write = pti_char_write, .open = pti_char_open, .release = pti_char_release, }; static struct miscdevice pti_char_driver = { .minor = MISC_DYNAMIC_MINOR, .name = CHARNAME, .fops = &pti_char_driver_ops }; /** * pti_console_write()- Write to the console that has been acquired. * * @c: Not used in this implementaiton. * @buf: Data to be written. * @len: Length of buf. */ static void pti_console_write(struct console *c, const char *buf, unsigned len) { static struct pti_masterchannel mc = {.master = CONSOLE_ID, .channel = 0}; mc.channel = pti_console_channel; pti_console_channel = (pti_console_channel + 1) & 0x7f; pti_write_full_frame_to_aperture(&mc, buf, len); } /** * pti_console_device()- Return the driver tty structure and set the * associated index implementation. * * @c: Console device of the driver. * @index: index associated with c. * * Returns: * always value of pti_tty_driver structure when this function * is called. */ static struct tty_driver *pti_console_device(struct console *c, int *index) { *index = c->index; return pti_tty_driver; } /** * pti_console_setup()- Initialize console variables used by the driver. * * @c: Not used. * @opts: Not used. * * Returns: * always 0. */ static int pti_console_setup(struct console *c, char *opts) { pti_console_channel = 0; pti_control_channel = 0; return 0; } /* * pti_console struct, used to capture OS printk()'s and shift * out to the PTI device for debugging. This cannot be * enabled upon boot because of the possibility of eating * any serial console printk's (race condition discovered). * The console should be enabled upon when the tty port is * used for the first time. Since the primary purpose for * the tty port is to hook up syslog to it, the tty port * will be open for a really long time. */ static struct console pti_console = { .name = TTYNAME, .write = pti_console_write, .device = pti_console_device, .setup = pti_console_setup, .flags = CON_PRINTBUFFER, .index = 0, }; /** * pti_port_activate()- Used to start/initialize any items upon * first opening of tty_port(). * * @port- The tty port number of the PTI device. * @tty- The tty struct associated with this device. * * Returns: * always returns 0 * * Notes: The primary purpose of the PTI tty port 0 is to hook * the syslog daemon to it; thus this port will be open for a * very long time. */ static int pti_port_activate(struct tty_port *port, struct tty_struct *tty) { if (port->tty->index == PTITTY_MINOR_START) console_start(&pti_console); return 0; } /** * pti_port_shutdown()- Used to stop/shutdown any items upon the * last tty port close. * * @port- The tty port number of the PTI device. * * Notes: The primary purpose of the PTI tty port 0 is to hook * the syslog daemon to it; thus this port will be open for a * very long time. */ static void pti_port_shutdown(struct tty_port *port) { if (port->tty->index == PTITTY_MINOR_START) console_stop(&pti_console); } static const struct tty_port_operations tty_port_ops = { .activate = pti_port_activate, .shutdown = pti_port_shutdown, }; /* * Note the _probe() call sets everything up and ties the char and tty * to successfully detecting the PTI device on the pci bus. */ /** * pti_pci_probe()- Used to detect pti on the pci bus and set * things up in the driver. * * @pdev- pci_dev struct values for pti. * @ent- pci_device_id struct for pti driver. * * Returns: * 0 for success * otherwise, error */ static int __devinit pti_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int retval = -EINVAL; int pci_bar = 1; dev_dbg(&pdev->dev, "%s %s(%d): PTI PCI ID %04x:%04x\n", __FILE__, __func__, __LINE__, pdev->vendor, pdev->device); retval = misc_register(&pti_char_driver); if (retval) { pr_err("%s(%d): CHAR registration failed of pti driver\n", __func__, __LINE__); pr_err("%s(%d): Error value returned: %d\n", __func__, __LINE__, retval); return retval; } retval = pci_enable_device(pdev); if (retval != 0) { dev_err(&pdev->dev, "%s: pci_enable_device() returned error %d\n", __func__, retval); return retval; } drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); if (drv_data == NULL) { retval = -ENOMEM; dev_err(&pdev->dev, "%s(%d): kmalloc() returned NULL memory.\n", __func__, __LINE__); return retval; } drv_data->pti_addr = pci_resource_start(pdev, pci_bar); retval = pci_request_region(pdev, pci_bar, dev_name(&pdev->dev)); if (retval != 0) { dev_err(&pdev->dev, "%s(%d): pci_request_region() returned error %d\n", __func__, __LINE__, retval); kfree(drv_data); return retval; } drv_data->aperture_base = drv_data->pti_addr+APERTURE_14; drv_data->pti_ioaddr = ioremap_nocache((u32)drv_data->aperture_base, APERTURE_LEN); if (!drv_data->pti_ioaddr) { pci_release_region(pdev, pci_bar); retval = -ENOMEM; kfree(drv_data); return retval; } pci_set_drvdata(pdev, drv_data); tty_port_init(&drv_data->port); drv_data->port.ops = &tty_port_ops; tty_register_device(pti_tty_driver, 0, &pdev->dev); tty_register_device(pti_tty_driver, 1, &pdev->dev); register_console(&pti_console); return retval; } static struct pci_driver pti_pci_driver = { .name = PCINAME, .id_table = pci_ids, .probe = pti_pci_probe, .remove = pti_pci_remove, }; /** * * pti_init()- Overall entry/init call to the pti driver. * It starts the registration process with the kernel. * * Returns: * int __init, 0 for success * otherwise value is an error * */ static int __init pti_init(void) { int retval = -EINVAL; /* First register module as tty device */ pti_tty_driver = alloc_tty_driver(PTITTY_MINOR_NUM); if (pti_tty_driver == NULL) { pr_err("%s(%d): Memory allocation failed for ptiTTY driver\n", __func__, __LINE__); return -ENOMEM; } pti_tty_driver->driver_name = DRIVERNAME; pti_tty_driver->name = TTYNAME; pti_tty_driver->major = 0; pti_tty_driver->minor_start = PTITTY_MINOR_START; pti_tty_driver->type = TTY_DRIVER_TYPE_SYSTEM; pti_tty_driver->subtype = SYSTEM_TYPE_SYSCONS; pti_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; pti_tty_driver->init_termios = tty_std_termios; tty_set_operations(pti_tty_driver, &pti_tty_driver_ops); retval = tty_register_driver(pti_tty_driver); if (retval) { pr_err("%s(%d): TTY registration failed of pti driver\n", __func__, __LINE__); pr_err("%s(%d): Error value returned: %d\n", __func__, __LINE__, retval); pti_tty_driver = NULL; return retval; } retval = pci_register_driver(&pti_pci_driver); if (retval) { pr_err("%s(%d): PCI registration failed of pti driver\n", __func__, __LINE__); pr_err("%s(%d): Error value returned: %d\n", __func__, __LINE__, retval); tty_unregister_driver(pti_tty_driver); pr_err("%s(%d): Unregistering TTY part of pti driver\n", __func__, __LINE__); pti_tty_driver = NULL; return retval; } return retval; } /** * pti_exit()- Unregisters this module as a tty and pci driver. */ static void __exit pti_exit(void) { int retval; tty_unregister_device(pti_tty_driver, 0); tty_unregister_device(pti_tty_driver, 1); retval = tty_unregister_driver(pti_tty_driver); if (retval) { pr_err("%s(%d): TTY unregistration failed of pti driver\n", __func__, __LINE__); pr_err("%s(%d): Error value returned: %d\n", __func__, __LINE__, retval); } pci_unregister_driver(&pti_pci_driver); retval = misc_deregister(&pti_char_driver); if (retval) { pr_err("%s(%d): CHAR unregistration failed of pti driver\n", __func__, __LINE__); pr_err("%s(%d): Error value returned: %d\n", __func__, __LINE__, retval); } unregister_console(&pti_console); return; } module_init(pti_init); module_exit(pti_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ken Mills, Jay Freyensee"); MODULE_DESCRIPTION("PTI Driver");