soc/intel/common/uart: Add support for enabling UART debug controller on resume

It has been observed on a number of platforms (baytrail, kaby lake)
that if serial console is not enabled in coreboot, but is enabled in
kernel (v4.4), then on resume kernel hangs. In order to fix this, add
support for enabling UART debug port controller on resume.

In order to decide whether UART debug port controller should be
enabled in ramstage, following things are checked in given order:
1. If coreboot has serial console enabled, there is no need to
re-initialize the controller.

2. This special action is taken only for UART debug port controller.

3. If boot is not S3 resume, then initialization is skipped.

4. Callback into SoC to check if it wants to initialize the
controller.

If all the above conditions are met, then UART debug port controller
is initialized and taken out of reset.

BUG=b:64030366
TEST=Verified with the entire patchset series that:
1. If coreboot does not have serial console enabled, but Linux kernel
has console enabled, then on resume, coreboot initializes UART debug
port controller.

2. If coreboot and Linux do not have serial console enabled, then
coreboot does not initialize UART debug port controller.

3. If coreboot has serial console enabled, there is no change in
behavior.

Change-Id: Ic936ac2a787fdc83935103c3ce4ed8f124a97a89
Signed-off-by: Furquan Shaikh <furquan@chromium.org>
Reviewed-on: https://review.coreboot.org/20835
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net>
This commit is contained in:
Furquan Shaikh 2017-08-04 16:12:19 -07:00 committed by Furquan Shaikh
parent 3b90b5f129
commit a8198eb9ad
3 changed files with 133 additions and 3 deletions

View File

@ -18,6 +18,7 @@
#include <arch/io.h> #include <arch/io.h>
#include <device/device.h> #include <device/device.h>
#include <stdint.h>
/* /*
* Common routine to initialize UART controller PCI config space, take it out of * Common routine to initialize UART controller PCI config space, take it out of
@ -25,7 +26,44 @@
*/ */
void uart_common_init(device_t dev, uintptr_t baseaddr); void uart_common_init(device_t dev, uintptr_t baseaddr);
/*
* Check if UART debug controller is initialized
* Returns:
* true = If debug controller PCI config space is initialized and device is
* out of reset
* false = otherwise
*/
bool uart_debug_controller_is_initialized(void);
/*
* Check if dev corresponds to UART debug port controller.
*
* Returns:
* true: UART dev is debug port
* false: otherwise
*/
bool uart_is_debug_controller(struct device *dev);
/**************************** SoC callbacks ***********************************/
void pch_uart_read_resources(struct device *dev); void pch_uart_read_resources(struct device *dev);
/*
* Check if UART debug port controller needs to be initialized on resume.
*
* Returns:
* true = when SoC wants common code to do the UART debug port initialization
* false = otherwise
*/
bool pch_uart_init_debug_controller_on_resume(void);
/*
* Get UART debug controller device structure
*
* Returns:
* Pointer to device structure = If device has a UART debug controller.
* NULL = otherwise
*/
device_t pch_uart_get_debug_controller(void);
#endif /* SOC_INTEL_COMMON_BLOCK_UART_H */ #endif /* SOC_INTEL_COMMON_BLOCK_UART_H */

View File

@ -1,2 +1,3 @@
bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_UART) += uart.c bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_UART) += uart.c
ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_UART) += uart.c ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_UART) += uart.c
smm-$(CONFIG_SOC_INTEL_COMMON_BLOCK_UART) += uart.c

View File

