Anonymous avatar Anonymous committed 68fad83

Factor out the SMU fan management code into a new module (powermac_thermal)
that will connect all of the various sensors and fan control modules on
Apple hardware with software-controlled fans (e.g. all G5 systems).

MFC after: 1 month

Comments (0)

Files changed (4)

sys/conf/files.powerpc

 powerpc/powermac/macio.c	optional	powermac pci
 powerpc/powermac/openpic_macio.c optional	powermac pci
 powerpc/powermac/platform_powermac.c optional	powermac
+powerpc/powermac/powermac_thermal.c optional	powermac
 powerpc/powermac/pswitch.c	optional	powermac pswitch
 powerpc/powermac/pmu.c		optional	powermac pmu 
 powerpc/powermac/smu.c		optional	powermac smu 

sys/powerpc/powermac/powermac_thermal.c

+/*-
+ * Copyright (c) 2009-2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/systm.h>
+
+#include <sys/types.h>
+#include <sys/kthread.h>
+#include <sys/malloc.h>
+#include <sys/reboot.h>
+#include <sys/sysctl.h>
+#include <sys/queue.h>
+
+#include "powermac_thermal.h"
+
+static void fan_management_proc(void);
+static void pmac_therm_manage_fans(void);
+
+static struct proc *pmac_them_proc;
+static int enable_pmac_thermal = 1;
+
+static struct kproc_desc pmac_therm_kp = {
+	"pmac_thermal",
+	fan_management_proc,
+	&pmac_them_proc
+};
+
+SYSINIT(pmac_therm_setup, SI_SUB_KTHREAD_IDLE, SI_ORDER_ANY, kproc_start,
+    &pmac_therm_kp);
+SYSCTL_INT(_machdep, OID_AUTO, manage_fans, CTLFLAG_RW | CTLFLAG_TUN,
+    &enable_pmac_thermal, 1, "Enable automatic fan management");
+MALLOC_DEFINE(M_PMACTHERM, "pmactherm", "Powermac Thermal Management");
+
+struct pmac_fan_le {
+	struct pmac_fan			*fan;
+	int				last_val;
+	SLIST_ENTRY(pmac_fan_le)	entries;
+};
+struct pmac_sens_le {
+	struct pmac_therm		*sensor;
+	int				last_val;
+	SLIST_ENTRY(pmac_sens_le)	entries;
+};
+static SLIST_HEAD(pmac_fans, pmac_fan_le) fans = SLIST_HEAD_INITIALIZER(fans);
+static SLIST_HEAD(pmac_sensors, pmac_sens_le) sensors =
+    SLIST_HEAD_INITIALIZER(sensors);
+
+static void
+fan_management_proc(void)
+{
+	/* Nothing to manage? */
+	if (SLIST_EMPTY(&fans))
+		return;
+	
+	while (1) {
+		pmac_therm_manage_fans();
+		pause("pmac_therm", hz);
+	}
+}
+
+static void
+pmac_therm_manage_fans(void)
+{
+	struct pmac_sens_le *sensor;
+	struct pmac_fan_le *fan;
+	int average_excess, max_excess_zone, frac_excess;
+	int nsens, nsens_zone;
+
+	if (!enable_pmac_thermal)
+		return;
+
+	/* Read all the sensors */
+	SLIST_FOREACH(sensor, &sensors, entries) {
+		sensor->last_val = sensor->sensor->read(sensor->sensor);
+		if (sensor->last_val > sensor->sensor->max_temp) {
+			printf("WARNING: Current temperature (%s: %d.%d C) "
+			    "exceeds critical temperature (%d.%d C)! "
+			    "Shutting down!\n", sensor->sensor->name,
+			    sensor->last_val / 10, sensor->last_val % 10,
+			    sensor->sensor->max_temp / 10,
+			    sensor->sensor->max_temp % 10);
+			shutdown_nice(RB_POWEROFF);
+		}
+	}
+
+	/* Set all the fans */
+	SLIST_FOREACH(fan, &fans, entries) {
+		nsens = nsens_zone = 0;
+		average_excess = max_excess_zone = 0;
+		SLIST_FOREACH(sensor, &sensors, entries) {
+			frac_excess = (sensor->last_val -
+			    sensor->sensor->target_temp)*100 /
+			    (sensor->sensor->max_temp -
+			    sensor->sensor->target_temp);
+			if (sensor->sensor->zone == fan->fan->zone) {
+				max_excess_zone = imax(max_excess_zone,
+				    frac_excess);
+				nsens_zone++;
+			}
+			average_excess += frac_excess;
+			nsens++;
+		}
+		average_excess /= nsens;
+
+		/* If there are no sensors in this zone, use the average */
+		if (nsens_zone == 0)
+			max_excess_zone = average_excess;
+		/* No sensors at all? Use default */
+		if (nsens == 0) {
+			fan->fan->set(fan->fan, fan->fan->default_rpm);
+			continue;
+		}
+
+		/*
+		 * Scale the fan linearly in the max temperature in its
+		 * thermal zone.
+		 */
+		fan->fan->set(fan->fan, max_excess_zone *
+		    (fan->fan->max_rpm - fan->fan->min_rpm)/100 +
+		    fan->fan->min_rpm);
+	}
+}
+
+void
+pmac_thermal_fan_register(struct pmac_fan *fan)
+{
+	struct pmac_fan_le *list_entry;
+
+	list_entry = malloc(sizeof(struct pmac_fan_le), M_PMACTHERM,
+	    M_ZERO | M_WAITOK);
+	list_entry->fan = fan;
+
+	SLIST_INSERT_HEAD(&fans, list_entry, entries);
+}
+
+void
+pmac_thermal_sensor_register(struct pmac_therm *sensor)
+{
+	struct pmac_sens_le *list_entry;
+
+	list_entry = malloc(sizeof(struct pmac_sens_le), M_PMACTHERM,
+	    M_ZERO | M_WAITOK);
+	list_entry->sensor = sensor;
+
+	SLIST_INSERT_HEAD(&sensors, list_entry, entries);
+}
+

