History

ALSA: seq: Bind UMP device

This patch introduces a new ALSA sequencer client for the kernel UMP object, snd-seq-ump-client. It's a UMP version of snd-seq-midi driver, while this driver creates a sequencer client per UMP endpoint which contains (fixed) 16 ports. The UMP rawmidi device is opened in APPEND mode for output, so that multiple sequencer clients can share the same UMP endpoint, as well as the legacy UMP rawmidi devices that are opened in APPEND mode, too. For input, on the other hand, the incoming data is processed on the fly in the dedicated hook, hence it doesn't open a rawmidi device. The UMP packet group is updated upon delivery depending on the target sequencer port (which corresponds to the actual UMP group). Each sequencer port sets a new port type bit, SNDRV_SEQ_PORT_TYPE_MIDI_UMP, in addition to the other standard types for MIDI.

Takashi Iwai committed 3 years ago

Commit 81fd4441 parent 329ffe17 files · +439 −2Browse files

Unified · Split

7 files
  1. include/sound/seq_device.h +1 −0
  2. include/sound/ump.h +14 −1
  3. include/uapi/sound/asequencer.h +1 −0
  4. sound/core/seq/Kconfig +5 −0
  5. sound/core/seq/Makefile +2 −0
  6. sound/core/seq/seq_ump_client.c +389 −0
  7. sound/core/ump.c +27 −1