@ -12,13 +12,18 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
*/ */
#include <arch/acpi.h>
#include <device/device.h> #include <device/device.h>
#include <device/pci.h> #include <device/pci.h>
#include <device/pci_def.h> #include <device/pci_def.h>
#include <device/pci_ids.h> #include <device/pci_ids.h>
#include <device/pci_ops.h>
#include <intelblocks/lpss.h> #include <intelblocks/lpss.h>
#include <intelblocks/uart.h> #include <intelblocks/uart.h>
#define UART_PCI_ENABLE (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER)
static void uart_lpss_init(uintptr_t baseaddr) static void uart_lpss_init(uintptr_t baseaddr)
{ {
/* Take UART out of reset */ /* Take UART out of reset */
@ -35,11 +40,40 @@ void uart_common_init(device_t dev, uintptr_t baseaddr)
pci_write_config32(dev, PCI_BASE_ADDRESS_0, baseaddr); pci_write_config32(dev, PCI_BASE_ADDRESS_0, baseaddr);
/* Enable memory access and bus master */ /* Enable memory access and bus master */
pci_write_config32(dev, PCI_COMMAND, pci_write_config32(dev, PCI_COMMAND, UART_PCI_ENABLE);
PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
uart_lpss_init(baseaddr); uart_lpss_init(baseaddr);
}
__attribute__((weak)) device_t pch_uart_get_debug_controller(void)
{
/*
* device_t can either be a pointer to struct device (e.g. ramstage) or
* a simple integer (e.g. SMM) depending upon whether __SIMPLE_DEVICE__
* is defined for the stage. Thus, the return requires additional
* casting to uintptr_t.
*/
return (device_t)(uintptr_t)NULL;
}
bool uart_debug_controller_is_initialized(void)
{
device_t dev;
uintptr_t base;
dev = pch_uart_get_debug_controller();
if (!dev)
return false;
base = pci_read_config32(dev, PCI_BASE_ADDRESS_0) & ~0xFFF;
if (!base)
return false;
if ((pci_read_config32(dev, PCI_COMMAND) & UART_PCI_ENABLE)
!= UART_PCI_ENABLE)
return false;
return !lpss_is_controller_in_reset(base);
} }
#if ENV_RAMSTAGE #if ENV_RAMSTAGE
@ -49,10 +83,67 @@ __attribute__((weak)) void pch_uart_read_resources(struct device *dev)
pci_dev_read_resources(dev); pci_dev_read_resources(dev);
} }
__attribute__((weak)) bool pch_uart_init_debug_controller_on_resume(void)
{
/* By default, do not initialize controller. */
return false;
}
bool uart_is_debug_controller(struct device *dev)
{
return dev == pch_uart_get_debug_controller();
}
/*
* This is a workaround to enable UART controller for the debug port if:
* 1. CONSOLE_SERIAL is not enabled in coreboot, and
* 2. This boot is S3 resume, and
* 3. SoC wants to initialize debug UART controller.
*
* This workaround is required because Linux kernel hangs on resume if console
* is not enabled in coreboot, but it is enabled in kernel and not suspended.
*/
static bool uart_controller_needs_init(struct device *dev)
{
/*
* If coreboot has CONSOLE_SERIAL enabled, the skip re-initalizing
* controller here.
*/
if (IS_ENABLED(CONFIG_CONSOLE_SERIAL))
return false;
/* If this device does not correspond to debug port, then skip. */
if (!uart_is_debug_controller(dev))
return false;
/* Initialize UART controller only on S3 resume. */
if (!acpi_is_wakeup_s3())
return false;
/*
* Call SoC specific routine to confirm it wants to initialize
* controller.
*/
return pch_uart_init_debug_controller_on_resume();
}
static void uart_common_enable_resources(struct device *dev)
{
pci_dev_enable_resources(dev);
if (uart_controller_needs_init(dev)) {
uintptr_t base;
base = pci_read_config32(dev, PCI_BASE_ADDRESS_0) & ~0xFFF;
if (base)
uart_lpss_init(base);
}
}
static struct device_operations device_ops = { static struct device_operations device_ops = {
.read_resources = &pch_uart_read_resources, .read_resources = &pch_uart_read_resources,
.set_resources = &pci_dev_set_resources, .set_resources = &pci_dev_set_resources,
.enable_resources = &pci_dev_enable_resources, .enable_resources = &uart_common_enable_resources,
}; };
static const unsigned short pci_device_ids[] = { static const unsigned short pci_device_ids[] = {