sys/powerpc/powermac/powermac_thermal.h

+/*-
+ * Copyright (c) 2009-2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _POWERPC_POWERMAC_POWERMAC_THERMAL_H
+#define _POWERPC_POWERMAC_POWERMAC_THERMAL_H
+
+struct pmac_fan {
+	int min_rpm, max_rpm, default_rpm;
+	
+	char name[32];
+	int zone;
+
+	int (*read)(struct pmac_fan *);
+	int (*set)(struct pmac_fan *, int value);
+};
+
+struct pmac_therm {
+	int target_temp, max_temp;	/* Tenths of a degree K */
+	
+	char name[32];
+	int zone;
+
+	int (*read)(struct pmac_therm *);
+};
+
+void pmac_thermal_fan_register(struct pmac_fan *);
+void pmac_thermal_sensor_register(struct pmac_therm *);
+
+#endif

sys/powerpc/powermac/smu.c

 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 #include <powerpc/powermac/macgpiovar.h>
+#include <powerpc/powermac/powermac_thermal.h>
 
 #include "clock_if.h"
 #include "iicbus_if.h"
 STAILQ_HEAD(smu_cmdq, smu_cmd);
 
 struct smu_fan {
+	struct pmac_fan fan;
+	device_t dev;
 	cell_t	reg;
-	cell_t	min_rpm;
-	cell_t	max_rpm;
-	cell_t	unmanaged_rpm;
-	char	location[32];
 
 	int	old_style;
 	int	setpoint;
 };
 
 struct smu_sensor {
+	struct pmac_therm therm;
+	device_t dev;
+
 	cell_t	reg;
-	char	location[32];
 	enum {
 		SMU_CURRENT_SENSOR,
 		SMU_VOLTAGE_SENSOR,
 	uint16_t	sc_slots_pow_scale;
 	int16_t		sc_slots_pow_offset;
 
-	/* Thermal management parameters */
-	int		sc_target_temp;		/* Default 55 C */
-	int		sc_critical_temp;	/* Default 90 C */
-
 	struct cdev 	*sc_leddev;
 };
 
 static void	smu_attach_i2c(device_t dev, phandle_t i2croot);
 static void	smu_attach_fans(device_t dev, phandle_t fanroot);
 static void	smu_attach_sensors(device_t dev, phandle_t sensroot);
-static void	smu_fan_management_proc(void *xdev);
-static void	smu_manage_fans(device_t smu);
 static void	smu_set_sleepled(void *xdev, int onoff);
 static int	smu_server_mode(SYSCTL_HANDLER_ARGS);
 static void	smu_doorbell_intr(void *xdev);
 	sc->sc_slots_pow_offset = (data[6] << 8) + data[7];
 
 	/*
-	 * Set up simple-minded thermal management.
-	 */
-	sc->sc_target_temp = 55;
-	sc->sc_critical_temp = 90;
-
-	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
-	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
-	    "target_temp", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_target_temp,
-	    sizeof(int), "Target temperature (C)");
-	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
-	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
-	    "critical_temp", CTLTYPE_INT | CTLFLAG_RW,
-	    &sc->sc_critical_temp, sizeof(int), "Critical temperature (C)");
-
-	kproc_create(smu_fan_management_proc, dev, &sc->sc_fanmgt_proc,
-	    RFHIGHPID, 0, "smu_thermal");
-
-	/*
 	 * Set up LED interface
 	 */
 	sc->sc_leddev = led_create(smu_set_sleepled, dev, "sleepled");
  */
 
 static int
