* Copyright 2010-2017 Intel Corporation.
* 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.
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed Knights Ferry,
* and the Intel product codenamed Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
* Intel offers no warranty of any kind regarding the code. This code is
* licensed on an "AS IS" basis and Intel is not obligated to provide
* any support, assistance, installation, training, or other services
* of any kind. Intel is also not obligated to provide any updates,
* enhancements or extensions. Intel specifically disclaims any warranty
* of merchantability, non-infringement, fitness for any particular
* purpose, and any other warranty.
* Further, Intel disclaims all liability of any kind, including but
* not limited to liability for infringement of any proprietary rights,
* relating to the use of the code, even if Intel is notified of the
* possibility of such liability. Except as expressly stated in an Intel
* license agreement provided with this code and agreed upon with Intel,
* no license, express or implied, by estoppel or otherwise, to any
* intellectual property rights is granted herein.
/* TODO: Improve debug messages */
static int micvcons_open(struct tty_struct
* tty
, struct file
* filp
);
static void micvcons_close(struct tty_struct
* tty
, struct file
* filp
);
static int micvcons_write(struct tty_struct
* tty
, const unsigned char *buf
,
static int micvcons_write_room(struct tty_struct
*tty
);
static void micvcons_set_termios(struct tty_struct
*tty
, struct ktermios
* old
);
static void micvcons_timeout(struct timer_list
*);
static void micvcons_throttle(struct tty_struct
*tty
);
static void micvcons_unthrottle(struct tty_struct
*tty
);
static void micvcons_wakeup_readbuf(struct work_struct
*work
);
static int micvcons_resume(struct _mic_ctx_t
*mic_ctx
);
static struct tty_operations micvcons_tty_ops
= {
.write_room
= micvcons_write_room
,
.set_termios
= micvcons_set_termios
,
.throttle
= micvcons_throttle
,
.unthrottle
= micvcons_unthrottle
,
static struct tty_driver
*micvcons_tty
= NULL
;
static u16 extra_timeout
= 0;
static u8 restart_timer_flag
= MICVCONS_TIMER_RESTART
;
static spinlock_t timer_list_lock
;
// The timer API introduced in Linux 4.14.0 includes a from_timer() macro built
// atop the container_of() macro. As the kernel moves away from using
// timer_list.data, it quasi-assumes that the timer_list struct will be
// contained in some larger struct that also contains the data, hence this
// timer_wrapper struct since it allows us to avoid any serious code changes to
static struct timer_wrapper
{
struct timer_list vcons_timer
;
struct list_head timer_list_head
;
micvcons_create(int num_bds
)
INIT_LIST_HEAD(&timer_wrapper
.timer_list_head
);
micvcons_tty
= alloc_tty_driver(num_bds
);
micvcons_tty
->owner
= THIS_MODULE
;
micvcons_tty
->driver_name
= MICVCONS_DEVICE_NAME
;
micvcons_tty
->name
= MICVCONS_DEVICE_NAME
;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0))
micvcons_tty
->minor_num
= num_bds
;
micvcons_tty
->minor_start
= 0;
micvcons_tty
->type
= TTY_DRIVER_TYPE_SERIAL
;
micvcons_tty
->subtype
= SERIAL_TYPE_NORMAL
;
micvcons_tty
->flags
= TTY_DRIVER_REAL_RAW
| TTY_DRIVER_DYNAMIC_DEV
;
micvcons_tty
->init_termios
= tty_std_termios
;
micvcons_tty
->init_termios
.c_iflag
= IGNCR
;
micvcons_tty
->init_termios
.c_oflag
= 0;
micvcons_tty
->init_termios
.c_cflag
= B9600
| CS8
| CREAD
| HUPCL
| CLOCAL
;
micvcons_tty
->init_termios
.c_lflag
= 0;
tty_set_operations(micvcons_tty
, &micvcons_tty_ops
);
if ((ret
= tty_register_driver(micvcons_tty
)) != 0) {
printk("Failed to register vcons tty driver\n");
put_tty_driver(micvcons_tty
);
for (bd
= 0; bd
< num_bds
; bd
++) {
port
= &mic_data
.dd_ports
[bd
];
port
->dp_bdinfo
= mic_data
.dd_bi
[bd
];
spin_lock_init(&port
->dp_lock
);
mutex_init (&port
->dp_mutex
);
bd_info
= (bd_info_t
*)port
->dp_bdinfo
;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0))
tty_port_init(&port
->port
);
dev
= tty_port_register_device(&port
->port
, micvcons_tty
, bd
, NULL
);
dev
= tty_register_device(micvcons_tty
, bd
, NULL
);
printk("Failed to register vcons tty device\n");
snprintf(wq_name
, sizeof(wq_name
), "VCONS MIC %d", bd
);
port
->dp_wq
= __mic_create_singlethread_workqueue(wq_name
);
printk(KERN_ERR
"%s: create_singlethread_workqueue\n",
tty_unregister_device(micvcons_tty
, bd
);
INIT_WORK(&port
->dp_wakeup_read_buf
, micvcons_wakeup_readbuf
);
timer_setup(&timer_wrapper
.vcons_timer
, micvcons_timeout
, 0);
void micvcons_destroy(int num_bds
)
for (bd
= 0; bd
< num_bds
; bd
++) {
port
= &mic_data
.dd_ports
[bd
];
destroy_workqueue(port
->dp_wq
);
tty_unregister_device(micvcons_tty
, bd
);
ret
= tty_unregister_driver(micvcons_tty
);
put_tty_driver(micvcons_tty
);
printk(KERN_ERR
"tty unregister_driver failed with code %d\n", ret
);
micvcons_open(struct tty_struct
* tty
, struct file
* filp
)
micvcons_port_t
*port
= &mic_data
.dd_ports
[tty
->index
];
mic_ctx_t
*mic_ctx
= get_per_dev_ctx(tty
->index
);
mutex_lock(&port
->dp_mutex
);
spin_lock_bh(&port
->dp_lock
);
if ((filp
->f_flags
& O_ACCMODE
) != O_RDONLY
) {
if ((filp
->f_flags
& O_ACCMODE
) != O_WRONLY
) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0))
port
->dp_vcons
= &mic_ctx
->bi_vcons
;
ret
= micvcons_start(mic_ctx
);
spin_lock(&timer_list_lock
);
list_add_tail_rcu(&port
->list_member
, &timer_wrapper
.timer_list_head
);
if (list_is_singular(&timer_wrapper
.timer_list_head
)) {
restart_timer_flag
= MICVCONS_TIMER_RESTART
;
mod_timer(&timer_wrapper
.vcons_timer
, jiffies
+
msecs_to_jiffies(MICVCONS_SHORT_TIMEOUT
));
spin_unlock(&timer_list_lock
);
spin_unlock_bh(&port
->dp_lock
);
mutex_unlock(&port
->dp_mutex
);
micvcons_del_timer_entry(micvcons_port_t
*port
)
spin_lock(&timer_list_lock
);
list_del_rcu(&port
->list_member
);
if (list_empty(&timer_wrapper
.timer_list_head
)) {
restart_timer_flag
= MICVCONS_TIMER_SHUTDOWN
;
spin_unlock(&timer_list_lock
);
del_timer_sync(&timer_wrapper
.vcons_timer
);
spin_unlock(&timer_list_lock
);
micvcons_close(struct tty_struct
* tty
, struct file
* filp
)
micvcons_port_t
*port
= (micvcons_port_t
*)tty
->driver_data
;
mutex_lock(&port
->dp_mutex
);
micvcons_del_timer_entry(port
);
flush_workqueue(port
->dp_wq
);
spin_lock_bh(&port
->dp_lock
);
if (port
->dp_reader
== filp
)
if (port
->dp_writer
== filp
)
spin_unlock_bh(&port
->dp_lock
);
mutex_unlock(&port
->dp_mutex
);
micvcons_write(struct tty_struct
* tty
, const unsigned char *buf
, int count
)
micvcons_port_t
*port
= (micvcons_port_t
*)tty
->driver_data
;
mic_ctx_t
*mic_ctx
= get_per_dev_ctx(tty
->index
);
struct vcons_buf
*vcons_host_header
;
spin_lock_bh(&port
->dp_lock
);
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
if (vcons_host_header
->mic_magic
== MIC_VCONS_SLEEPING
) {
status
= micvcons_resume(mic_ctx
);
/* If card can not wakeup, it is dead. */
if (vcons_host_header
->mic_magic
!= MIC_VCONS_READY
)
bytes
= micvcons_port_write(port
, buf
, count
);
mic_send_hvc_intr(mic_ctx
);
spin_unlock_bh(&port
->dp_lock
);
micvcons_del_timer_entry(port
);
micvcons_write_room(struct tty_struct
*tty
)
micvcons_port_t
*port
= (micvcons_port_t
*)tty
->driver_data
;
spin_lock_bh(&port
->dp_lock
);
room
= micscif_rb_space(port
->dp_out
);
spin_unlock_bh(&port
->dp_lock
);
micvcons_set_termios(struct tty_struct
*tty
, struct ktermios
* old
)
micvcons_readchars(micvcons_port_t
*port
)
len
= micscif_rb_count(port
->dp_in
, sizeof(buf
));
get_count
= min(len
, (int)sizeof(buf
));
ret
= micscif_rb_get_next(port
->dp_in
, buf
, get_count
);
micscif_rb_update_read_ptr(port
->dp_in
);
if (port
->dp_reader
&& port
->dp_canread
) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0))
if ((bytes_read
= tty_insert_flip_string(
&port
->port
, buf
, get_count
)) != 0)
tty_flip_buffer_push(&port
->port
);
bytes_read
= tty_insert_flip_string(port
->dp_tty
,
tty_flip_buffer_push(port
->dp_tty
);
bytes_total
+= bytes_read
;
if (bytes_read
!= get_count
) {
printk(KERN_WARNING
"dropping characters: \
bytes_read %d, get_count %d\n",
micvcons_initport(micvcons_port_t
*port
)
struct vcons_buf
*vcons_host_header
;
struct vcons_mic_header
*vcons_mic_header
;
char *mic_hdr
, *mic_buf
, *host_buf
;
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
if (!vcons_host_header
) {
printk(KERN_ERR
"vcons_host_header NULL\n");
host_buf
= (char *)port
->dp_vcons
->dc_buf_virt
;
printk(KERN_ERR
"host_buf NULL\n");
if (port
->dp_bdinfo
->bi_ctx
.bi_family
== FAMILY_ABR
) {
set_pci_aperture(&port
->dp_bdinfo
->bi_ctx
,
(port
->dp_bdinfo
->bi_ctx
.aper
.len
- PAGE_SIZE
) >> PAGE_SHIFT
,
vcons_host_header
->i_hdr_addr
& PAGE_MASK
, PAGE_SIZE
);
mic_hdr
= port
->dp_bdinfo
->bi_ctx
.aper
.va
+
port
->dp_bdinfo
->bi_ctx
.aper
.len
- PAGE_SIZE
;
mic_buf
= mic_hdr
+ PAGE_SIZE
/2;
mic_hdr
= port
->dp_bdinfo
->bi_ctx
.aper
.va
+ vcons_host_header
->i_hdr_addr
;
mic_buf
= port
->dp_bdinfo
->bi_ctx
.aper
.va
+ vcons_host_header
->i_buf_addr
;
port
->dp_in
= kmalloc(sizeof(struct micscif_rb
), GFP_ATOMIC
);
port
->dp_out
= kmalloc(sizeof(struct micscif_rb
), GFP_ATOMIC
);
vcons_mic_header
= (struct vcons_mic_header
*)mic_hdr
;
micscif_rb_init(port
->dp_in
,
&vcons_host_header
->o_wr
,
vcons_host_header
->o_size
);
micscif_rb_init(port
->dp_out
, &vcons_host_header
->i_rd
,
vcons_host_header
->i_size
);
writel(MIC_VCONS_HOST_OPEN
, &vcons_mic_header
->host_status
);
micvcons_readport(micvcons_port_t
*port
)
int num_chars_read
= 0, status
;
static uint32_t prev_mic_magic
;
struct vcons_buf
*vcons_host_header
;
if (!port
|| !port
->dp_vcons
)
spin_lock_bh(&port
->dp_lock
);
spin_unlock_bh(&port
->dp_lock
);
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
if ((vcons_host_header
->mic_magic
!= MIC_VCONS_READY
) &&
(vcons_host_header
->mic_magic
!= MIC_VCONS_SLEEPING
)) {
if ((vcons_host_header
->mic_magic
== MIC_VCONS_RB_VER_ERR
)
&& (vcons_host_header
->mic_magic
!= prev_mic_magic
)) {
printk(KERN_ERR
"Card and host ring buffer versions mismatch.");
printk(KERN_ERR
"Card version: %d, Host version: %d \n",
vcons_host_header
->mic_rb_ver
,
vcons_host_header
->host_rb_ver
);
status
= micvcons_initport(port
);
spin_unlock_bh(&port
->dp_lock
);
if (vcons_host_header
->mic_magic
== MIC_VCONS_SLEEPING
) {
* If the card is sleeping and there is data in the
* buffer, schedule work in a work queue to wake-up
* the card and read from the buffer.
if (micscif_rb_count(port
->dp_in
, 1))
&port
->dp_wakeup_read_buf
);
num_chars_read
= micvcons_readchars(port
);
tty_wakeup(port
->dp_tty
);
prev_mic_magic
= vcons_host_header
->mic_magic
;
spin_unlock_bh(&port
->dp_lock
);
micvcons_wakeup_readbuf(struct work_struct
*work
)
struct vcons_buf
*vcons_host_header
;
port
= container_of(work
, micvcons_port_t
, dp_wakeup_read_buf
);
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
spin_lock_bh(&port
->dp_lock
);
status
= micvcons_resume(get_per_dev_ctx(port
->dp_tty
->index
));
micvcons_readchars(port
);
tty_wakeup(port
->dp_tty
);
/* If card can not wakeup, it is dead. */
spin_unlock_bh(&port
->dp_lock
);
micvcons_del_timer_entry(port
);
micvcons_timeout(struct timer_list
*t
)
struct timer_wrapper
*tm_wrap
= from_timer(tm_wrap
, t
, vcons_timer
);
struct list_head
*timer_list_ptr
= &tm_wrap
->timer_list_head
;
list_for_each_entry_rcu(port
, timer_list_ptr
, list_member
) {
num_chars_read
= micvcons_readport(port
);
spin_lock(&timer_list_lock
);
if (restart_timer_flag
== MICVCONS_TIMER_RESTART
) {
extra_timeout
= (console_active
? 0 :
extra_timeout
+ MICVCONS_SHORT_TIMEOUT
);
extra_timeout
= min(extra_timeout
, (u16
)MICVCONS_MAX_TIMEOUT
);
mod_timer(&timer_wrapper
.vcons_timer
, jiffies
+
msecs_to_jiffies(MICVCONS_SHORT_TIMEOUT
+extra_timeout
));
spin_unlock(&timer_list_lock
);
micvcons_throttle(struct tty_struct
*tty
)
micvcons_port_t
*port
= (micvcons_port_t
*)tty
->driver_data
;
micvcons_unthrottle(struct tty_struct
*tty
)
micvcons_port_t
*port
= (micvcons_port_t
*)tty
->driver_data
;
int micvcons_start(mic_ctx_t
*mic_ctx
)
struct vcons_buf
*vcons_host_header
;
micvcons_port_t
*port
= mic_ctx
->bd_info
->bi_port
;
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
if (vcons_host_header
->mic_magic
== MIC_VCONS_SLEEPING
) {
status
= micvcons_resume(mic_ctx
);
if (vcons_host_header
->mic_magic
== MIC_VCONS_READY
) {
status
= micvcons_initport(port
);
int micvcons_port_write(struct micvcons_port
*port
, const unsigned char *buf
,
bytes
= min(count
, micscif_rb_space(port
->dp_out
));
ret
= micscif_rb_write(port
->dp_out
, (void *)buf
, bytes
);
micscif_rb_commit(port
->dp_out
);
* micvcons_stop - cleans up before a node is rebooted
* @ mic_ctx: node to clean up
* Called before rebooting a node, reads remaining characters
* from the node's vcons output buffer, resets the input/output
* ring buffers so that things work when the node comes up again
micvcons_stop(mic_ctx_t
*mic_ctx
)
struct vcons_buf
*vcons_host_header
;
port
= mic_ctx
->bd_info
->bi_port
;
spin_lock_bh(&port
->dp_lock
);
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
vcons_host_header
->mic_magic
= 0;
spin_unlock_bh(&port
->dp_lock
);
* micvcons_resume - sets the state of a node's console to ready
* @ mic_ctx: node to clean up
* @ return: zero if successful.
* called before resuming a node from PC6. MUST acquire the spinlock
* port->dp_lock with bottom-halves disabled before calling this function.
micvcons_resume(mic_ctx_t
*mic_ctx
)
struct vcons_buf
*vcons_host_header
;
port
= mic_ctx
->bd_info
->bi_port
;
vcons_host_header
= mic_ctx
->bi_vcons
.dc_hdr_virt
;
if (vcons_host_header
->mic_magic
== MIC_VCONS_SLEEPING
) {
vcons_host_header
->mic_magic
= MIC_VCONS_WAKINGUP
;
spin_unlock_bh(&port
->dp_lock
);
status
= micscif_connect_node(mic_get_scifnode_id(mic_ctx
), false);
spin_lock_bh(&port
->dp_lock
);
} while ((status
== 0) &&
(vcons_host_header
->mic_magic
== MIC_VCONS_SLEEPING
));
vcons_host_header
->mic_magic
= MIC_VCONS_READY
;
* micvcons_pm_disconnect_node - Check if a card can be put to sleep in case
* there is any activity on the virtual console. If yes, it also sets the
* internal state of a node's console to sleeping.
* @ node_bitmask: bits set indicate which cards to check.
* Bit-1 for the first, Bit-2 for the second,...
* Ignore Bit-0 which indicates host.
* @ return: bits set indicating which cards can sleep.
* This is called from PM to check if a card can be put to sleep (PC-6 state).
* This is called when the node is disconnected from the SCIF network
* before putting it into the PC6 state where it should no longer
* receive an PCIe transactions until woken up by the host driver.
micvcons_pm_disconnect_node(uint8_t *node_bitmask
, enum disconn_type type
)
if ((type
== DISCONN_TYPE_POWER_MGMT
) && (node_bitmask
)) {
struct vcons_buf
*vcons_host_header
;
for (i
= 0; i
<= mic_data
.dd_numdevs
; i
++) {
if (!get_nodemask_bit(node_bitmask
, i
))
if (!(mic_ctx
= get_per_dev_ctx(i
- 1)))
port
= mic_ctx
->bd_info
->bi_port
;
* If this function is called when virtual console is
* not active, port->dp_vcons needs to be initialized.
port
->dp_vcons
= &mic_ctx
->bi_vcons
;
vcons_host_header
= (struct vcons_buf
*)port
->dp_vcons
->dc_hdr_virt
;
spin_lock_bh(&port
->dp_lock
);
vcons_host_header
->mic_magic
= MIC_VCONS_SLEEPING
;
spin_unlock_bh(&port
->dp_lock
);