using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AtCoders {
	class Program {
		static void Main(string[] args) {
	
			var inputArray = retreiveLongArray();
			var inputCount = 2;

			// 不正な入力は処理しない
			if (inputArray.Length != inputCount) {
				Console.WriteLine("invalid input");
				return;
			}

			var H = (int)inputArray[0];
			var W = (int)inputArray[1];

			var maze = new string[H];
			for (int h = 0; h < H; h++) {
				maze[h]= Console.ReadLine();
			}

			var max = 0;

			for (int hl = 0; hl < H; hl++) {
				for (int wl = 0; wl < W; wl++) {
					//#のマスはスタートに使えない
					if (maze[hl][wl]=='#') {
						continue;
					}
					//終わり位置の座標と、何回移動するかの辞書
					var dictionary = new Dictionary<string, int>();

					//スタートのマス
					var startAoki = new Aokikun(hl, wl, 0);
					var aokiList = new List<Aokikun> {
						startAoki
					};

					//スタートにもどらないよう、あらかじめ埋めておく
					dictionary.Add(startAoki.getPosition(), 0);


					while (aokiList.Count > 0) {

						var nextAokiList = new List<Aokikun>();

						foreach (var aoki in aokiList) {

							var h = aoki._h;
							var w = aoki._w;



							//上への移動
							if (h - 1 >= 0 && maze[h - 1][w] == '.') {
								var newAoki = new Aokikun(h - 1, w, aoki._n+1);
								if (!dictionary.ContainsKey(newAoki.getPosition())) {
									nextAokiList.Add(newAoki);
									dictionary.Add(newAoki.getPosition(), newAoki._n);
								}
							}
							//下への移動
							if (h + 1 < H && maze[h + 1][w] == '.') {
								var newAoki = new Aokikun(h + 1, w, aoki._n + 1);
								if (!dictionary.ContainsKey(newAoki.getPosition())) {
									nextAokiList.Add(newAoki);
									dictionary.Add(newAoki.getPosition(), newAoki._n);
								}
							}
							//左への移動
							if (w - 1 >= 0 && maze[h][w-1] == '.') {
								var newAoki = new Aokikun(h , w-1, aoki._n + 1);
								if (!dictionary.ContainsKey(newAoki.getPosition())) {
									nextAokiList.Add(newAoki);
									dictionary.Add(newAoki.getPosition(), newAoki._n);
								}
							}
							//右への移動
							if (w + 1 < W && maze[h][w + 1] == '.') {
								var newAoki = new Aokikun(h, w + 1, aoki._n + 1);
								if (!dictionary.ContainsKey(newAoki.getPosition())) {
									nextAokiList.Add(newAoki);
									dictionary.Add(newAoki.getPosition(), newAoki._n);
								}
							}

						}
						//aokiリストを新しくする
						aokiList = nextAokiList;

					}
					var thisMax = dictionary.Select(x => x.Value).Max();
					max = Math.Max(max, thisMax);

				}
			}


			//答えの出力
			var ans = max;
			Console.WriteLine(ans);

		}

		/// <summary>
		/// スペースで区切られた文字列配列の入力を受け取る
		/// </summary>
		/// <returns></returns>
		private static string[] retreiveStringArray() {
			var inputArray =  Console.ReadLine().Split(' ');
			return inputArray;
		}

		/// <summary>
		/// スペースで区切られた整数配列の入力を受け取る
		/// </summary>
		/// <returns></returns>
		private static long[] retreiveLongArray() {
			var inputArray = Console.ReadLine().Split(' ');
			var longArray = inputArray.Select(s => long.Parse(s));
			return longArray.ToArray();
		}
	}

	public class Aokikun {
		public int _h { get; set; }
		public int _w { get; set; }
		public int _n { get; set; }

		public Aokikun(int h,int w , int n) {
			_h = h;
			_w = w;
			_n = n;
		}

		public string getPosition() {
			return _h + "_" + _w;
		}
	}
}


Measured memory_bytes: 27492