-smu_fan_set_rpm(device_t smu, struct smu_fan *fan, int rpm)
+smu_fan_set_rpm(struct smu_fan *fan, int rpm)
 {
+	device_t smu = fan->dev;
 	struct smu_cmd cmd;
 	int error;
 
 	error = EIO;
 
 	/* Clamp to allowed range */
-	rpm = max(fan->min_rpm, rpm);
-	rpm = min(fan->max_rpm, rpm);
+	rpm = max(fan->fan.min_rpm, rpm);
+	rpm = min(fan->fan.max_rpm, rpm);
 
 	/*
 	 * Apple has two fan control mechanisms. We can't distinguish
 }
 
 static int
-smu_fan_read_rpm(device_t smu, struct smu_fan *fan)
+smu_fan_read_rpm(struct smu_fan *fan)
 {
+	device_t smu = fan->dev;
 	struct smu_cmd cmd;
 	int rpm, error;
 
 	sc = device_get_softc(smu);
 	fan = &sc->sc_fans[arg2];
 
-	rpm = smu_fan_read_rpm(smu, fan);
+	rpm = smu_fan_read_rpm(fan);
 	if (rpm < 0)
 		return (rpm);
 
 
 	sc->sc_lastuserchange = time_uptime;
 
-	return (smu_fan_set_rpm(smu, fan, rpm));
+	return (smu_fan_set_rpm(fan, rpm));
 }
 
 static void
 		if (strcmp(type, "fan-rpm-control") != 0)
 			continue;
 
+		fan->dev = dev;
 		fan->old_style = 0;
 		OF_getprop(child, "reg", &fan->reg, sizeof(cell_t));
-		OF_getprop(child, "min-value", &fan->min_rpm, sizeof(cell_t));
-		OF_getprop(child, "max-value", &fan->max_rpm, sizeof(cell_t));
+		OF_getprop(child, "min-value", &fan->fan.min_rpm, sizeof(int));
+		OF_getprop(child, "max-value", &fan->fan.max_rpm, sizeof(int));
+		OF_getprop(child, "zone", &fan->fan.zone, sizeof(int));
 
-		if (OF_getprop(child, "unmanaged-value", &fan->unmanaged_rpm,
-		    sizeof(cell_t)) != sizeof(cell_t))
-			fan->unmanaged_rpm = fan->max_rpm;
+		if (OF_getprop(child, "unmanaged-value", &fan->fan.default_rpm,
+		    sizeof(int)) != sizeof(int))
+			fan->fan.default_rpm = fan->fan.max_rpm;
 
-		fan->setpoint = smu_fan_read_rpm(dev, fan);
+		fan->setpoint = smu_fan_read_rpm(fan);
 
-		OF_getprop(child, "location", fan->location,
-		    sizeof(fan->location));
+		OF_getprop(child, "location", fan->fan.name,
+		    sizeof(fan->fan.name));
 	
 		/* Add sysctls */
-		for (i = 0; i < strlen(fan->location); i++) {
-			sysctl_name[i] = tolower(fan->location[i]);
+		for (i = 0; i < strlen(fan->fan.name); i++) {
+			sysctl_name[i] = tolower(fan->fan.name[i]);
 			if (isspace(sysctl_name[i]))
 				sysctl_name[i] = '_';
 		}
 		oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid),
 		    OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information");
 		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minrpm",
-		    CTLTYPE_INT | CTLFLAG_RD, &fan->min_rpm, sizeof(cell_t),
+		    CTLTYPE_INT | CTLFLAG_RD, &fan->fan.min_rpm, sizeof(int),
 		    "Minimum allowed RPM");
 		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxrpm",