include/sound/seq_device.h+1−0
@@ -78,5 +78,6 @@ void snd_seq_driver_unregister(struct snd_seq_driver *drv); · 77 unmodified lines
7878 */
7979 #define SNDRV_SEQ_DEV_ID_MIDISYNTH "seq-midi"
8080 #define SNDRV_SEQ_DEV_ID_OPL3 "opl3-synth"
81+#define SNDRV_SEQ_DEV_ID_UMP "seq-ump-client"
8182
8283 #endif /* __SOUND_SEQ_DEVICE_H */
include/sound/ump.h+14−1
@@ -11,6 +11,7 @@ struct snd_ump_endpoint; · 10 unmodified lines
1111 struct snd_ump_block;
1212 struct snd_ump_ops;
1313 struct ump_cvt_to_ump;
14+struct snd_seq_ump_ops;
1415
1516 struct snd_ump_endpoint {
1617 struct snd_rawmidi core; /* raw UMP access */
@@ -30,9 +31,9 @@ struct snd_ump_endpoint { · 13 unmodified lines
3031 int input_buf_head;
3132 int input_pending;
3233
33−#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
3434 struct mutex open_mutex;
3535
36+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
3637 spinlock_t legacy_locks[2];
3738 struct snd_rawmidi *legacy_rmidi;
3839 struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS];
@@ -42,6 +43,12 @@ struct snd_ump_endpoint { · 3 unmodified lines
4243 struct snd_rawmidi_file legacy_out_rfile;
4344 struct ump_cvt_to_ump *out_cvts;
4445 #endif
46+
47+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
48+ struct snd_seq_device *seq_dev;
49+ const struct snd_seq_ump_ops *seq_ops;
50+ void *seq_client;
51+#endif
4552 };
4653
4754 /* ops filled by UMP drivers */
@@ -52,6 +59,12 @@ struct snd_ump_ops { · 4 unmodified lines
5259 void (*drain)(struct snd_ump_endpoint *ump, int dir);
5360 };
5461
62+/* ops filled by sequencer binding */
63+struct snd_seq_ump_ops {
64+ void (*input_receive)(struct snd_ump_endpoint *ump,
65+ const u32 *data, int words);
66+};
67+
5568 struct snd_ump_block {
5669 struct snd_ump_block_info info;
5770 struct snd_ump_endpoint *ump;
include/uapi/sound/asequencer.h+1−0
@@ -439,6 +439,7 @@ struct snd_seq_remove_events { · 438 unmodified lines
439439 #define SNDRV_SEQ_PORT_TYPE_MIDI_XG (1<<4) /* XG compatible device */
440440 #define SNDRV_SEQ_PORT_TYPE_MIDI_MT32 (1<<5) /* MT-32 compatible device */
441441 #define SNDRV_SEQ_PORT_TYPE_MIDI_GM2 (1<<6) /* General MIDI 2 compatible device */
442+#define SNDRV_SEQ_PORT_TYPE_MIDI_UMP (1<<7) /* UMP */
442443
443444 /* other standards...*/
444445 #define SNDRV_SEQ_PORT_TYPE_SYNTH (1<<10) /* Synth device (no MIDI compatible - direct wavetable) */
sound/core/seq/Kconfig+5−0
@@ -62,6 +62,7 @@ config SND_SEQ_VIRMIDI · 61 unmodified lines
6262
6363 config SND_SEQ_UMP
6464 bool "Support for UMP events"
65+ default y if SND_SEQ_UMP_CLIENT
6566 help
6667 Say Y here to enable the support for handling UMP (Universal MIDI
6768 Packet) events via ALSA sequencer infrastructure, which is an
@@ -69,4 +70,8 @@ config SND_SEQ_UMP · 1 unmodified line
6970 It includes the automatic conversion of ALSA sequencer events
7071 among legacy and UMP clients.
7172
73+config SND_SEQ_UMP_CLIENT
74+ tristate
75+ def_tristate SND_UMP
76+
7277 endif # SND_SEQUENCER
sound/core/seq/Makefile+2−0
@@ -14,12 +14,14 @@ snd-seq-midi-emul-objs := seq_midi_emul.o · 13 unmodified lines
1414 snd-seq-midi-event-objs := seq_midi_event.o
1515 snd-seq-dummy-objs := seq_dummy.o
1616 snd-seq-virmidi-objs := seq_virmidi.o
17+snd-seq-ump-client-objs := seq_ump_client.o
1718
1819 obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o
1920 obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/
2021
2122 obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
2223 obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o
24+obj-$(CONFIG_SND_SEQ_UMP_CLIENT) += snd-seq-ump-client.o
2325 obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o
2426 obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o
2527 obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o
sound/core/seq/seq_ump_client.c+389−0
@@ -0,0 +1,389 @@
1+// SPDX-License-Identifier: GPL-2.0-or-later
2+/* ALSA sequencer binding for UMP device */
3+
4+#include <linux/init.h>
5+#include <linux/slab.h>
6+#include <linux/errno.h>
7+#include <linux/mutex.h>
8+#include <linux/string.h>
9+#include <linux/module.h>
10+#include <asm/byteorder.h>
11+#include <sound/core.h>
12+#include <sound/ump.h>
13+#include <sound/seq_kernel.h>
14+#include <sound/seq_device.h>
15+#include "seq_clientmgr.h"
16+
17+struct seq_ump_client;
18+struct seq_ump_group;
19+
20+enum {
21+ STR_IN = SNDRV_RAWMIDI_STREAM_INPUT,
22+ STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT
23+};
24+
25+/* object per UMP group; corresponding to a sequencer port */
26+struct seq_ump_group {
27+ int group; /* group index (0-based) */
28+ unsigned int dir_bits; /* directions */
29+ bool active; /* activeness */
30+ char name[64]; /* seq port name */
31+};
32+
33+/* context for UMP input parsing, per EP */
34+struct seq_ump_input_buffer {
35+ unsigned char len; /* total length in words */
36+ unsigned char pending; /* pending words */
37+ unsigned char type; /* parsed UMP packet type */
38+ unsigned char group; /* parsed UMP packet group */
39+ u32 buf[4]; /* incoming UMP packet */
40+};
41+
42+/* sequencer client, per UMP EP (rawmidi) */
43+struct seq_ump_client {
44+ struct snd_ump_endpoint *ump; /* assigned endpoint */
45+ int seq_client; /* sequencer client id */
46+ int opened[2]; /* current opens for each direction */
47+ struct snd_rawmidi_file out_rfile; /* rawmidi for output */
48+ struct seq_ump_input_buffer input; /* input parser context */
49+ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */
50+};
51+
52+/* number of 32bit words for each UMP message type */
53+static unsigned char ump_packet_words[0x10] = {
54+ 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
55+};
56+
57+/* conversion between UMP group and seq port;
58+ * assume the port number is equal with UMP group number (1-based)
59+ */
60+static unsigned char ump_group_to_seq_port(unsigned char group)
61+{
62+ return group + 1;
63+}
64+
65+/* process the incoming rawmidi stream */
66+static void seq_ump_input_receive(struct snd_ump_endpoint *ump,
67+ const u32 *val, int words)
68+{
69+ struct seq_ump_client *client = ump->seq_client;
70+ struct snd_seq_ump_event ev = {};
71+
72+ if (!client->opened[STR_IN])
73+ return;
74+
75+ ev.source.port = ump_group_to_seq_port(ump_message_group(*val));
76+ ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
77+ ev.flags = SNDRV_SEQ_EVENT_UMP;
78+ memcpy(ev.ump, val, words << 2);
79+ snd_seq_kernel_client_dispatch(client->seq_client,
80+ (struct snd_seq_event *)&ev,
81+ true, 0);
82+}
83+
84+/* process an input sequencer event; only deal with UMP types */
85+static int seq_ump_process_event(struct snd_seq_event *ev, int direct,
86+ void *private_data, int atomic, int hop)
87+{
88+ struct seq_ump_client *client = private_data;
89+ struct snd_rawmidi_substream *substream;
90+ struct snd_seq_ump_event *ump_ev;
91+ unsigned char type;
92+ int len;
93+
94+ substream = client->out_rfile.output;
95+ if (!substream)
96+ return -ENODEV;
97+ if (!snd_seq_ev_is_ump(ev))
98+ return 0; /* invalid event, skip */
99+ ump_ev = (struct snd_seq_ump_event *)ev;
100+ type = ump_message_type(ump_ev->ump[0]);
101+ len = ump_packet_words[type];
102+ if (len > 4)
103+ return 0; // invalid - skip
104+ snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);
105+ return 0;
106+}
107+
108+/* open the rawmidi */
109+static int seq_ump_client_open(struct seq_ump_client *client, int dir)
110+{
111+ struct snd_ump_endpoint *ump = client->ump;
112+ int err = 0;
113+
114+ mutex_lock(&ump->open_mutex);
115+ if (dir == STR_OUT && !client->opened[dir]) {
116+ err = snd_rawmidi_kernel_open(&ump->core, 0,
117+ SNDRV_RAWMIDI_LFLG_OUTPUT |
118+ SNDRV_RAWMIDI_LFLG_APPEND,
119+ &client->out_rfile);
120+ if (err < 0)
121+ goto unlock;
122+ }
123+ client->opened[dir]++;
124+ unlock:
125+ mutex_unlock(&ump->open_mutex);
126+ return err;
127+}
128+
129+/* close the rawmidi */
130+static int seq_ump_client_close(struct seq_ump_client *client, int dir)
131+{
132+ struct snd_ump_endpoint *ump = client->ump;
133+
134+ mutex_lock(&ump->open_mutex);
135+ if (!--client->opened[dir])
136+ if (dir == STR_OUT)
137+ snd_rawmidi_kernel_release(&client->out_rfile);
138+ mutex_unlock(&ump->open_mutex);
139+ return 0;
140+}
141+
142+/* sequencer subscription ops for each client */
143+static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)
144+{
145+ struct seq_ump_client *client = pdata;
146+
147+ return seq_ump_client_open(client, STR_IN);
148+}
149+
150+static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)
151+{
152+ struct seq_ump_client *client = pdata;
153+
154+ return seq_ump_client_close(client, STR_IN);
155+}
156+
157+static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)
158+{
159+ struct seq_ump_client *client = pdata;
160+
161+ return seq_ump_client_open(client, STR_OUT);
162+}
163+
164+static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)
165+{
166+ struct seq_ump_client *client = pdata;
167+
168+ return seq_ump_client_close(client, STR_OUT);
169+}
170+
171+/* fill port_info from the given UMP EP and group info */
172+static void fill_port_info(struct snd_seq_port_info *port,
173+ struct seq_ump_client *client,
174+ struct seq_ump_group *group)
175+{
176+ unsigned int rawmidi_info = client->ump->core.info_flags;
177+
178+ port->addr.client = client->seq_client;
179+ port->addr.port = ump_group_to_seq_port(group->group);
180+ port->capability = 0;
181+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)
182+ port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
183+ SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
184+ SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
185+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)
186+ port->capability |= SNDRV_SEQ_PORT_CAP_READ |
187+ SNDRV_SEQ_PORT_CAP_SYNC_READ |
188+ SNDRV_SEQ_PORT_CAP_SUBS_READ;
189+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
190+ port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
191+ if (group->dir_bits & (1 << STR_IN))
192+ port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
193+ if (group->dir_bits & (1 << STR_OUT))
194+ port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
195+ port->ump_group = group->group + 1;
196+ if (!group->active)
197+ port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;
198+ port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
199+ SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
200+ SNDRV_SEQ_PORT_TYPE_HARDWARE |
201+ SNDRV_SEQ_PORT_TYPE_PORT;
202+ port->midi_channels = 16;
203+ if (*group->name)
204+ snprintf(port->name, sizeof(port->name), "Group %d (%s)",
205+ group->group + 1, group->name);
206+ else
207+ sprintf(port->name, "Group %d", group->group + 1);
208+}
209+
210+/* create a new sequencer port per UMP group */
211+static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
212+{
213+ struct seq_ump_group *group = &client->groups[group_index];
214+ struct snd_seq_port_info *port;
215+ struct snd_seq_port_callback pcallbacks;
216+ int err;
217+
218+ port = kzalloc(sizeof(*port), GFP_KERNEL);
219+ if (!port) {
220+ err = -ENOMEM;
221+ goto error;
222+ }
223+
224+ fill_port_info(port, client, group);
225+ port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
226+ memset(&pcallbacks, 0, sizeof(pcallbacks));
227+ pcallbacks.owner = THIS_MODULE;
228+ pcallbacks.private_data = client;
229+ pcallbacks.subscribe = seq_ump_subscribe;
230+ pcallbacks.unsubscribe = seq_ump_unsubscribe;
231+ pcallbacks.use = seq_ump_use;
232+ pcallbacks.unuse = seq_ump_unuse;
233+ pcallbacks.event_input = seq_ump_process_event;
234+ port->kernel = &pcallbacks;
235+ err = snd_seq_kernel_client_ctl(client->seq_client,
236+ SNDRV_SEQ_IOCTL_CREATE_PORT,
237+ port);
238+ error:
239+ kfree(port);
240+ return err;
241+}
242+
243+/* update dir_bits and active flag for all groups in the client */
244+static void update_group_attrs(struct seq_ump_client *client)
245+{
246+ struct snd_ump_block *fb;
247+ struct seq_ump_group *group;
248+ int i;
249+
250+ for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
251+ group = &client->groups[i];
252+ *group->name = 0;
253+ group->dir_bits = 0;
254+ group->active = 0;
255+ group->group = i;
256+ }
257+
258+ list_for_each_entry(fb, &client->ump->block_list, list) {
259+ if (fb->info.first_group < 0 ||
260+ fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)
261+ break;
262+ group = &client->groups[fb->info.first_group];
263+ for (i = 0; i < fb->info.num_groups; i++, group++) {
264+ if (fb->info.active)
265+ group->active = 1;
266+ switch (fb->info.direction) {
267+ case SNDRV_UMP_DIR_INPUT:
268+ group->dir_bits |= (1 << STR_IN);
269+ break;
270+ case SNDRV_UMP_DIR_OUTPUT:
271+ group->dir_bits |= (1 << STR_OUT);
272+ break;
273+ case SNDRV_UMP_DIR_BIDIRECTION:
274+ group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN);
275+ break;
276+ }
277+ if (!*fb->info.name)
278+ continue;
279+ if (!*group->name) {
280+ /* store the first matching name */
281+ strscpy(group->name, fb->info.name,
282+ sizeof(group->name));
283+ } else {
284+ /* when overlapping, concat names */
285+ strlcat(group->name, ", ", sizeof(group->name));
286+ strlcat(group->name, fb->info.name,
287+ sizeof(group->name));
288+ }
289+ }
290+ }
291+}
292+
293+/* release the client resources */
294+static void seq_ump_client_free(struct seq_ump_client *client)
295+{
296+ if (client->seq_client >= 0)
297+ snd_seq_delete_kernel_client(client->seq_client);
298+
299+ client->ump->seq_ops = NULL;
300+ client->ump->seq_client = NULL;
301+
302+ kfree(client);
303+}
304+
305+/* update the MIDI version for the given client */
306+static void setup_client_midi_version(struct seq_ump_client *client)
307+{
308+ struct snd_seq_client *cptr;
309+
310+ cptr = snd_seq_kernel_client_get(client->seq_client);
311+ if (!cptr)
312+ return;
313+ if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
314+ cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
315+ else
316+ cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
317+ snd_seq_kernel_client_put(cptr);
318+}
319+
320+static const struct snd_seq_ump_ops seq_ump_ops = {
321+ .input_receive = seq_ump_input_receive,
322+};
323+
324+/* create a sequencer client and ports for the given UMP endpoint */
325+static int snd_seq_ump_probe(struct device *_dev)
326+{
327+ struct snd_seq_device *dev = to_seq_dev(_dev);
328+ struct snd_ump_endpoint *ump = dev->private_data;
329+ struct snd_card *card = dev->card;
330+ struct seq_ump_client *client;
331+ int p, err;
332+
333+ client = kzalloc(sizeof(*client), GFP_KERNEL);
334+ if (!client)
335+ return -ENOMEM;
336+
337+ client->ump = ump;
338+
339+ client->seq_client =
340+ snd_seq_create_kernel_client(card, ump->core.device,
341+ ump->core.name);
342+ if (client->seq_client < 0) {
343+ err = client->seq_client;
344+ goto error;
345+ }
346+
347+ setup_client_midi_version(client);
348+ update_group_attrs(client);
349+
350+ for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
351+ err = seq_ump_group_init(client, p);
352+ if (err < 0)
353+ goto error;
354+ }
355+
356+ ump->seq_client = client;
357+ ump->seq_ops = &seq_ump_ops;
358+ return 0;
359+
360+ error:
361+ seq_ump_client_free(client);
362+ return err;
363+}
364+
365+/* remove a sequencer client */
366+static int snd_seq_ump_remove(struct device *_dev)
367+{
368+ struct snd_seq_device *dev = to_seq_dev(_dev);
369+ struct snd_ump_endpoint *ump = dev->private_data;
370+
371+ if (ump->seq_client)
372+ seq_ump_client_free(ump->seq_client);
373+ return 0;
374+}
375+
376+static struct snd_seq_driver seq_ump_driver = {
377+ .driver = {
378+ .name = KBUILD_MODNAME,
379+ .probe = snd_seq_ump_probe,
380+ .remove = snd_seq_ump_remove,
381+ },
382+ .id = SNDRV_SEQ_DEV_ID_UMP,
383+ .argsize = 0,
384+};
385+
386+module_snd_seq_driver(seq_ump_driver);
387+
388+MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
389+MODULE_LICENSE("GPL");
sound/core/ump.c+27−1
@@ -132,8 +132,8 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, · 131 unmodified lines
132132 if (!ump)
133133 return -ENOMEM;
134134 INIT_LIST_HEAD(&ump->block_list);
135−#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
136135 mutex_init(&ump->open_mutex);
136+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
137137 spin_lock_init(&ump->legacy_locks[0]);
138138 spin_lock_init(&ump->legacy_locks[1]);
139139 #endif
@@ -166,8 +166,30 @@ EXPORT_SYMBOL_GPL(snd_ump_endpoint_new); · 26 unmodified lines
166166 * Device register / unregister hooks;
167167 * do nothing, placeholders for avoiding the default rawmidi handling
168168 */
169+
170+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
171+static void snd_ump_dev_seq_free(struct snd_seq_device *device)
172+{
173+ struct snd_ump_endpoint *ump = device->private_data;
174+
175+ ump->seq_dev = NULL;
176+}
177+#endif
178+
169179 static int snd_ump_dev_register(struct snd_rawmidi *rmidi)
170180 {
181+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
182+ struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
183+ int err;
184+
185+ err = snd_seq_device_new(ump->core.card, ump->core.device,
186+ SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev);
187+ if (err < 0)
188+ return err;
189+ ump->seq_dev->private_data = ump;
190+ ump->seq_dev->private_free = snd_ump_dev_seq_free;
191+ snd_device_register(ump->core.card, ump->seq_dev);
192+#endif
171193 return 0;
172194 }
173195
@@ -280,6 +302,10 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) · 106 unmodified lines
280302 n = snd_ump_receive_ump_val(ump, *p++);
281303 if (!n)
282304 continue;
305+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
306+ if (ump->seq_ops)
307+ ump->seq_ops->input_receive(ump, ump->input_buf, n);
308+#endif
283309 process_legacy_input(ump, ump->input_buf, n);
284310 }
285311

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t