| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * (C) Copyright 2022 Microsoft Corporation <www.microsoft.com> |
| * Stephen Carlson <stcarlso@linux.microsoft.com> |
| * |
| * PCI Express Maximum Packet Size (MPS) configuration |
| */ |
| |
| #include <bootretry.h> |
| #include <cli.h> |
| #include <command.h> |
| #include <console.h> |
| #include <dm.h> |
| #include <init.h> |
| #include <asm/processor.h> |
| #include <asm/io.h> |
| #include <pci.h> |
| |
| #define PCI_MPS_SAFE 0 |
| #define PCI_MPS_PEER2PEER 1 |
| |
| static int pci_mps_find_safe(struct udevice *bus, unsigned int *min_mps, |
| unsigned int *n) |
| { |
| struct udevice *dev; |
| int res = 0, addr; |
| unsigned int mpss; |
| u32 regval; |
| |
| if (!min_mps || !n) |
| return -EINVAL; |
| |
| for (device_find_first_child(bus, &dev); |
| dev; |
| device_find_next_child(&dev)) { |
| addr = dm_pci_find_capability(dev, PCI_CAP_ID_EXP); |
| if (addr <= 0) |
| continue; |
| |
| res = dm_pci_read_config32(dev, addr + PCI_EXP_DEVCAP, |
| ®val); |
| if (res != 0) |
| return res; |
| mpss = (unsigned int)(regval & PCI_EXP_DEVCAP_PAYLOAD); |
| *n += 1; |
| if (mpss < *min_mps) |
| *min_mps = mpss; |
| } |
| |
| return res; |
| } |
| |
| static int pci_mps_set_bus(struct udevice *bus, unsigned int target) |
| { |
| struct udevice *dev; |
| u32 mpss, target_mps = (u32)(target << 5); |
| u16 mps; |
| int res = 0, addr; |
| |
| for (device_find_first_child(bus, &dev); |
| dev && res == 0; |
| device_find_next_child(&dev)) { |
| addr = dm_pci_find_capability(dev, PCI_CAP_ID_EXP); |
| if (addr <= 0) |
| continue; |
| |
| res = dm_pci_read_config32(dev, addr + PCI_EXP_DEVCAP, |
| &mpss); |
| if (res != 0) |
| return res; |
| |
| /* Do not set device above its maximum MPSS */ |
| mpss = (mpss & PCI_EXP_DEVCAP_PAYLOAD) << 5; |
| if (target_mps < mpss) |
| mps = (u16)target_mps; |
| else |
| mps = (u16)mpss; |
| res = dm_pci_clrset_config16(dev, addr + PCI_EXP_DEVCTL, |
| PCI_EXP_DEVCTL_PAYLOAD, mps); |
| } |
| |
| return res; |
| } |
| |
| /* |
| * Sets the MPS of each PCI Express device to the specified policy. |
| */ |
| static int pci_mps_set(int policy) |
| { |
| struct udevice *bus; |
| int i, res = 0; |
| /* 0 = 128B, min value for hotplug */ |
| unsigned int mps = 0; |
| |
| if (policy == PCI_MPS_SAFE) { |
| unsigned int min_mps = PCI_EXP_DEVCAP_PAYLOAD_4096B, n = 0; |
| |
| /* Find maximum MPS supported by all devices */ |
| for (i = 0; |
| uclass_get_device_by_seq(UCLASS_PCI, i, &bus) == 0 && |
| res == 0; |
| i++) |
| res = pci_mps_find_safe(bus, &min_mps, &n); |
| |
| /* If no devices were found, do not reconfigure */ |
| if (n == 0) |
| return res; |
| mps = min_mps; |
| } |
| |
| /* This message is checked by the sandbox test */ |
| printf("Setting MPS of all devices to %uB\n", 128U << mps); |
| for (i = 0; |
| uclass_get_device_by_seq(UCLASS_PCI, i, &bus) == 0 && res == 0; |
| i++) |
| res = pci_mps_set_bus(bus, mps); |
| |
| return res; |
| } |
| |
| /* |
| * PCI MPS tuning commands |
| * |
| * Syntax: |
| * pci_mps safe |
| * pci_mps peer2peer |
| */ |
| static int do_pci_mps(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| char cmd = 'u'; |
| int ret = 0; |
| |
| if (argc > 1) |
| cmd = argv[1][0]; |
| |
| switch (cmd) { |
| case 's': /* safe */ |
| ret = pci_mps_set(PCI_MPS_SAFE); |
| break; |
| case 'p': /* peer2peer/hotplug */ |
| ret = pci_mps_set(PCI_MPS_PEER2PEER); |
| break; |
| default: /* usage, help */ |
| goto usage; |
| } |
| |
| return ret; |
| usage: |
| return CMD_RET_USAGE; |
| } |
| |
| /***************************************************/ |
| |
| U_BOOT_LONGHELP(pci_mps, |
| "safe\n" |
| " - Set PCI Express MPS of all devices to safe values\n" |
| "pci_mps peer2peer\n" |
| " - Set PCI Express MPS of all devices to support hotplug and peer-to-peer DMA\n"); |
| |
| U_BOOT_CMD(pci_mps, 2, 0, do_pci_mps, |
| "configure PCI Express MPS", pci_mps_help_text); |