-		    CTLTYPE_INT | CTLFLAG_RD, &fan->max_rpm, sizeof(cell_t),
+		    CTLTYPE_INT | CTLFLAG_RD, &fan->fan.max_rpm, sizeof(int),
 		    "Maximum allowed RPM");
 		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm",
 		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev,
 		    sc->sc_nfans, smu_fanrpm_sysctl, "I", "Fan RPM");
 
+		fan->fan.read = (int (*)(struct pmac_fan *))smu_fan_read_rpm;
+		fan->fan.set = (int (*)(struct pmac_fan *, int))smu_fan_set_rpm;
+		pmac_thermal_fan_register(&fan->fan);
+
 		fan++;
 		sc->sc_nfans++;
 	}
 }
 
 static int
-smu_sensor_read(device_t smu, struct smu_sensor *sens, int *val)
+smu_sensor_read(struct smu_sensor *sens)
 {
+	device_t smu = sens->dev;
 	struct smu_cmd cmd;
 	struct smu_softc *sc;
 	int64_t value;
 
 	error = smu_run_cmd(smu, &cmd, 1);
 	if (error != 0)
-		return (error);
+		return (-1);
 	
 	sc = device_get_softc(smu);
 	value = (cmd.data[0] << 8) | cmd.data[1];
 		value += ((int64_t)sc->sc_cpu_diode_offset) << 9;
 		value <<= 1;
 
-		/* Convert from 16.16 fixed point degC into integer C. */
-		value >>= 16;
+		/* Convert from 16.16 fixed point degC into integer 0.1 K. */
+		value = 10*(value >> 16) + 2732;
 		break;
 	case SMU_VOLTAGE_SENSOR:
 		value *= sc->sc_cpu_volt_scale;
 		break;
 	}
 
-	*val = value;
-	return (0);
+	return (value);
 }
 
 static int
 	sc = device_get_softc(smu);
 	sens = &sc->sc_sensors[arg2];
 
-	error = smu_sensor_read(smu, sens, &value);
-	if (error != 0)
-		return (error);
+	value = smu_sensor_read(sens);
+	if (value < 0)
+		return (EBUSY);
 
 	error = sysctl_handle_int(oidp, &value, 0, req);
 
 		char sysctl_name[40], sysctl_desc[40];
 		const char *units;
 
+		sens->dev = dev;
 		OF_getprop(child, "device_type", type, sizeof(type));
 
 		if (strcmp(type, "current-sensor") == 0) {
 		}
 
 		OF_getprop(child, "reg", &sens->reg, sizeof(cell_t));
-		OF_getprop(child, "location", sens->location,
-		    sizeof(sens->location));
+		OF_getprop(child, "zone", &sens->therm.zone, sizeof(int));
+		OF_getprop(child, "location", sens->therm.name,
+		    sizeof(sens->therm.name));
 
-		for (i = 0; i < strlen(sens->location); i++) {
-			sysctl_name[i] = tolower(sens->location[i]);
+		for (i = 0; i < strlen(sens->therm.name); i++) {
+			sysctl_name[i] = tolower(sens->therm.name[i]);
 			if (isspace(sysctl_name[i]))
 				sysctl_name[i] = '_';
 		}
 		sysctl_name[i] = 0;
 
-		sprintf(sysctl_desc,"%s (%s)", sens->location, units);
+		sprintf(sysctl_desc,"%s (%s)", sens->therm.name, units);
 
 		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO,
 		    sysctl_name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
-		    dev, sc->sc_nsensors, smu_sensor_sysctl, "I", sysctl_desc);
+		    dev, sc->sc_nsensors, smu_sensor_sysctl, 
+		    (sens->type == SMU_TEMP_SENSOR) ? "IK" : "I", sysctl_desc);
+
+		if (sens->type == SMU_TEMP_SENSOR) {
+			/* Make up some numbers */
+			sens->therm.target_temp = 500 + 2732; /* 50 C */
+			sens->therm.max_temp = 900 + 2732; /* 90 C */
+
+			sens->therm.read =
+			    (int (*)(struct pmac_therm *))smu_sensor_read;
+			pmac_thermal_sensor_register(&sens->therm);
+		}
 
 		sens++;
 		sc->sc_nsensors++;
 }
 
 static void
-smu_fan_management_proc(void *xdev)
-{
-	device_t smu = xdev;
-
-	while(1) {
-		smu_manage_fans(smu);
-		pause("smu", SMU_FANMGT_INTERVAL * hz / 1000);
-	}
-}
-
-static void
-smu_manage_fans(device_t smu)
-{
-	struct smu_softc *sc;
-	int i, maxtemp, temp, factor, error;
-
-	sc = device_get_softc(smu);
-
-	maxtemp = 0;
-	for (i = 0; i < sc->sc_nsensors; i++) {
-		if (sc->sc_sensors[i].type != SMU_TEMP_SENSOR)
-			continue;
-
-		error = smu_sensor_read(smu, &sc->sc_sensors[i], &temp);
-		if (error == 0 && temp > maxtemp)
-			maxtemp = temp;
-	}
-
-	if (maxtemp > sc->sc_critical_temp) {
-		device_printf(smu, "WARNING: Current system temperature (%d C) "
-		    "exceeds critical temperature (%d C)! Shutting down!\n",
-		    maxtemp, sc->sc_critical_temp);
-		shutdown_nice(RB_POWEROFF);
-	}
-
-	if (maxtemp - sc->sc_target_temp > 20)
-		device_printf(smu, "WARNING: Current system temperature (%d C) "
-		    "more than 20 degrees over target temperature (%d C)!\n",
-		    maxtemp, sc->sc_target_temp);
-
-	if (time_uptime - sc->sc_lastuserchange < 3) {
-		/*
-		 * If we have heard from a user process in the last 3 seconds,
-		 * go away.
-		 */
-
-		return;
-	}
-
-	if (maxtemp < 10) { /* Bail if no good sensors */
-		for (i = 0; i < sc->sc_nfans; i++) 
-			smu_fan_set_rpm(smu, &sc->sc_fans[i],
-			    sc->sc_fans[i].unmanaged_rpm);
-		return;
-	}
-
-	if (maxtemp - sc->sc_target_temp > 4) 
-		factor = 110;
-	else if (maxtemp - sc->sc_target_temp > 1) 
-		factor = 105;
-	else if (sc->sc_target_temp - maxtemp > 4) 
-		factor = 90;
-	else if (sc->sc_target_temp - maxtemp > 1) 
-		factor = 95;
-	else
-		factor = 100;
-
-	for (i = 0; i < sc->sc_nfans; i++) 
-		smu_fan_set_rpm(smu, &sc->sc_fans[i],
-		    (sc->sc_fans[i].setpoint * factor) / 100);
-}
-
-static void
 smu_set_sleepled(void *xdev, int onoff)
 {
 	static struct smu_cmd cmd;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.